It is currently 16 Apr 2024, 04:39
   
Text Size

Magic Grammar

General Discussion of the Intricacies

Moderator: CCGHQ Admins

Magic Grammar

Postby Arch » 04 Sep 2011, 13:41

I've decided to start up a project to create a grammar for magic card rules. This has been discussed here before but as far as I know it have never really taken off.

Some arbitrary requirements I set forth for this are;

Language / tool independent.

Meaning it should not be tied to a particular programming language or require a certain tool.

Understandable for a computer.

Just getting a grammar could probably be done mechanically rather quickly but it would not be very useful. In order for it to be useful rules in the grammar have to be arranged so that you can figure out what parts do what.
This is probably what most would strive for but I think it's worth mentioning anyways.



The "Overrule" tool:

I implemented a tool to help me develop the grammar. It has two purposes; see that I don't break anything when moving forward and debugging the current work. It performs those duties ok (even though it takes 10+ minutes for a complete run-through of everything).

It also presents the output from passing a rule through the grammar. There's a "quickstart" version linked below if anyone wants to try it out. Check out the "about" tab for a minimal manual in that case.



The grammar:

The grammar is ISO EBNF with a few exceptions. Exceptions (A matches B but not C) and comments are not supported. This also means that 1+ repetition is not supported. I have while prototyping not found any need for exceptions so I've not implemented support for it. Comments will probably be implemented as the grammar grows and need for documentation and explantion arises. This is of course a limitation in the validation tool and not a problem with the grammar.

