Giter Site home page Giter Site logo

tracery's Introduction

Welcome to Tracery!

A text-expansion library

There are many new examples of Tracery in use I also have an exciting new interactive tutorial

I strongly recommend using the minified library

Write grammar objects, get generative stories

An example grammar

{
	"name": ["Arjun","Yuuma","Darcy","Mia","Chiaki","Izzi","Azra","Lina"],
	"animal": ["unicorn","raven","sparrow","scorpion","coyote","eagle","owl","lizard","zebra","duck","kitten"],
	"mood": ["vexed","indignant","impassioned","wistful","astute","courteous"],
	"story": ["#hero# traveled with her pet #heroPet#.  #hero# was never #mood#, for the #heroPet# was always too #mood#."],
	"origin": ["#[hero:#name#][heroPet:#animal#]story#"]
}

Output of that grammar.

Of course, many grammars are more complex!

Lina traveled with her pet duck. Lina was never indignant, for the duck was always too indignant.
Yuuma traveled with her pet unicorn. Yuuma was never wistful, for the unicorn was always too indignant.
Azra traveled with her pet coyote. Azra was never wistful, for the coyote was always too impassioned.
Yuuma traveled with her pet owl. Yuuma was never wistful, for the owl was always too courteous.
Azra traveled with her pet zebra. Azra was never impassioned, for the zebra was always too astute.

How to use Tracery as a broswer library

Import tracery <script defer src="js/libs/tracery.js"></script>

Use the tracery object to create a Grammar object from a source object (specification below) tracery.createGrammar(spellbook);

The grammar can create Trace objects. A Trace is one possible expansion of a grammar. var trace = app.grammar.createTrace();

The trace can be expanded into a tree structure, step by step, or all at once. trace.expand(); Once expanded, the trace can create a 'flattened' version of itself: a single string of text. var myString = trace.flatten();

Or the grammar can generate a trace and flatten it, all in one step var myTitle = app.grammar.createFlattened()

Traces will start their expansions with the 'origin' symbol by default, but you can also create one from a rule (see "Rule Syntax" below), or from a symbol var trace = app.grammar.createTrace("A story about #character#"); var trace = app.grammar.createTraceFromSymbol("bookTitle");

Many traces can be working on a single grammar at the same time, without getting in each others way.

How to use Tracery as a Node.js library

Use this Node library created by George Buckenham: https://github.com/v21/tracery

Input

Syntax overview

Grammar

A grammar is a key-value storage system for rules.

Rule syntax

Each symbol should be followed by an array of text strings representing rules

  "emotion" : ["happy", "sad", "proud"],

or, if you're writing a long string of single words, you can use 'split'

  "emotion" : "happy sad reflective morose proud".split(" "),

Rules can also contain expansion symbols, words surrounded by #'s:

mainCharacter: ["Brittany the Wombat"],
story : ["This is a story about #mainCharacter#"]

Expansion symbols can have modifiers. Modifiers can change something about the string expansion of that symbol. #animal.capitalize# or #animal.a# or #animal.s#

name: ["Brittany"],
animal: ["wombat"],
story : ["This is a story about #name# the #animal.capitalize#"]

tracery's People

Contributors

dariusk avatar erbridge avatar galaxykate avatar johnicholas avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tracery's Issues

Modifiers not working / NPM Package probem

I've cannot get the capitalize and other modifiers to work in node.js.

Running this code:

var tracery = require('tracery-grammar');

var grammar = tracery.createGrammar({
'animal': ['panda','fox','capybara','iguana'],
'emotion': ['sad','happy','angry','jealous'],
'origin':['I am #emotion.a# #animal.capital#.'],
});

console.log(grammar.flatten('#origin#'));

outputs

I am jealous((.a)) capybara((.capital)).

Tracery was installed via npm and don't seem to include any code for modifiers. Looking at the files on here on Github I see there is a separate branch, tracery2, which has a mods-eng-basic.js package.

