| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- {
- /*global parse, options, offset, line, column, text, SyntaxError*/
- var opts = arguments[2] || options,
- fileName = opts.fileName || "NOFILENAME",
- locations = opts.locations !== undefined ? opts.locations : true,
- comments = opts.comments !== undefined ? opts.comments : false,
- showAst = opts.showAst !== undefined ? opts.showAst : false;
- var err = function err(msg, suffix, opts) {
- opts = opts || {};
- if (typeof msg === "number") msg = "ERR" + msg + ": " + errors[msg];
- if (suffix) msg += suffix;
- return new SyntaxError(msg,
- opts.expected,
- opts.found,
- offset(),
- opts.line || line(),
- opts.col || column()
- );
- };
- var scopify = function scopify(i, q) {
- if (q === null) q = {min:1, max:1};
- i.scope = q;
- return i;
- }
- var minmaxify = function minmaxify(lo, hi) {
- var m = hi ? hi.v || null : lo;
- return {min:lo, max:m};
- }
- var model = function model(statements) {
- var model = {
- systems: {},
- behaviors: {},
- interactions: [],
- triggers: [],
- init: [],
- };
- if (showAst) model.ast = statements;
- // accumulate varying statements into a flatter structure
- for (var s in statements) {
- var statement = statements[s];
- switch(statements[s].type) {
- case "System":
- model.systems[statement.id] = statement;
- break;
- case "Behavior":
- model.behaviors[statement.id] = statement;
- break;
- case "Then":
- case "Join":
- model.interactions.push(statement);
- break;
- case "Trigger":
- model.triggers.push(statement);
- break;
- case "Init":
- model.init.push(statement);
- default: break;
- }
- }
- // add atomic events to behaviors list
- for (var r in ast.references) {
- var ref = ast.references[r];
- if (!model.behaviors[ref])
- model.behaviors[ref] = new ast.Behavior(ref, [], [])
- }
- return warnify(model);
- };
- var warnify = function warnify(model) {
- model.errors = ast.errors;
- for (var i in model.interactions) {
- model.interactions[i].body.forEach(function (sys) {
- if (sys.type === "Selector" && !model.systems[sys.system])
- model.errors.push(err("ERROR: reference to an undefined system (" + sys.system + ")", null, sys.location));
- });
- }
- for (var t in model.triggers) {
- var sys = model.triggers[t].on;
- if (sys.type === "Selector" && !model.systems[sys.system])
- model.errors.push(err("ERROR: reference to an undefined system (" + sys.system + ")", null, sys.location));
- }
- for (var i in model.init) {
- var sys = model.init[i].system;
- if (sys && !model.systems[sys])
- model.errors.push(err("ERROR: reference to an undefined system (" + sys + ")", null, model.init[i].location));
- }
- for (var b in model.behaviors) {
- model.behaviors[b].refs.forEach(function (ref) {
- if (model.systems[ref])
- model.errors.push(err("ERROR: reference to system (" + ref + ") in a behavior (" + model.behaviors[b].id + ")", null, model.behaviors[b].location));
- });
- }
- return model;
- };
- var ast = {
- comments: [],
- errors: [],
- references: [],
- Base: function Base(type) {
- this.type = type;
- if (locations) {
- this.location = {
- pos: offset(),
- line: line(),
- col: column(),
- len: text().length,
- };
- }
- },
- Comment: function Comment(block, text) {
- ast.Base.call(this, "Comment");
- this.block = block;
- this.text = text;
- ast.comments.push(this);
- },
- Id: function Id(type, id) {
- ast.Base.call(this, type);
- this.id = id;
- },
- System: function System(id, body, refs) {
- ast.Id.call(this, "System", id);
- this.body = body;
- this.refs = refs;
- this.properties = {};
- this.scope = { min:1, max:1 };
- },
- Behavior: function Behavior(id, body, refs) {
- ast.Id.call(this, "Behavior", id);
- this.body = body;
- this.refs = refs;
- this.properties = {};
- },
- Interaction: function Interaction(type, body) {
- ast.Base.call(this, type);
- this.body = body;
- },
- Sequence: function Sequence(body) {
- ast.Base.call(this, "Sequence");
- this.body = body;
- this.scope = { min:1, max:1 };
- },
- Alternation: function Alternation(body) {
- ast.Base.call(this, "Alternation");
- this.body = body;
- this.scope = { min:1, max:1 };
- },
- Group: function Group(async, body) {
- ast.Base.call(this, "Group");
- this.async = async;
- this.body = body;
- },
- Selector: function Selector(system, pattern, pre, post) {
- ast.Base.call(this, "Selector");
- this.system = system;
- this.pattern = pattern;
- this.pre = pre || null;
- this.post = post || null;
- },
- Trigger: function Trigger(selector, body) {
- ast.Base.call(this, "Trigger");
- this.on = selector;
- this.do = body;
- },
- Init: function Init(system, body) {
- ast.Base.call(this, "Init");
- this.system = system;
- this.do = body;
- },
- };
- } //jshint ignore:start
- start
- = s:statement*
- { return model(s); }
- statement
- = s:( system_statement
- / behavior_statement
- / interaction_statement
- / trigger
- / init )
- _SEMI_
- { return s; }
- system_statement "System"
- = _SYSTEM_ _COLON_ id:system_id
- !{ ast.toplevelname = id; ast.localrefs = []; }
- _EQ_ body:behavior_pattern
- { return new ast.System(id, body, ast.localrefs); }
- behavior_statement "Behavior"
- = _BEHAVIOR_ _COLON_ id:behavior_id
- !{ ast.toplevelname = id; ast.localrefs = []; }
- _EQ_ body:behavior_pattern
- { return new ast.Behavior(id, body, ast.localrefs); }
- interaction_statement "Interaction"
- = _INTERACTION_ _COLON_ interaction:interaction_body
- { return interaction; }
- interaction_body
- = first:system_item_selector _ type:THEN _
- second:system_item_selector rest:(_ THEN _ a:system_item_selector { return a; })*
- { return new ast.Interaction(type, [first, second].concat(rest)); }
- / first:system_item_selector_sans _ type:JOIN _
- second:system_item_selector_sans rest:(_ JOIN _ a:system_item_selector_sans { return a; })*
- { return new ast.Interaction(type, [first, second].concat(rest)); }
- interaction_type
- = _ t:( THEN / JOIN ) _
- { return t; }
- system_item_selector "Selector"
- = !{ ast.selector = true; }
- _ pre:( "{" c:embedded_code "}" { return c; } )? _
- sys:system_id ":" _ pattern:behavior_pattern
- _ post:( "{" c:embedded_code "}" { return c; } )? _
- { ast.selector = false; return new ast.Selector(sys, pattern, pre, post); }
- system_item_selector_sans "Selector"
- = !{ ast.selector = true; }
- _ sys:system_id ":" _ pattern:behavior_pattern _
- { ast.selector = false; return new ast.Selector(sys, pattern); }
- behavior_pattern "Behavior Pattern"
- = ( alternation / sequence )+
- alternation
- = head:behavior_pattern_item tail:( _PIPE_ item:behavior_pattern_item { return item; } )+
- {
- var body = [head].concat(tail);
- for (var i = 0; i < body.length; i++) {
- if (body[i].scope.max && body[i].scope.min > body[i].scope.max)
- ast.errors.push(err("ERROR: Scope (" + body[i].id + ") lower bound exceeds its upper bound"));
- }
- return new ast.Alternation(body);
- }
- sequence
- = body:behavior_pattern_item+
- {
- for (var i = 0; i < body.length; i++) {
- if (body[i].scope.max && body[i].scope.min > body[i].scope.max)
- ast.errors.push(err("ERROR: Scope (" + body[i].id + ") lower bound exceeds its upper bound"));
- }
- return new ast.Sequence(body);
- }
- behavior_pattern_item
- = _ i:group q:quantifier? _
- { return scopify(i, q); }
- / _ i:event_ref q:quantifier? _
- { if (ast.localrefs.indexOf(i.id) === -1 && !ast.selector) { ast.localrefs.push(i.id); } return scopify(i, q); }
- group "Group"
- = _ async:ASYNC? "(" _ body:behavior_pattern _ ")"
- { return new ast.Group(async, body); }
- quantifier "Quantifier"
- = "+" _ { return {min:1, max:null}; }
- / "*" _ { return {min:0, max:null}; }
- / "?" _ { return {min:0, max:1}; }
- / quantifier_range
- quantifier_range
- = "{" _ lo:INT_GTE0 hi:( u:_COMMA_ v:INT_GT0? { return {u:u, v:v}; } )? _ "}" _
- { return minmaxify(lo, hi); }
- trigger "Trigger"
- = _WHEN_ _COLON_ selector:system_item_selector_sans "{" body:embedded_code "}"
- { return new ast.Trigger(selector, body); }
- init "Init Block"
- = _INIT_ _COLON_ system:system_id? _ "{" body:embedded_code "}"
- { return new ast.Init(system, body); }
- embedded_code
- = [^{}]* ( "{" embedded_code "}" )? [^{}]*
- { return text().trim(); }
- behavior_id = id:ID { return id; }
- system_id = id:ID { return id; }
- event_ref = id:ID
- {
- if (!ast.selector) {
- if (id === ast.toplevelname) {
- ast.errors.push(err("ERROR: Definition is recursive (" + id + ")"));
- ast.toplevelname = null;
- } else ast.references.push(id);
- }
- return { type: "Behavior", id:id };
- }
- KEYWORD = SYSTEM / BEHAVIOR / INTERACTION / ASYNC
- SYSTEM = t:"SYSTEM" { return t; }
- BEHAVIOR = t:"BEHAVIOR" { return t; }
- INTERACTION = t:"INTERACTION" { return t; }
- WHEN = t:"WHEN" { return t; }
- INIT = t:"INIT" { return t; }
- ASYNC = t:"ASYNC" { return t; }
- THEN = "->" { return "Then"; }
- JOIN = "==" { return "Join"; }
- _SYSTEM_ = _ t:SYSTEM _ { return t; }
- _BEHAVIOR_ = _ t:BEHAVIOR _ { return t; }
- _INTERACTION_ = _ t:INTERACTION _ { return t; }
- _WHEN_ = _ t:WHEN _ { return t; }
- _INIT_ = _ t:INIT _ { return t; }
- _EQ_ = _ t:"=" _ { return t; }
- _COMMA_ = _ t:"," _ { return t; }
- _PIPE_ = _ t:"|" _ { return t; }
- _COLON_ = _ t:":" _ { return t; }
- _SEMI_ = _ t:";" _ { return t; }
- ID "Identifier" /*TODO: UNICODE?*/
- = !KEYWORD [A-Za-z] [a-zA-Z0-9_]*
- { return text(); }
- INT "Integer"
- = [0-9]+
- { return parseInt(text()); }
- INT_GT0 "Integer > 0"
- = [1-9] [0-9]*
- { return parseInt(text()); }
- INT_GTE0 "Integer >= 0"
- = "0"
- { return 0; }
- / INT_GT0
- _ "Optional Whitespace"
- = ([ \t\n\r] / COMMENT)*
- { return null; }
- __ "Required Whitespace"
- = [ \t\n\r] _?
- { return null; }
- NL "Newline"
- = [\n]
- { return null; }
- COMMENT
- = c:COMMENT_LINE NL?
- { return c; }
- / c:COMMENT_BLOCK NL?
- { return c; }
- COMMENT_LINE "Comment Line"
- = "//" c:[^\n]*
- { return new ast.Comment(false, c.join("")); }
- COMMENT_BLOCK "Comment Block"
- = "/*" c:(!"*/" c:. {return c})* "*/"
- { return new ast.Comment(true, c.join("")); }
|