jschomay / elm-narrative-engine / NarrativeEngine.Syntax.RuleParser

A helper module for easily authoring rules and queries.

See https://github.com/jschomay/elm-narrative-engine/blob/master/src/Example.elm for a full example.

Entity matchers / queries

Example syntax:

PLAYER.current_location=(*.dark).fear<5
CAVE.dark.!explored
*.enemy.current_location=CAVE

These all will get parsed into NarrativeEngine.WorldModel.EntityMatchers.

The format is <entity ID or * or $><one or more queries as defined below>

* will become a MatchAny, otherwise it will be a Match with the supplied ID. $ is passed through directly (to be replaced with the trigger ID eventually).

By convention IDs are capitalized and query keywords are snake case, but they don't have to be.

Each query starts with a .. Query details follow.

Tags

ID.tag1.tag2

becomes

Match "ID" [ HasTag "tag1", HasTag "tag2" ]

Stats

ID.stat1=1.stat2>0.stat3<-2.stat_4>(stat ID1.other_stat).stat_5<(stat ID2.other_stat).stat_6=(stat ID3.other_stat)

becomes

Match "ID"
    [ HasStat "stat1" EQ (SpecificStat 1)
    , HasStat "stat2" GT (SpecificStat 0)
    , HasStat "stat3" LT (SpecificStat -2)
    , HasStat "stat_4" GT (CompareStat "ID1" "other_stat")
    , HasStat "stat_5" LT (CompareStat "ID2" "other_stat")
    , HasStat "stat_6" EQ (CompareStat "ID3" "other_stat")
    ]

Links

ID.link1=ID1.link2=(ID2.tag1).link3=(*.tag2).link4=(link ID3.other_link)

becomes

Match "ID"
    [ HasLink "link1" (SpecificLink (Match "ID1" []))
    , HasLink "link2" (SpecificLink (Match "ID2" [ HasTag "tag1" ]))
    , HasLink "link3" (SpecificLink (MatchAny [ HasTag "tag2" ]))
    , HasLink "link4" (CompareLink "ID3" "other_link")
    ]

Not

Any query segment can be prefixed with a ! for not

*.!tag1.!stat>9.!link=ID2

becomes

MatchAny
    [ Not (HasTag "tag1")
    , Not (HasStat "stat" GT (SpecificStat 9))
    , Not (HasLink "link" (SpecificLink (Match "ID2" [])))
    ]


type alias ParsedMatcher =
Result String NarrativeEngine.Core.WorldModel.EntityMatcher

The result of parsing an "entity matcher" syntax string.

parseMatcher : String -> ParsedMatcher

Parse an "entity matcher" syntax string.

Changes syntax

Example syntax:

Change world:
PLAYER.current_location=$.fear+1
CAVE.explored
(*.enemy).blinded

These all will get parsed into NarrativeEngine.WorldModel.ChangeWorlds.

The format is <entity ID or * or $><one or more changes as defined below>

To UpdateAll, use a generic matcher in parens. Otherwise use an ID for a specific Update. $ is passed through directly (to be replaced with the trigger ID eventually).

Each change starts with a ..

Tags

ID.tag1.-tag2

becomes

Update "ID" [ AddTag "tag1", RemoveTag "tag2" ]

Stats

ID.stat1=1.stat2+1.stat3-2

becomes

Update "ID"
    [ SetStat "stat1" 1
    , IncStat "stat2" 1
    , DecStat "stat3" 2
    ]

Links

ID.link1=ID2.link2=(link ID2.link1)

becomes

Update "ID"
    [ SetLink "link1" (SpecificLinkTarget "ID2")
    , SetLink "link2" (LookUpLinkTarget "ID2" "link1")
    ]

Update all

(*.tag1).tag2

becomes

UpdateAll [ HasTag "tag1" ] [ AddTag "tag2" ]


type alias ParsedChanges =
Result String NarrativeEngine.Core.WorldModel.ChangeWorld

The result of parsing a "change world" syntax string.

parseChanges : String -> ParsedChanges

Parse a "change world" syntax string.

Rules syntax

ON: *.line

IF: PLAYER.chapter=1
    BROADWAY_STREET.leaving_broadway_street_station_plot=1

DO: BRIEFCASE.location=THIEF
    BROADWAY_STREET.leaving_broadway_street_station_plot=2

You can include spaces and newlines as desired. The : after each rule part is optional. You can also leave out the "IF" and/or "DO" parts.

To create a SpecificTrigger rule instead of using an entity matcher as a trigger, use quotes arround the trigger string, like this:

ON: "next-day"

Obviously, this only applies to the "ON:" line (which becomes the trigger).


type alias ExtendFn a =
a -> NarrativeEngine.Core.Rules.Rule {} -> NarrativeEngine.Core.Rules.Rule a

A function for "merging" extra fields into a Rule {}.


type alias ParsedRules a =
Result NarrativeEngine.Syntax.Helpers.ParseErrors (NarrativeEngine.Core.Rules.Rules a)

The result of parsing many rules.


type alias ParsedRule a =
Result String (NarrativeEngine.Core.Rules.Rule a)

The result of parsing a "rule" syntax string.

parseRules : ExtendFn a -> Dict NarrativeEngine.Core.Rules.RuleID ( String, a ) -> ParsedRules a

Parses multiple "rule" syntax strings. The rules are tuples of the "rule" syntax for parsing, and the extra fields for that rule. You also need to provide an "extend function" to "merge" extra fields into the standard rule fields.

In general you should use parseRules at the top level of you application, and display any errors with NarrativeEngine.Syntax.Helpers.parseErrorsView.

parseRule : ExtendFn a -> ( String, a ) -> ParsedRule a

Parses a single "rule" syntax string along with a record of additional fields. The extend function is used to "merge" the additional fields into the standard rule record. (You can use always identity if you don't have any extra fields).