I tried downloading the tracery2 branch, creating a NPM package for it and installing but it makes no difference. I'm new to node.js, NPM, and Tracery and cannot find a way to load the mods-eng-basic.js / get the modifiers to work.

How do I get the modifiers working in Node.js?

Put tracery up on npm

I've not looked properly at this, but from my previous experience, the problems I had were largely down to code that implicitly depended on globals defined elsewhere (ie, jQuery, or other parts of the library). npm uses, as good practice(?) things like this:
var local = require('dependency');

Previously, I just took a hacksaw to the library and glommed it into a single file, stripped out any external dependencies, and hacked out all the AMD stuff, but this time round it'd be good to keep the two versions a bit more in sync (or even identical)

There's a further stage to this, which is somewhat separate, which is to test for breaking changes in the syntax against the current CBDQ corpus. The only one I can think of off the top of my head is //, but it's worth thinking of a way to look for others.

Feature request(?): change probabilities

Hello,

I checked the documentation and was unable to find it, so this is probably a feature request. Does it make sense to add a feature to mess around with the likelihood of certain tokens? For example, if one wanted to make a faux astrology bot (not to give you any ideas, @dariusk ), and wanted to use some tokens ("Mars", "Venus") more than others?

THANKS!

Shirish

Backslash escaping #s isn't valid JSON

So the escape sequence for including a literal # is \#, right? Unfortunately, in JSON, that can only be used to escape a small range of things. JSON support seems like a valuable thing - it certainly simplifies giving as-you-type feedback in CBDQ. Is there a way we can change this to be JSON compatible?

Variable expansion breaks on commas in variable text.

{

    "origin":"[clippath1:#clippath-path#]<clipPath id=\"overlay1\">#clippath1#</clipPath>",


    "clippath-path":["<path d=\"M 512 0 L 0 0 L 0 #h# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint# #curvepoint#  M 512 0 Z\"></path>"],
    "curvepoint":"q #dist_half# #h_rel_half#, #dist# #h_rel#",

    "dist":["2#digit#","2#digit#","4#digit#","3#digit#","5#digit#","6#digit#","7#digit#","8#digit#"],
    "dist_half":["#digit#","#digit#","#0-5#"],
    "h_rel":["-2#digit#", "1#digit#", "-1#digit#","#digit#", "-#digit#","2#digit#"],
    "h_rel_half":["-#digit#", "#0-5#", "-#0-5#","#digit#"],
    "h" : ["2#digit##digit#","2#digit##digit#","3#digit##digit#","1#digit##digit#"],


    "digit":["0","1","2","3","4","5","6","7","8","9"],
    "0-5":["0","1","2","3","4","5"]
}

should produce something like

<clipPath id="overlay1"><path d="M 512 0 L 0 0 L 0 251 q 2 -4 24 28 q 0 3 23 2 q 1 2 41 -28 q 5 -3 47 11 q 5 6 21 -27 q 1 -0 44 14 q 4 6 43 13 q 4 -1 29 2 q 4 -3 27 19 q 3 0 24 17 q 5 2 27 15 q 5 -7 63 -14 q 0 -3 26 1 q 0 -2 76 -5 q 6 -4 28 9 q 2 4 76 -21 q 2 7 23 8 q 0 2 20 -27 q 4 -2 86 -6 q 5 -5 30 -4 q 3 -1 75 -16 q 1 -3 27 5 q 8 -3 22 -19 q 1 -2 80 4 q 2 6 35 -1 q 9 3 73 29 q 6 -3 56 26 q 1 4 47 25 q 5 0 36 20 q 4 8 31 4 q 1 0 73 9 q 1 -1 70 -3 q 0 -2 56 10 q 8 4 20 -13 q 1 9 20 -14 q 0 -5 23 3 q 5 -1 45 -11 q 3 -4 54 -16 M 512 0 Z"></path></clipPath>

but instead it produces something like

<clipPath id="overlay1"> 62 28 q 3 -1</clipPath>

Changing "curvepoint":"q #dist_half# #h_rel_half#, #dist# #h_rel#", to "curvepoint":"q #dist_half# #h_rel_half# #dist# #h_rel#", solves the problem.