Special forms are used to move "variables" out of the grammar. Enumerated items that are expected to change (or if there's just a whole bunch of them) reside in .special-files. This goes for things such as all the various types. This is to make it easier to inject new items without having to change the actual grammar. Will probably refactor keywords to use that format in the future as well.



Current status:

The overrule tool is "complete". Meaning I'm currently satified with how it works. There are improvements to be made but nothing that needs immediate attention.

The grammar itself is just getting started. It currently handles most keywords and some other arbitrarily chosen rules. Basically the easy stuff.

I've also been looking for other magic grammars. The only one I actually know about though is https://bitbucket.org/eswald/incantus/s ... 3/parsing/ . If anyone has something laying around I'd be interested in seeing it.

Other than that any help is welcome. It might be a bit early to ask for extra hands since there's not much to work on yet but advice and general comments are welcome too.


Links:

Overrule - application only
http://dl.dropbox.com/u/2771470/overrul ... dalone.jar

Overrule - "quickstart"
http://dl.dropbox.com/u/2771470/overrule/overrule.zip

cards.xml
viewtopic.php?f=27&t=1347

core.ebnf
https://github.com/karmag/loa-grammar

Overrule code
https://github.com/karmag/overrule
https://github.com/karmag/bana (dependency)
https://github.com/karmag/loa-util (dependency)

ISO EBNF (actually the final draft version)
http://www.cl.cam.ac.uk/~mgk25/iso-14977.pdf

EBNF on wikipedia
http://en.wikipedia.org/wiki/Extended_B ... 3Naur_Form
User avatar
Arch
Programmer
 
Posts: 206
Joined: 04 Jul 2009, 09:35
Has thanked: 0 time
Been thanked: 15 times

Re: Magic Grammar

Postby silly freak » 06 Sep 2011, 08:31

Hi! Umm... not wanting to put your work down, but... is overrule yet another tool for parsing grammars? if so, why not e.g. antlr in the first place? it's written in java, but supports many target languages. I think that its three-stage model (lexer-parser-tree parser) would also help language independence, because each programmer could write his/her own tree grammar for the desired target language with the desired transformations. The main parsing, i.e. building the tree, is still language independent.

okay, sorry for that. otherwise, thanks for your efforts! I'm glad something like this is done and hope I can help!
___

where's the "trust me, that will work!" switch for the compiler?
Laterna Magica - blog, forum, project, 2010/09/06 release!
silly freak
DEVELOPER
 
Posts: 598
Joined: 26 Mar 2009, 07:18
Location: Vienna, Austria
Has thanked: 93 times
Been thanked: 25 times

Re: Magic Grammar

Postby Arch » 06 Sep 2011, 18:14

No need to apologize. It's a legitimate question.

Basically I've been trying to get a grammar of the ground for some time now.

The grammar has not been the only goal though. Clojure, which I'm using, is really nice for things like manipulating and transforming lists, map and related datatypes. So I've had a lot of fun just playing around with that. I have two prior projects similar to this one that I scrapped. This is the first project I felt had a good potential of going somewhere which is why I'm putting it up here.

I've been aware of antlr but haven't taken the time to investigate it beyond the quickstart guide. In the future I need to find a way to speed up bulk-work to shorten the feedback cycle and antlr is a definite contender there. I'm guessing it's way faster than my implementation. The deliverable here is the grammar itself, which is why I'm accepting my own tool for now. A tool is just a tool and I'm fully ready to replace it if need be or if something better comes along but for now it's fine by me.

So in short: as a hobbyist - not using antlr was more fun.
User avatar
Arch
Programmer
 
Posts: 206
Joined: 04 Jul 2009, 09:35
Has thanked: 0 time
Been thanked: 15 times

Re: Magic Grammar

Postby silly freak » 07 Sep 2011, 11:27

Arch wrote:So in short: as a hobbyist - not using antlr was more fun.
having implemented two or three configuration file formats using different technologies, that's a perfectly valid explanation ;)
___

where's the "trust me, that will work!" switch for the compiler?
Laterna Magica - blog, forum, project, 2010/09/06 release!
silly freak
DEVELOPER
 
Posts: 598
Joined: 26 Mar 2009, 07:18
Location: Vienna, Austria
Has thanked: 93 times
Been thanked: 25 times

Re: Magic Grammar

Postby Max mtg » 07 Oct 2011, 23:29

silly freak wrote:Hi! Umm... not wanting to put your work down, but... is overrule yet another tool for parsing grammars? if so, why not e.g. antlr in the first place? it's written in java, but supports many target languages. I think that its three-stage model (lexer-parser-tree parser) would also help language independence, because each programmer could write his/her own tree grammar for the desired target language with the desired transformations. The main parsing, i.e. building the tree, is still language independent.

okay, sorry for that. otherwise, thanks for your efforts! I'm glad something like this is done and hope I can help!
I tried to parse with antlr - got no success. It's because antlr need to tokenize everything. I cannot just tell its lexer to work differently for dirrefent parts of phrase.

Decided to make my own parser on C#. The first part of my work is about dividing text into sub-sentences, like triggers, conditions etc. Later will parse each part and determine its effects of requirements
Single class for single responsibility.
Max mtg
Programmer
 
Posts: 1997
Joined: 02 Jul 2011, 14:26
Has thanked: 173 times
Been thanked: 334 times

Re: Magic Grammar

Postby silly freak » 08 Oct 2011, 16:52

i have a really basic and ATM kinda stupid grammar written in ANTLR. It supports only a few phrases (ATM ignoring things like targeting and more sophisticated effects, see below) but successfully parses what it supports into what I need: Java ability object that my program can execute. (well, it should work, but I'm not finished testing whether it really works in the game, because of a giant restructuring going on at the same time)

Can you be more specific where it is that you have problems with non-context-specific grammars? It is possible to do it with semantic predicates in ANTLR, but possibly not elegant. My "workaround" for mana, which might look a little like tap and untap, is to parse mana sequences as a single token and then parse that string using a separate mana grammar. (the mana grammar is an "island grammar", so I've heard)

Ok, below is my ANTLR code and an overview of the working phrases:
"what works" | Open
Code: Select all
Triggers:
  When ~ enters the battlefield
Costs:
  {T}
  {Q}
  <<Mana costs like {W}{G/U} etc.>>
  Sacrifice ~
Effects:
  Add <<Mana>> to your mana pool.
  ~ deals <<Number>> damage to all creatures.
  Destroy all creatures. (They can't be regenerated.)
  Draw a card.
  Draw <<Number>> cards.
All these can be combined to form effects like
  {T}: Draw a card.
  Destroy all creatures. They can't be regenerated. <<i.e. a spell>>
  When ~ enters the battlefield, ~ deals 2 damage to all creatures.
You see, I took the easy way for things like "all creatures" instead of making a rule for it, but I only need the grammar for test cards ATM, and I don't really see how I'm limited from enhancing it later if the tree rules can return e.g. target specifications that the effects can then use.

"imports/CostParser.g" | Open
Code: Select all
parser grammar CostParser;

tokens {
  COST;
  SACRIFICE;
}

cost:
  cost0 (COMMA! cost0)*;

fragment cost0:
  manaCost|tapCost|untapCost|sacrificeCost;

fragment manaCost:
  MANA
  -> ^(COST MANA);

fragment tapCost:
  TAP
  -> ^(COST TAP);

fragment untapCost:
  UNTAP
  -> ^(COST UNTAP);
 
fragment sacrificeCost:
  Sacrifice SELF
  -> ^(COST ^(SACRIFICE SELF));
 
"imports/CostTree.g" | Open
Code: Select all
tree grammar CostTree;

cost returns [Function<? super ActivateAction, ? extends PlayInformation> result]:
  ^(COST
    (manaCost {
      result = $manaCost.result;
    } |tapCost {
      result = $tapCost.result;
    } |untapCost {
      result = $untapCost.result;
    } |sacrificeSelfCost {
      result = $sacrificeSelfCost.result;
    })
  );

manaCost returns [Function<? super ActivateAction, ? extends PlayInformation> result]:
  manaSequence {
    result = costs.getManaCost($manaSequence.result);
  };

tapCost returns [Function<? super ActivateAction, ? extends PlayInformation> result]:
  TAP {
    result = costs.getTapCost();
  };

untapCost returns [Function<? super ActivateAction, ? extends PlayInformation> result]:
  UNTAP {
    result = costs.getUntapCost();
  };

sacrificeSelfCost returns [Function<? super ActivateAction, ? extends PlayInformation> result]:
  ^(SACRIFICE SELF) {
    result = costs.getSacrificeSelfCost();
  };

"imports/EffectParser.g" | Open
Code: Select all
parser grammar EffectParser;

tokens {
  EFFECT;
  MANA_EFFECT;
  DAMAGE_ALL_EFFECT;
  DESTROY_ALL_EFFECT;
  DRAW_CARDS_EFFECT;
}

effect:
  effect0+;

fragment effect0:
  addManaEffect|damageAllEffect|destroyAllEffect|drawCardsEffect;

fragment addManaEffect:
  Add MANA To Your Mana Pool PERIOD
  -> ^(EFFECT ^(MANA_EFFECT MANA));

fragment damageAllEffect:
  SELF Deals NUMBER Damage To All Creatures PERIOD
  -> ^(EFFECT ^(DAMAGE_ALL_EFFECT NUMBER));

fragment destroyAllEffect:
  Destroy All Creatures PERIOD (They Cant Be Regenerated PERIOD)?
  -> ^(EFFECT DESTROY_ALL_EFFECT);

fragment drawCardsEffect:
  Draw (A Card|NUMBER Cards) PERIOD
  -> ^(EFFECT ^(DRAW_CARDS_EFFECT A? NUMBER?));

"imports/EffectTree.g" | Open
Code: Select all
tree grammar EffectTree;

effect returns [Function<? super PlayAction, ? extends PlayInformation> result, boolean mana]:
  ^(EFFECT
    (addManaEffect {
      $result = $addManaEffect.result;
      $mana = $addManaEffect.mana;
    } |damageAllEffect {
      $result = $damageAllEffect.result;
      $mana = $damageAllEffect.mana;
    } |destroyAllEffect {
      $result = $destroyAllEffect.result;
      $mana = $destroyAllEffect.mana;
    } |drawCardsEffect {
      $result = $drawCardsEffect.result;
      $mana = $drawCardsEffect.mana;
    })
  );

addManaEffect returns [Function<? super PlayAction, ? extends PlayInformation> result, boolean mana]:
  ^(MANA_EFFECT manaSequence) {
    $result = effects.getAddManaEffect($manaSequence.result);
    $mana = true;
  };

damageAllEffect returns [Function<? super PlayAction, ? extends PlayInformation> result, boolean mana]:
  ^(DAMAGE_ALL_EFFECT number) {
    $result = effects.getDamageAllEffect($number.result, true, false);
    $mana = false;
  };

destroyAllEffect returns [Function<? super PlayAction, ? extends PlayInformation> result, boolean mana]:
  DESTROY_ALL_EFFECT {
    $result = effects.getDestroyAllEffect(true);
    $mana = false;
  };

drawCardsEffect returns [Function<? super PlayAction, ? extends PlayInformation> result, boolean mana]:
{
  int num = 1;
} ^(DRAW_CARDS_EFFECT
    (A|number {
      num = $number.result;
    })
  ) {
  $result = effects.getDrawCardsEffect(num);
  $mana = false;
};

"imports/ManaLexer.g" | Open
Code: Select all
lexer grammar ManaLexer;

options {
  language = Java;
//  superClass = AbstractOracleLexer;
}

MANA:
  SYMBOL+;

fragment SYMBOL:
  OBRACE PART (SLASH PART)* CBRACE;

fragment PART:
  WHITE|BLUE|BLACK|RED|GREEN|SNOW|VARIABLE|NUMBER;

//raw fragment
fragment OBRACE: '{';
fragment SLASH:  '/';
fragment CBRACE: '}';

fragment WHITE: ('W' | 'w');
fragment BLUE:  ('U' | 'u');
fragment BLACK: ('B' | 'b');
fragment RED:   ('R' | 'r');
fragment GREEN: ('G' | 'g');

fragment SNOW:  ('S' | 's');

fragment VARIABLE: ('X' | 'Y' | 'Z' | 'x' | 'y' | 'z');

fragment NUMBER: '0' | ('1'..'9') ('0'..'9')*;

"imports/ManaTree.g" | Open
Code: Select all
tree grammar ManaTree;

manaSymbol returns [ManaSymbol result]:
  MANA {result = ManaFactory.parseSymbol($MANA.text);};

manaSequence returns [ManaSequence result]:
  MANA {result = ManaFactory.parseSequence($MANA.text);};

"imports/TriggerParser.g" | Open
Code: Select all
parser grammar TriggerParser;

tokens {
  TRIGGER;
  ETB;
}

trigger:
  trigger0;

fragment trigger0:
  selfEtbTrigger;

fragment selfEtbTrigger:
  When SELF Enters The Battlefield
  -> ^(TRIGGER ^(ETB SELF));

"imports/TriggerTree.g" | Open
Code: Select all
tree grammar TriggerTree;

trigger returns [Predicate<? super TriggerAction> result]:
  ^(TRIGGER
    (selfEtbTrigger {
      result = $selfEtbTrigger.result;
    })
  );

selfEtbTrigger returns [Predicate<? super TriggerAction> result]:
  ^(ETB SELF) {
    result = triggers.getSelfEtbTrigger();
  };

"imports/Words.g" | Open
Code: Select all
lexer grammar Words;

A: 'a';
Add: 'add';
All: 'all';
Battlefield: 'battlefield';
Be: 'be';
Cant: 'can\'t';
Card: 'card';
Cards: 'cards';
Creatures: 'creatures';
Damage: 'damage';
Deals: 'deals';
Destroy: 'destroy';
Draw: 'draw';
Enters: 'enters';
Mana: 'mana';
Pool: 'pool';
Regenerated: 'regenerated';
Sacrifice: 'sacrifice';
The: 'the';
They: 'they';
To: 'to';
When: 'when';
You: 'you';
Your: 'your';

"net/slightlymagic/laterna/magica/abilities/oracle/OracleLexer.g" | Open
Code: Select all
lexer grammar OracleLexer;

options {
  language = Java;
//  superClass = AbstractOracleLexer;
}

import Words, ManaLexer;

@header {
package net.slightlymagic.laterna.magica.abilities.oracle;

import net.slightlymagic.laterna.magica.abilities.oracle.WrapperException;
}

@members {
@Override
public void reportError(RecognitionException e) {
    throw new WrapperException(e);
}


TAP: '{t}';
UNTAP: '{q}';

COLON: ':';
COMMA: ',';
PERIOD: '.';
SELF: '~';

NUMBER: '0' | ('1'..'9') ('0'..'9')*;

WS: (' '|'\t'|'\f'|'\r'|'\n')+ {$channel=HIDDEN;};
REMINDER: '(' (~')')* ')' {$channel=HIDDEN;};

"net/slightlymagic/laterna/magica/abilities/oracle/OracleParser.g" | Open
Code: Select all
parser grammar OracleParser;

options {
  language   = Java;
  output     = AST;
  tokenVocab = OracleLexer;
//  superClass = AbstractOracleParser;
}

import CostParser, EffectParser, TriggerParser;

tokens {
  ACTIVATED_ABILITY;
  TRIGGERED_ABILITY;
  SPELL_EFFECT;
}

@header {
package net.slightlymagic.laterna.magica.abilities.oracle;
}

@members {
@Override
protected Object recoverFromMismatchedToken(IntStream input, int ttype, BitSet follow) throws RecognitionException {
    throw new MismatchedTokenException(ttype, input);
}

@Override
public Object recoverFromMismatchedSet(IntStream input, RecognitionException e, BitSet follow) throws RecognitionException {
    throw e;
}
}

@rulecatch {
catch (RecognitionException e) {
    throw e;
}
}

ability:
  EOF | activatedAbility | triggeredAbility | spellEffect;

activatedAbility:
  cost COLON effect
  -> ^(ACTIVATED_ABILITY cost effect);

triggeredAbility:
  trigger COMMA effect
  -> ^(TRIGGERED_ABILITY trigger effect);

spellEffect:
  effect
  -> ^(SPELL_EFFECT effect);

"net/slightlymagic/laterna/magica/abilities/oracle/OracleTree.g" | Open
Code: Select all
tree grammar OracleTree;

options {
  language     = Java;
  tokenVocab   = OracleParser;
  ASTLabelType = CommonTree;
  superClass   = AbstractOracleTree;
}

import ManaTree, CostTree, EffectTree, TriggerTree;

@header {
package net.slightlymagic.laterna.magica.abilities.oracle;

import static net.slightlymagic.laterna.magica.mana.ManaFactory.ManaFactory;

import com.google.common.base.Function;
import com.google.common.base.Predicate;

import net.slightlymagic.laterna.magica.ability.Ability;
import net.slightlymagic.laterna.magica.ability.ActivatedAbility;
import net.slightlymagic.laterna.magica.ability.TriggeredAbility;
import net.slightlymagic.laterna.magica.ability.spell.SpellAction;
import net.slightlymagic.laterna.magica.ability.impl.ActivatedAbilityImpl;

import net.slightlymagic.laterna.magica.action.play.ActivateAction;
import net.slightlymagic.laterna.magica.action.play.PlayAction;
import net.slightlymagic.laterna.magica.action.play.PlayInformation;
import net.slightlymagic.laterna.magica.action.play.TriggerAction;
import net.slightlymagic.laterna.magica.action.play.impl.PlayInformationFunction;

import net.slightlymagic.laterna.magica.cost.impl.ManaCostInformation;
import net.slightlymagic.laterna.magica.cost.impl.SacrificeSelfInformation;
import net.slightlymagic.laterna.magica.cost.impl.TapSymbolInformation;
import net.slightlymagic.laterna.magica.cost.impl.UntapSymbolInformation;

import net.slightlymagic.laterna.magica.effects.impl.AddManaInformation;

import net.slightlymagic.laterna.magica.mana.ManaSequence;
import net.slightlymagic.laterna.magica.mana.ManaSymbol;

import net.slightlymagic.laterna.magica.util.FactoryFunction;
}

@members {
@Override
protected Object recoverFromMismatchedToken(IntStream input, int ttype, BitSet follow) throws RecognitionException {
    throw new MismatchedTokenException(ttype, input);
}

@Override
public Object recoverFromMismatchedSet(IntStream input, RecognitionException e, BitSet follow) throws RecognitionException {
    throw e;
}
}

@rulecatch {
catch (RecognitionException e) {
    throw e;
}
}

number returns [int result]:
  NUMBER {
    result = Integer.parseInt($NUMBER.text);
  };

ability returns [Ability result]:
  EOF {
    result = null;
  } |
  activatedAbility {
    result = $activatedAbility.result;
  } | triggeredAbility {
    result = $triggeredAbility.result;
  } | spellEffect {
    result = $spellEffect.result;
  };

activatedAbility returns [ActivatedAbility result]:
{
  List<Function<? super ActivateAction, ? extends PlayInformation>> costs =
    new ArrayList<Function<? super ActivateAction, ? extends PlayInformation>>();
  List<Function<? super PlayAction, ? extends PlayInformation>> effects =
    new ArrayList<Function<? super PlayAction, ? extends PlayInformation>>();
  boolean mana = false;
} ^(ACTIVATED_ABILITY
    (cost {
      costs.add($cost.result);
    })+
    (effect {
      effects.add($effect.result);
      mana |= $effect.mana;
    })+
  ) {
  result = abilities.getActivatedAbility(costs, effects, mana);
};

triggeredAbility returns [TriggeredAbility result]:
{
  List<Function<? super PlayAction, ? extends PlayInformation>> effects =
    new ArrayList<Function<? super PlayAction, ? extends PlayInformation>>();
  boolean mana = false;
} ^(TRIGGERED_ABILITY
    trigger
    (effect {
      effects.add($effect.result);
      mana |= $effect.mana;
    })+
  ) {
  result = abilities.getTriggeredAbility($trigger.result, effects, mana);
};

spellEffect returns [SpellAction result]:
{
  List<Function<? super PlayAction, ? extends PlayInformation>> effects =
    new ArrayList<Function<? super PlayAction, ? extends PlayInformation>>();
} ^(SPELL_EFFECT
    (effect {
      effects.add($effect.result);
    })+
  ) {
  result = abilities.getSpellEffect(effects);
};

___

where's the "trust me, that will work!" switch for the compiler?
Laterna Magica - blog, forum, project, 2010/09/06 release!
silly freak
DEVELOPER
 
Posts: 598
Joined: 26 Mar 2009, 07:18
Location: Vienna, Austria
Has thanked: 93 times
Been thanked: 25 times

Re: Magic Grammar

Postby Max mtg » 09 Oct 2011, 02:02

I started from the very top level - that is XML. Was stuck when I was about to parse both manacost and name of card with the same lexer rules. Had to define WURGB as mana tokes, which can also be part of name or keyword...
It also looks like, once a part of text has been matched by a token definition, that part of text cannot be recognized as any other token.

In your example you've already substitued CardName with '~' and that makes the processing more... possible.
Single class for single responsibility.
Max mtg
Programmer
 
Posts: 1997
Joined: 02 Jul 2011, 14:26
Has thanked: 173 times
Been thanked: 334 times

Re: Magic Grammar

Postby MageKing17 » 09 Oct 2011, 06:05

Max mtg wrote:In your example you've already substitued CardName with '~' and that makes the processing more... possible.
Something similar to this definitely makes parsing easier; in Magic rules, the name of the card is a special token that always refers to that card and nothing else (except in special cases, like "permanents named such-and-such"). In Incantus, I spent a bit of time replacing references like this with "~" so that run-time replacement of the card name could be done (which is extremely useful for cards like Sakashima the Impostor).
User avatar
MageKing17
Programmer
 
Posts: 473
Joined: 12 Jun 2008, 20:40
Has thanked: 5 times
Been thanked: 9 times

Re: Magic Grammar

Postby Arch » 09 Oct 2011, 08:37

I do the same thing. While I feel like name recognition should be part of the grammar I haven't been able to find a good way to do it. Don't want to have to define all the names but I also don't want to do something like: NAME = *
User avatar
Arch
Programmer
 
Posts: 206
Joined: 04 Jul 2009, 09:35
Has thanked: 0 time
Been thanked: 15 times

Re: Magic Grammar

Postby silly freak » 09 Oct 2011, 11:05

in antlr you should be able to do it with a semantic predicate, where you check whether the character sequence is equal to the name and otherwise fail the lever rule. I'm on my phone and don't want to write an example, but googling for semantic predicate should make clear what I mean
___

where's the "trust me, that will work!" switch for the compiler?
Laterna Magica - blog, forum, project, 2010/09/06 release!
silly freak
DEVELOPER
 
Posts: 598
Joined: 26 Mar 2009, 07:18
Location: Vienna, Austria
Has thanked: 93 times
Been thanked: 25 times

Re: Magic Grammar

Postby Max mtg » 09 Oct 2011, 13:22

silly freak wrote:in antlr you should be able to do it with a semantic predicate, where you check whether the character sequence is equal to the name and otherwise fail the lever rule. I'm on my phone and don't want to write an example, but googling for semantic predicate should make clear what I mean
It's quite complicated, so it seemed an easier thing to write own code in a language I'm already familiar with. Working with antlr leads to situations when I know "which steps solve the problem", but have no idea if it is possible in antlr and how to do that.

For instance: name shortcuts (see rules for Ulamog, the Infinite Gyre), references to previously picked objects or rules (Burn the Impure, Dispatch)

There are also some "abilities inside abilities", where ultimate of Koth of the Hammer is the worst sample - it has two nested abilites, one in another.
Single class for single responsibility.
Max mtg
Programmer
 
Posts: 1997
Joined: 02 Jul 2011, 14:26
Has thanked: 173 times
Been thanked: 334 times

Re: Magic Grammar

Postby silly freak » 24 Oct 2011, 10:26

Max mtg wrote:For instance: name shortcuts (see rules for Ulamog, the Infinite Gyre), references to previously picked objects or rules (Burn the Impure, Dispatch)

There are also some "abilities inside abilities", where ultimate of Koth of the Hammer is the worst sample - it has two nested abilites, one in another.
Name shortcuts... I see how they can be hard, but you know I've sort-of dropped this problem for my grammar. All I (would) do is to search for the part of the name before the comma in addition to the full name in my replacement code.

References... I'm not sure ho I will do it, but I'm sure that I don't worry on the language parsing level about what "that creature" references. When I'm building an ability object out of the parsed ability text for my program, I will.
Personally, I'm even fine if my parser accepts "That creature gains +1/+1." if there is no rules text specifying a creature. I take it as given that the rules text I parse is correct for the most part. (e.g. "draw one card" or "draw 1 card", even "draw one cards") is fine for me, even though real cards always say "draw a card")
Of course, I understand if you ambitions are higher in that regard. My goal is to cover as many abilities as possible to make adding cards to my program easier, and not to parse every single card and nothing but exactly valid rules text, although that would be more than just nice ;)

Nested abilities... not so hard, I think. It's pretty easy to add a lexer rule for any quoted string, and then make a parser rule that parses the quotes' content as an independent ability.

Anyway, I've put together a blog post about my accomplishment so far. Do you (all) have anything to look at? It would be very inspirational!

PS: in my search for quoted abilities, I found "Unattach ~: Prevent all combat damage that would be dealt to this creature this turn." An interesting ability, in that the "~" is actually wrong here, as it should reference the ability-granting and not the ability-having card.
___

where's the "trust me, that will work!" switch for the compiler?
Laterna Magica - blog, forum, project, 2010/09/06 release!
silly freak
DEVELOPER
 
Posts: 598
Joined: 26 Mar 2009, 07:18
Location: Vienna, Austria
Has thanked: 93 times
Been thanked: 25 times

Re: Magic Grammar

Postby Arch » 24 Oct 2011, 16:41

My stuff's available here but it's not much to look at at this point. Currently handles most keywords and some simple abilities. I've name-escaped and cleared duplicates out the rules landing on a total of 11651 rules; 639 parsed. That is pure tree building though, haven't done anything towards integrating them into an engine.

Also with this homebrew of parser I've got I get times like
real 5m59.187s
user 19m23.641s
sys 0m9.821s
when running the full 11k cards. How long does it take for you to parse the full 19710? I would be interesting to see how fast it is even though it might be hard to draw any conclusions from such a comparison.

The unattach thing seems like an interesting problem. You would really need to keep your references straight for that to work out. I wouldn't say it's wrong since it actually should reference itself. Got to make sure to parse/apply/lock it in the right context.
User avatar
Arch
Programmer
 
Posts: 206
Joined: 04 Jul 2009, 09:35
Has thanked: 0 time
Been thanked: 15 times

Re: Magic Grammar

Postby MageKing17 » 24 Oct 2011, 18:47

silly freak wrote:PS: in my search for quoted abilities, I found "Unattach ~: Prevent all combat damage that would be dealt to this creature this turn." An interesting ability, in that the "~" is actually wrong here, as it should reference the ability-granting and not the ability-having card.
Yes, that one's a tricky bastard to parse; on the other hand, the ~ isn't incorrect, because when you display that ability on the card that has it (the base ability), it should display that card's name, regardless of what that name might be. On the other hand, the card it grants the ability to should display the name of the card that gave it to it, regardless of what it might be. That could get trickier, although is made easier by the fact that all such abilities (there are seven) are on equipment, and I know of no way to change the name of an equipment card. Sakashima the Impostor could copy an equipment card that had been temporarily animated, making the ability read "Unattach Sakashima the Impostor: blah blah", but wouldn't actually change the name of the card while attached to something, so I guess we don't have to worry about it; the code to grant the ability to the other card would just take the current name of the artifact it was on and use that in the text of the ability it granted.
User avatar
MageKing17
Programmer
 
Posts: 473
Joined: 12 Jun 2008, 20:40
Has thanked: 5 times
Been thanked: 9 times

Re: Magic Grammar

Postby silly freak » 27 Oct 2011, 07:07

Arch wrote:Also with this homebrew of parser I've got I get times like
real 5m59.187s
user 19m23.641s
sys 0m9.821s
when running the full 11k cards. How long does it take for you to parse the full 19710? I would be interesting to see how fast it is even though it might be hard to draw any conclusions from such a comparison.
What "ability source" do you use? I use Magic Data 2011/10/01, and after filtering that, it seems to contain 11889 unique abilities. what did you mean by real/user/sys in your timings?

Nontheless, I added timing code to my test. Only parsing is timed; reading and writing from the file is done outside. I do, however, create the whole file-output during the main loop, lacking a better idea, and got the following output:

Code: Select all
Total:   19710; Ignored:     0, Successful:  5363, Failed: 14347
Time for 19710 abilities: 1970 ms (99995 ns per ability)

and

Total:   11889; Ignored:     0, Successful:   907, Failed: 10982
Time for 11889 abilities: 1034 ms (86998 ns per ability)
(I'm currently restructuring, so the success number is smaller than "advertised". Timing is done via a System.nanoTime() before and after the whole loop. nanoTime() has nano precision, but not necessarily nano resolution)
___

where's the "trust me, that will work!" switch for the compiler?
Laterna Magica - blog, forum, project, 2010/09/06 release!
silly freak
DEVELOPER
 
Posts: 598
Joined: 26 Mar 2009, 07:18
Location: Vienna, Austria
Has thanked: 93 times
Been thanked: 25 times

Next

Return to Magic Rules Engine Programming

Who is online

Users browsing this forum: No registered users and 14 guests


Who is online

In total there are 14 users online :: 0 registered, 0 hidden and 14 guests (based on users active over the past 10 minutes)
Most users ever online was 4143 on 23 Jan 2024, 08:21

Users browsing this forum: No registered users and 14 guests

Login Form