"a" modifier doesn't account for cases like "useful"

In this grammar:

{
	"start": "#noun_phrase.a#",
	"noun_phrase": "useful tool like Tracery"
}

The expected output from start would be "a useful tool like Tracery", but instead we get "an useful tool like Tracery". I haven't tried Tracery 2, but looking at its altered a function I think the problem would remain (since the third letter isn't i).

The most reliable function I've found to do this job is in inflect.py, although its regex usage isn't especially readable.

how to build?

What's the best way to buildtracery.js from source? I noticed that the tracery.js is missing some functionality that tracery.min.js has (eg: grammar.toText()), so I wanted to rebuild it.

License?

I just discovered your library, and I love the idea of it, but I don't see a license mentioned anywhere in the documentation or code. Unfortunately I really can't (legally) do anything with it unless you explicitly license it! Please take a moment to choose a license and add it to the repository in a LICENSE file. I (and I'm sure many others) would greatly appreciate it. Thanks in advance.

"super advanced" Actions behave differently from static ones.

When I pass a 'correlated' bundle of information (such as "bracetypes" below) to a symbol, and then make recursive calls to that symbol, then the information gets overwritten unless I protect it with a second symbol ("brace2" below) which renames the relevant symbol. The more basic actions (like symbol:#letter# below) don't care.

{
     "letter": ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P"],
     "bracetypes": ["[open:(][close:)]","[open:{][close:}]","[open:<][close:>]","[open:«][close:»]","[open:𛰫][close:𛰬]","[open:⌜][close:⌝]","[open:ᙅ][close:ᙂ]","[open:ᙦ][close:ᙣ]","[open:⁅][close:⁆]","[open:⌈][close:⌉]","[open:⌊][close:⌋]","[open:⟦][close:⟧]","[open:⦃][close:⦄]","[open:⦗][close:⦘]","[open:⫷][close:⫸]"],
     "brace": ["#open2##symbol# #[symbol:#letter#][#bracetypes#]brace2##symbol##close2# #[symbol:#letter#][#bracetypes#]brace2#", "","#open2##symbol# #[symbol:#letter#][#bracetypes#]brace2##symbol##close2# "],
     "brace2": ["#[open2:#open#][close2:#close#]brace#"],
     "origin": ["#[symbol:#letter#][#bracetypes#]brace2#"]
}

This gives output with matched braces and matched brace labels, such as the following:

«G ⫷B ⌊N ⁅M ⌈C C⌉ ⫷K ᙦA ᙅE ⌜B ᙦB Bᙣ B⌝ ᙦJ 𛰫K 𛰫H H𛰬 ⦃P ᙅD ⌈M ⌈B B⌉ M⌉ Dᙂ ⁅H H⁆ P⦄ K𛰬 Jᙣ Eᙂ ᙅL (J J) ⫷E E⫸ Lᙂ Aᙣ ⁅M ⌈M (K K) M⌉ M⁆ K⫸ M⁆ N⌋ B⫸ «H ᙅJ Jᙂ H» G»

Technically I don't need the symbol/action "open2" since I only use the open braces before recursive calls. But if I rename all occurrances of "close2" to "close", I get this sort of output:

<A «O O> «L <O ᙅH H> O{{close}} L{{close}} <P «K K> P{{close}} A{{close}}

It's as if nested calls "borrow" the value of their parent, and then the variable is no longer defined after the call! Yet, note that the capital letters match up perfectly; they have no such issue.

Of course, how I initially wrote the code was with no protective symbol "brace2", like this:

{
     "letter": ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P"],
     "bracetypes": ["[open:(][close:)]","[open:{][close:}]","[open:<][close:>]","[open:«][close:»]","[open:𛰫][close:𛰬]","[open:⌜][close:⌝]","[open:ᙅ][close:ᙂ]","[open:ᙦ][close:ᙣ]","[open:⁅][close:⁆]","[open:⌈][close:⌉]","[open:⌊][close:⌋]","[open:⟦][close:⟧]","[open:⦃][close:⦄]","[open:⦗][close:⦘]","[open:⫷][close:⫸]"],
     "brace": ["#open##symbol# #[symbol:#letter#][#bracetypes#]brace##symbol##close# #[symbol:#letter#][#bracetypes#]brace#", "","#open##symbol# #[symbol:#letter#][#bracetypes#]brace##symbol##close# "],
     "origin": ["#[symbol:#letter#][#bracetypes#]brace#"]
}

which gives output like this:

{L ⟦F ᙦL ⁅C ⌈O ⁅N N⌉ (H H⌉ ⦃D D⌉ O{{close}} C{{close}} ⫷P P{{close}} L{{close}} 𛰫F ᙅF ⦗I Iᙂ F{{close}} F{{close}} F{{close}} L{{close}}

Again, the letters match up fine, yet the different braces and brackets don't.

Another interesting example is this piece of code:

{
     "origin": ["#[#xvals#]lambda#Hello!#x#","#[[x:seven, eight]]lambda#Hello!#x#", "#[x:one, two]lambda#Hello!#x#","#[x:#test#]lambda#Hello!#x#"],
     "test":["three","four"],
     "lambda":["#x#"],
     "x":["null"],
     "xvals":["[x:five, six]"]
}

The first and second rule for symbol "origin" clobbers the value of "x" so that after it's called, the variable no longer even exists. So, using the Tracery tutorial, I can get run histories like the following:

twoHello!null
threeHello!null
fourHello!null
threeHello!null
oneHello!null
oneHello!null
sixHello!{{x}}

Or like the following:

threeHello!null
threeHello!null
fiveHello!{{x}}
fiveHello!{{x}}
fourHello!{{x}}
twoHello!{{x}}
fourHello!{{x}}

Or:

twoHello!null
fourHello!null
eightHello!{{x}}
sixHello!{{x}}
threeHello!{{x}}
sixHello!{{x}}
oneHello!{{x}}

So "x" actually disappeared from the grammar! Now, I wouldn't mind being able to use actions to change the value of "x" over time. But having it just disappear isn't very useful, and I don't understand why the first two rules are different from the other two (using bare lambda or using lambda with "test").

Capitalize the first alphabet character

If the user wishes to make a word a hashtag, the capitalize modifier should capitalise the first letter. Right now, it sees the first character is a # and thus does nothing.

Example case:

"system":["\\#communism", "\\#socialism", "universal basic income (\\#UBI)"],
"origin": ["#system.capitalize# is better than feudalism!"]

Obviously there's a workaround for this, but it's slightly messy.

exploding grammars

I don't really understand the expansion process, but the probabilities in nested/recurrent grammars seem off. I was trying to build a "Sims patch notes" grammar, and I get this kind of output too frequently:

Fixed an issue in the Gallery by preventing the telescope in the woman with the woman in the telescope in the game with the man in the woman in the woman in the game with the man in the game with the telescope with the telescope with the man with the game with the dog with the telescope with the dog with the man with the dog in the dog in the dog in the woman with the woman in the dog in the woman in the dog in the telescope in the woman with the man with the woman in the telescope in the woman with the woman with the dog with the game with the dog with the man with the game with the telescope in the game in the man in the man in the dog in the game in the game with the dog with the dog with the game in the woman with the woman in the game in the telescope in the dog with the game in the dog with the woman in the woman with the game with the telescope in the woman with the game with the telescope in the woman in the game in the game in the game in the man in the woman with the telescope with the telescope with the dog with the dog in the game with the game with the dog with the telescope with the dog with the game in the man in the dog in the man with the woman in the dog in the dog with the woman with the woman in the telescope with the telescope in the woman in the man with the woman with the woman with the woman in the game with the dog with the dog with the man in the dog with the telescope with the game in the woman with the woman with the game with the woman with the telescope in the dog from thinking the man in the man with the game in the woman were the dog .

The problem comes from the np nonterminal in this grammar:

grammar = {
in: "with in".split(" "),
nn: "game man woman telescope dog".split(" "),
vt: "saw".split(" "),
vi: "sleeps".split(" "),
np: [
"the #nn#",
"#np# #pp#"
],
vp: ["#vi#", "#vt# #np#", "#vp# #pp#"],
s: ["#np# #vp#"],
pp: ["#in# #np#"],
}

Note that the pp is guaranteed to introduce another np. A different grammar shows the exploding possibility even more sharply:

grammar = {
nn: "game man woman telescope dog".split(" "),
np: [
"#nn#",
"#np# #np#",
"#np# #np# #np#"
],
};

Could we address this somehow? Maybe just a "when to start stopping" parameter that adjusts the probability of choosing productions based on recursion depth or the size of the commitments that we already have, so that eventually the process would be guaranteed to finish up?

I think Phillipe Flajolet has done something with random generation of combinatorial structures that might be more subtle and sophisticated, such as: http://lara.epfl.ch/w/_media/calculus-random.pdf

The grammar in readme.md throws errors.

There are two major problems with the code as written:

First, there is no "animal" symbol in the grammar.
Second, tracery errors out if one leaves a symbol unpopped at the end of its expansion.

(ie, instead of the current Origin symbol:

"origin" : ["[mainCharacter:#name# the #animal.capitalize#][destination:#place#]#story#[mainCharacter:pop]"

we need to have:

"origin" : ["[mainCharacter:#name# the #animal.capitalize#][destination:#place#]#story#[mainCharacter:pop][destination:pop]"

The other solution would be allow the current format to exist without erroring out - or a way of just saying "pop all of the things that have been pushed")

Links on your website died

Not an issue with Tracery directly, but I just wanted to let you know the Tracery links on galaxykate.com seem to be dead. Under the Tracery header there are three links pointing to pages on brightspiral.com

Write grammars online
Try an example Victorian Novel Generator
Learn more

Part of the problem seems to be the https link: if I visit https://brightspiral.com (no www) I get an certificate error, and if I ignore that I get to a 404. Visiting http://brightspiral.com (no https, no www) does work, but the other links still don't.

.ing does not work

In the documentation it mentions that you can #word.ing# but that appears to not be in the source code.

Tracery2 has breaking changes with setting variables

Specifically, setting variables within a node is no longer supported, as per

{ "name": ["Cheri","Fox","Morgana","Jedoo","Brick","Shadow","Krox","Urga","Zelph"] , "story": ["#hero.capitalize# was a great #occupation#, and this song tells of #heroTheir# adventure. #hero.capitalize# #didStuff#, then #heroThey# #didStuff#, then #heroThey# went home to read a book."] , "monster": ["dragon","ogre","witch","wizard","goblin","golem","giant","sphinx","warlord"] , "setPronouns": ["[heroThey:they][heroThem:them][heroTheir:their][heroTheirs:theirs]","[heroThey:she][heroThem:her][heroTheir:her][heroTheirs:hers]","[heroThey:he][heroThem:him][heroTheir:his][heroTheirs:his]"] , "setOccupation": ["[occupation:baker][didStuff:baked bread,decorated cupcakes,folded dough,made croissants,iced a cake]","[occupation:warrior][didStuff:fought #monster.a#,saved a village from #monster.a#,battled #monster.a#,defeated #monster.a#]"] , "origin": ["#[#setPronouns#][#setOccupation#][hero:#name#]story#"] }

This will produce results like
Cheri was a great ((occupation)), and this song tells of ((heroTheir)) adventure. Cheri ((didStuff)), then ((heroThey)) ((didStuff)), then ((heroThey)) went home to read a book.

Substitutions behave strangely inside bindings

My CBDQ bot started acting up a few months ago. Suddenly #[pMysteriousPower:#pMysteriousPower#]treasureOfPower# started generating the text "pMysteriousPower" instead of following the treasureOfPower rules.

To see what was going on, I tried #[a:FOO#[b:c]d#BAR]a# as a more elaborate example. It generated the text "FOOb".

Here's some test code:

console.log(tracery.createGrammar({
    origin: [
        "#[a:FOO#[b:c]d#BAR]a#"
    ]
}).flatten("#origin#"));

console.log(tracery.createGrammar({
    "origin": [
        "#story#"
    ],

    "story": [
        "#[pMysteriousPower:#mysteriousPower#]storyOfParams1#"
    ],
    "storyOfParams1": [
        "#[pMysteriousTechnoTreasure:#[pMysteriousPower:#pMysteriousPower#]treasureOfPower#]storyOfParams2#"
    ],
    "storyOfParams2": [
        "When you need #pMysteriousPower#, look no further than the #pMysteriousTechnoTreasure#!"
    ],

    "mysteriousTechnoTreasure": [
        "#[pMysteriousPower:#mysteriousPower#]treasureOfPower#"
    ],
    "treasureOfPower": [
        "#treasureType# of #pMysteriousPower#"
    ],
    "treasureType": [
        "amulet",
        "talisman"
    ],
    "mysteriousPower": [
        "teleportation",
        "frost"
    ]
}).flatten("#origin#"));

On the main branch, these result in the text "{{a}}" and (for example) "When you need frost, look no further than the amulet of frost!" respectively. Those look like good results to me.

On the tracery2 branch, I get the text "FOOb" and (for example) "When you need frost, look no further than pMysteriousPower!"

Here's an .html file demonstrating the above example code on the tracery2 branch. It doesn't display anything, but it logs to the console.

Plural for specific cases?

I'm not sure how exactly you would do this, but words like "wolf", "elf", "dwarf", "knife", and "ninja" (to name a few) are not given the proper plural form with .s:
"wolfs", "elfs", "dwarfs", "knifes", "ninjas"

capitalizeAll doesn't support Unicode properly

via Avery Katko on Twitter:

as of a couple days ago cheap bots done quick seems to be treating non-ascii letters (at least a bunch of accented latin vowels that I've used like ë and ú) as word delimiters for capitalizeAll
also capitalizes after apostrophes and hyphens

Some idea on how to implement the parameters for modifiers

Hi, I've fiddled with a piece of code which is similar to yours in terms of functionalities.

I've distinguished allocation from interpolation:

Allocation: [[ key = {{ value | modifier : parameter }} ]]
Interpolation: {{ value | modifier : parameter }}

As you can see the allocation can also contains an interpolation, but it's not necessary when you have to pop, to do that:
[[ key = POP ]]

With this syntax is possible to chain modifier with their parameter:
{{ value | modifier1 | modifier2 : param1 | modifier3 : param1 : param2 }}
In the example value is fed to modifier1 which has no params, the result is then fed into modifier2 which has a single param and all is ultimately fed into modifier3 which has a couple of params.

class Grammar {
    constructor() {
        
        this.rules = {};
        this.modifiers = {};
        this.variables = {};

        this.randomFn = Math.random;
    }

    // rule: "key -> value1 | value2 | value3"
    setRule(rule) {
        let validationRegex = /^ *[^->]+ * -> *[^->|]+( *\| *[^->| ]+[^->|]*)* *$/;
        if (!validationRegex.test(rule)) {
            console.error("Something wrong in your rule");
            return;
        }
        let [key, values] = rule.split(/ *-> */);
        values = values.split(/ *\| */);
        this.rules[key] = values;
    }

    setModifier(name, fn) {
        this.modifiers[name] = fn;
    }

    pickRandom(list) {
        return list[Math.floor(this.randomFn() * list.length)];
    }

    evaluate(expr) {
        let [value, ...modifiers] = expr.split(/ *\| */);
        if (this.variables[expr]) {
            return this.variables[expr][this.variables[expr].length - 1];
        }
        if (!this.rules[value]) {
           console.error("No rule found with the name: " + value);
           return;
        }
        value = this.pickRandom(this.rules[value]);
        if (!modifiers.length) {
            return value;
        }
        for (let modifier of modifiers) {
            let [modifierName, ...params] = modifier.split(/ *: */);
            if (!this.modifiers[modifierName]) {
                console.error("No modifier found with the name: " + modifierName);
                return;
            }
            value = this.modifiers[modifierName](value, ...params)
        }
        return value;
    }

    expand(text) {

        console.log(text); // Console each step
        
        let interpolation = " *{{ *([^|]+?( *\\| *([^: ]+( *: *[^: ]+)*) *?)*) *}} *";
        let allocation = ` *\\[\\[ *([^{}[\\]]+?) *=(${interpolation}| *POP *)\\]\\] *`;
        let interpolationRegex = new RegExp(`^${interpolation}$`);
        let allocationRegex = new RegExp(`^${allocation}$`);

        if (!(new RegExp(interpolation)).test(text) && !(new RegExp(allocation)).test(text)) {
            return text;
        }

        let result = text.replace(/(\[\[(.*?)\]\]|{{(.*?)}})/g, text => {
            
            if (allocationRegex.test(text)) {
                let [_, key, pop, expr] = text.match(allocationRegex);

                if (pop.trim() === "POP") {
                    if (!this.variables[key]) {
                        console.error("Nothing to pop at: " + key);
                        return;
                    }
                    this.variables[key].pop();
                    if (!this.variables[key].length) {
                        delete this.variables[key];
                    }
                    return "";
                }

                let value = this.evaluate(expr);

                if (!this.variables[key]) {
                    this.variables[key] = [];
                }
                this.variables[key].push(value);

                return value;
            }

            if (interpolationRegex.test(text)) {
                let expr = text.match(interpolationRegex)[1];
                return this.evaluate(expr);
            }

            console.error("Something wrong in your text");

        });

        this.expand(result);
    }
}

var g = new Grammar();

g.setRule("adj -> dark | stormy | beautiful");
g.setRule("noun -> {{adj}} night");

g.setModifier("discapitalize", (text, howMany) => {
    howMany = Number(howMany);
    let result = "";
    for (let i = 0; i < text.length; i++) {
        if (i < howMany) {
            result += text[i].toLowerCase();
        } else {
            result += text[i];
        }
    }
    return result;
});
g.setModifier("capitalize", (text, howMany) => {
    howMany = Number(howMany);
    let result = "";
    for (let i = 0; i < text.length; i++) {
        if (i < howMany) {
            result += text[i].toUpperCase();
        } else {
            result += text[i];
        }
    }
    return result;
});

g.expand("It was a [[ adjective = {{ adj | capitalize : 3 | discapitalize : 2 }} ]] and {{ adjective }} {{ noun }}");

// The above expansion will result in the following rounds:
// Step 0: It was a [[ adjective = {{ adj | capitalize : 3 | discapitalize : 2 }} ]] and {{ adjective }} {{ noun }}
// Step 1: It was a beAutiful and beAutiful {{adj}} night
// Step 2: It was a beAutiful and beAutiful dark night

Process modifiers before deactivating actions?

I think it would make more sense to process modifier functions while the actions (on that same symbol/keyword) are still active, and so the symbols introduced in the actions are still "in scope", so to speak.

Granted, I am sort of using this system for things it was never intended for. I wrote some custom modifiers that produce side effects in other data structures, and I'm trying to sneak more information to these custom functions via actions.

Actions overriding existing rules

From the syntax it looks like the actions are specific to the "rule call" and not globally set, but running this grammar in the online editor it looks to global.

{
"origin":"I love #[animal:#cat#]say# and #say#",
"say":"#animal.s#",
"animal": "dog",
"cat":"cat"
}

In the online editor this outputs: "I love cats and cats", and not what I would expect (I love cats and dogs).

To get the result that I want, the documentation suggests I need to use a POP before the second call, like this:

"origin":"I love #[animal:#cat#]say# and #[animal:POP]say#",

But this doesn't change anything, it still outputs "I love cats and cats".

What is the correct behavior here?

Java port

Hi!
Is there a Java port for this? If not I'd be interested in porting it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.