Browse Source

initial setup of new model-lang repo

Kyle P Davis 10 years ago
parent
commit
6d4d7be14e
8 changed files with 1151 additions and 0 deletions
  1. 45 0
      .jscsrc
  2. 1 0
      .jshintignore
  3. 32 0
      .jshintrc
  4. 125 0
      FEATURES.md
  5. 139 0
      src/lib/generator/modellang.es6
  6. 197 0
      src/lib/parser/modellang.pegjs
  7. 604 0
      src/lib/parser/mp2.pegjs
  8. 8 0
      src/www/.jshintrc

+ 45 - 0
.jscsrc

@@ -0,0 +1,45 @@
+{
+  "disallowImplicitTypeConversion": ["numeric", "boolean", "binary", "string"],
+  "disallowKeywords": ["with"],
+  "disallowKeywordsOnNewLine": ["else", "catch", "finally"],
+  "disallowMixedSpacesAndTabs": true,
+  "disallowNewlineBeforeBlockStatements": true,
+  "disallowQuotedKeysInObjects": true,
+  "disallowSpaceAfterObjectKeys": true,
+  "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
+  "disallowSpaceBeforeBinaryOperators": [","],
+  "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
+  "disallowSpacesInFunctionExpression": {
+  	"beforeOpeningRoundBrace": true
+  },
+  "disallowSpacesInsideArrayBrackets": true,
+  "disallowSpacesInsideObjectBrackets": true,
+  "disallowSpacesInsideParentheses": true,
+  "disallowTrailingWhitespace": true,
+  "disallowYodaConditions": true,
+  "requireBlocksOnNewline": true,
+  "requireCurlyBraces": ["for", "while", "do", "try", "catch"],
+  "requireLineFeedAtFileEnd": true,
+  "requireSpaceAfterBinaryOperators": [",", "+", "-", "/", "*", "=", "==", "===", "!=", "!=="],
+  "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
+  "requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="],
+  "requireSpacesInConditionalExpression": {
+  	"afterTest": true,
+  	"beforeConsequent": true,
+  	"afterConsequent": true,
+  	"beforeAlternate": true
+  },
+  "requireSpacesInFunctionExpression": {
+  	"beforeOpeningCurlyBrace": true
+  },
+  "requireTrailingComma": {
+  	"ignoreSingleLine": true,
+  	"ignoreSingleValue": true
+  },
+  "safeContextKeyword": ["self"],
+  "validateJSDoc": {
+	"checkParamNames": true,
+	"checkRedundantParams": true,
+	"requireParamTypes": true
+  }
+}

+ 1 - 0
.jshintignore

@@ -0,0 +1 @@
+node_modules/

+ 32 - 0
.jshintrc

@@ -0,0 +1,32 @@
+{
+
+	"camelcase": true,
+	"curly": false,
+	"eqeqeq": true,
+	"forin": true,
+	"immed": true,
+	"latedef": true,
+	"newcap": true,
+	"noarg": true,
+	"noempty": true,
+	"nonbsp": true,
+	"nonew": true,
+	"quotmark": true,
+	"undef": true,
+	"unused": "vars",
+	"strict": false,
+	"trailing": true,
+	"maxparams": 11,
+	"maxdepth": 7,
+	"maxstatements": 42,
+	"maxcomplexity": 13,
+	"maxlen": 140,
+
+	"boss": true,
+	"expr": true,
+	"globalstrict": true,
+
+	"node": true,
+	"devel": false
+
+}

+ 125 - 0
FEATURES.md

@@ -0,0 +1,125 @@
+# FEATURES
+
+A master list of the features for our new modeling language:
+
+- [ ] documentation
+	- [ ] overview
+		- [ ] who
+		- [ ] what
+		- [ ] why
+		- [ ] easy example
+	- [ ] the language
+		- [ ] spaces and comments
+		- [ ] patterns
+			- [ ] sequences
+			- [ ] alternations
+			- [ ] groups
+			- [ ] quantifiers
+		- [ ] behaviors
+			- [ ] system
+			- [ ] behavior
+		- [ ] interactions
+			- [ ] system item selectors
+				- [ ] `sys1`
+				- [ ] `sys1:pattern`
+			- [ ] `sys1:a -> sys2:b` (ordering)
+				- [ ] `sys1:a -> sys2:b -> sys3:b` (chained ordering)
+			- [ ] `sys1:a == sys2:b` (joining)
+			- [ ] `expr -> sys1:a` (conditionals)
+		- [ ] triggers
+			- [ ] `WHEN pattern { ... }`
+			- [ ] `WHEN expr { ... }`
+		- [ ] expressions and code blocks
+			- [ ] properties
+				- [ ] global scope
+				- [ ] pattern scope
+				- [ ] local scope
+			- [ ] predefined variables
+				- [ ] `global`
+				- [ ] `this`
+				- [ ] `parent`
+				- [ ] `previous`
+	- [ ] parser API
+	- [ ] generator API
+- [ ] code parser
+	- [ ] spaces and comments
+	- [ ] error handling
+	- [ ] patterns
+		- [ ] sequences
+		- [ ] alternations
+		- [ ] groups
+		- [ ] quantifiers
+	- [ ] behaviors
+		- [ ] system
+		- [ ] behavior
+	- [ ] interactions
+		- [ ] system item selectors
+			- [ ] `sys1`
+			- [ ] `sys1:pattern`
+		- [ ] `sys1:a -> sys2:b` (ordering)
+			- [ ] `sys1:a -> sys2:b -> sys3:b` (chained ordering)
+		- [ ] `sys1:a == sys2:b` (joining)
+		- [ ] `expr -> sys1:a` (conditionals)
+	- [ ] triggers
+		- [ ] `WHEN pattern { ... }`
+		- [ ] `WHEN expr { ... }`
+	- [ ] expressions and code blocks
+		- [ ] properties
+			- [ ] global scope
+			- [ ] pattern scope
+			- [ ] local scope
+		- [ ] special functions
+			- [ ] `COUNT(pattern)`
+		- [ ] special variables
+			- [ ] `global`
+			- [ ] `this`
+			- [ ] `parent`
+			- [ ] `previous`
+	- [ ] includes
+- [ ] event trace generator
+	- [ ] patterns
+		- [ ] sequences
+		- [ ] alternations
+		- [ ] groups
+		- [ ] quantifiers
+	- [ ] behaviors
+		- [ ] system
+		- [ ] behavior
+	- [ ] interactions
+		- [ ] system item selectors
+			- [ ] `sys1`
+			- [ ] `sys1:pattern`
+		- [ ] `sys1:a -> sys2:b` (ordering)
+			- [ ] `sys1:a -> sys2:b -> sys3:b` (chained ordering)
+		- [ ] `sys1:a == sys2:b` (joining)
+		- [ ] `expr -> sys1:a` (conditionals)
+	- [ ] triggers
+		- [ ] `WHEN pattern { ... }`
+		- [ ] `WHEN expr { ... }`
+	- [ ] expressions and code blocks
+		- [ ] properties
+			- [ ] global scope
+			- [ ] pattern scope
+			- [ ] local scope
+		- [ ] special functions
+			- [ ] `COUNT(pattern)`
+		- [ ] special variables
+			- [ ] `global`
+			- [ ] `this`
+			- [ ] `parent`
+			- [ ] `previous`
+- [ ] code editor
+	- [ ] syntax highlighting
+	- [ ] interactive error reporting from parser
+	- [ ] completion
+		- [ ] basic completion
+		- [ ] docs in completion
+		- [ ] context-based completion
+- [ ] trace visualizer
+- [ ] visual code editor
+- [ ] configurable layout
+- [ ] files
+	- [ ] new (tabs / splits using configurable layout)
+	- [ ] open examples?
+	- [ ] close
+	- [ ] save (localStorage? )

+ 139 - 0
src/lib/generator/modellang.es6

@@ -0,0 +1,139 @@
+#!/usr/bin/env babel-node --stage 0
+/*jshint esnext:true*/
+"use strict";
+//TODO: should be a lib not a test / binary
+
+
+let typeEnum = { //TODO: use Symbol for these?
+  SYSTEM: Symbol("SYSTEM"),
+  BEHAVIOR: Symbol("BEHAVIOR"),
+  ALTERNATION: Symbol("ALTERNATION"),
+};
+
+let behaviors = {
+	driving_a_car: {type:"BEHAVIOR", id:"driving_a_car", body:[
+			{type:"BEHAVIOR", id:"go_straight"},
+			{type:"ALTERNATION", id:"driving_a_car_alt1", scope:{min:0,max:1}, body:[
+				{type:"BEHAVIOR", id:"go_straight"},
+				{type:"BEHAVIOR", id:"turn_left"},
+				{type:"BEHAVIOR", id:"turn_right"},
+			]},
+			{type:"BEHAVIOR", id:"stop"},
+	]},
+	go_straight: {type:"BEHAVIOR", id:"go_straight", body:[
+			{type:"ALTERNATION", id:"go_straight_alt1", scope:{min:1,max:1}, body:[
+				{type:"BEHAVIOR", id:"accelerate"},
+				{type:"BEHAVIOR", id:"decelerate"},
+				{type:"BEHAVIOR", id:"cruise"},
+			]},
+	]},
+	turn_left: {type:"BEHAVIOR", id:"turn_left", body:[]},
+	turn_right: {type:"BEHAVIOR", id:"turn_right", body:[]},
+	accelerate: {type:"BEHAVIOR", id:"accelerate", body:[]},
+	decelerate: {type:"BEHAVIOR", id:"decelerate", body:[]},
+	cruise: {type:"BEHAVIOR", id:"cruise", body:[]},
+	stop: {type:"BEHAVIOR", id:"stop", body:[]},
+
+	foobarbaz: {type:"BEHAVIOR", id:"foobarbaz", body:[
+	  {type:"BEHAVIOR", id:"foo", scope:{max:1}},
+	  {type:"BEHAVIOR", id:"bar", scope:{max:1}},
+	  {type:"BEHAVIOR", id:"baz", scope:{max:1}},
+  ]},
+	foo: {type:"BEHAVIOR", id:"foo", body:[]},
+	bar: {type:"BEHAVIOR", id:"bar", body:[]},
+	baz: {type:"BEHAVIOR", id:"baz", body:[]},
+};
+
+let systems = {
+	car_race: {
+		type: "SYSTEM",
+		id: "car_race",
+		body: [
+		// 	{type:"BEHAVIOR", id:"driving_a_car", scope:{min:1, max:null}},
+		// 	{type:"BEHAVIOR", id:"go_straight", scope:{min:1, max:null}},
+			{type:"BEHAVIOR", id:"foobarbaz", scope:{min:1, max:null}},
+		],
+	},
+};
+
+
+
+//SYSTEM: car_race = foobarbaz+;
+//BEHAVIOR: foobarbaz = foo bar baz;
+
+//car_race foobarbaz foo bar baz
+//car_race foobarbaz foo bar baz foobarbaz foo bar baz
+//car_race foobarbaz foo bar baz foobarbaz foo bar baz foobarbaz foo bar baz
+
+
+
+
+const SCOPE_MAX = 3;
+
+function *getEventGenerator(obj, min, max, scope=SCOPE_MAX, i=0) {
+
+  let nextMin = obj.scope && typeof obj.scope.min === "number" ? Math.max(0, obj.scope.min) : 1;
+  let nextMax = obj.scope && typeof obj.scope.max === "number" ? Math.min(scope, obj.scope.max) : scope;
+
+  switch (obj.type) {
+
+    case "BEHAVIOR":
+      if (!obj.body) {
+        let realObj = behaviors[obj.id];
+        if (!realObj || !realObj.body) throw new Error(`Unable to find: ${obj.id}`);
+        obj = realObj;
+      }
+
+    case "SYSTEM":
+// console.log(i, obj);
+      let id = `${obj.id}_${max}`;
+      if (obj.body.length > 0) {
+        if (i < obj.body.length) {
+          yield* (
+            for (nextEvt of getEventGenerator(obj, min, max, scope, i + 1))
+              for (evt of getEventGenerator(obj.body[i], nextMin, nextMax, scope))
+                i === 0 ? [].concat(id, evt, nextEvt) : [].concat(evt, nextEvt)
+          );
+        } else {
+          yield [];
+          if (min < max) {
+            yield* getEventGenerator(obj, min + 1, max, scope, 0);
+          }
+        }
+      } else {
+        yield [id];
+      }
+      break;
+
+    case "ALTERNATION":
+      let id = `${obj.id}_${max}`;
+      if (obj.body.length > 0) {
+        for (let bodyObj of obj.body) {
+          for (let evt of getEventGenerator(bodyObj, nextMin, nextMax, scope)) {
+            if (max > 1) {
+              for (let evt2 of getEventGenerator(obj, min, max - 1)) {
+                yield [].concat(evt, evt2);
+              }
+            } else {
+              yield evt;
+            }
+          }
+        }
+      } else {
+        yield id;
+      }
+      break;
+
+    default:
+      throw new Error(`UNIMPLEMENTED TYPE: ${obj.type}`);
+
+  }
+}
+
+for (let systemId of Object.keys(systems)) {
+	console.log(`START: ${systemId}`);
+	for (let evt of getEventGenerator(systems[systemId], 1, 1)) {
+		// console.log(`GOT:`, evt.map(x => JSON.stringify(x)).join("\n"))
+		console.log(`GOT:`, evt)
+	}
+}

+ 197 - 0
src/lib/parser/modellang.pegjs

@@ -0,0 +1,197 @@
+{
+  /*global parse, options, offset, line, column, text, SyntaxError*/
+  var opts = arguments[2] || options,
+    fileName = opts.fileName || "NOFILENAME",
+    locations = opts.locations !== undefined ? opts.locations : false,
+    comments = opts.comments !== undefined ? opts.comments : false;
+
+  var err = function err(msg, suffix) {
+      if (typeof msg === "number") msg = "ERR" + msg + ": " + errors[msg];
+      if (suffix) msg += suffix;
+      return new SyntaxError(msg, null, null, offset(), line(), column());
+  };
+
+  var ast = {
+
+    errors: [], // tracks all of the non-grammatical errors
+
+    Base: function Base(type) {
+      this.type = type;
+      if (locations) {
+        this.location = {
+          pos: offset(),
+          line: line(),
+          col: column(),
+          len: text().length,
+        };
+      }
+      if (comments && type !== "Comment") {
+        //TODO: not attached in right place
+         this.comments = ast._comments;
+         ast._comments = [];
+      }
+    },
+
+    _comments: [],
+    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;
+    },
+
+  };
+
+} //jshint ignore:start
+
+
+
+start
+  =
+  !{ ast.errors = []; }
+  s:statement*
+  {
+  	return {
+  		errors: ast.errors,
+  		statements: s,
+		}
+  }
+
+statement
+  =
+  (
+  	system_statement
+  / behavior_statement
+  / interaction_statement
+  )
+  _SEMI_
+
+system_statement
+  = _SYSTEM_ _COLON_ id:system_id_init _EQ_ p:behavior_pattern
+
+behavior_statement
+  = _BEHAVIOR_ _COLON_ id:behavior_id_init _EQ_ p:behavior_pattern
+
+interaction_statement
+  = _INTERACTION_ _COLON_ system_item_selector
+  t:interaction_type !{ast.t=t} head:system_item_selector
+  tail:(
+  	t2:interaction_type
+  	  !{
+  	  	if (t2 != ast.t) ast.errors.push(err("Incompatible Interaction Types in same Interaction Statement"));
+  	  }
+  	system_item_selector
+  )*
+  {return [head].concat(tail)}
+
+interaction_type
+  = _ t:( "->" / "<-" / "==" ) _
+    {return t}
+
+system_item_selector
+  = _ system_id_ref (":" _ ("*" / behavior_pattern) )? _
+
+behavior_pattern
+  = alternation
+  / sequence
+
+alternation
+  = head:sequence tail:( _PIPE_ item:sequence {return item} )*
+    {return [head].concat(tail)}
+
+sequence
+  = behavior_pattern_item+
+
+behavior_pattern_item
+  = _ i:( group / behavior_id_ref / event_id_init ) q:quantifier? _
+    {return [i, q]}
+
+group
+  = _ ASYNC? "(" _ behavior_pattern _ ")"
+
+quantifier
+  = "+" _ {return [1, null]}
+  / "*" _ {return [0, null]}
+  / "?" _ {return [0, 1]}
+  / quantifier_range
+
+quantifier_range
+  = "{" _ lo:INT hi:( _COMMA_ v:INT_GT0? {return v} )? _ "}" _
+    {return [lo, hi || null]}
+
+event_id         = ID
+event_id_init    = event_id
+event_id_ref     = event_id
+
+behavior_id      = ID
+behavior_id_init = behavior_id
+behavior_id_ref  = behavior_id
+
+system_id        = ID
+system_id_init   = system_id
+system_id_ref    = system_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}
+ASYNC          = t:"ASYNC"            {return t}
+
+_SYSTEM_       = _ t:SYSTEM _         {return t}
+_BEHAVIOR_     = _ t:BEHAVIOR _       {return t}
+_INTERACTION_  = _ t:INTERACTION _    {return t}
+_WHEN_         = _ t:WHEN _           {return t}
+
+_EQ_           = _ t:"=" _            {return t}
+_COMMA_        = _ t:"," _            {return t}
+_PIPE_         = _ t:"|" _            {return t}
+_COLON_        = _ t:":" _            {return t}
+_SEMI_         = _ t:";" _            {return t}
+
+
+EOF = !.
+
+ID "Identifier"	/*TODO: UNICODE?*/
+  = !KEYWORD [A-Za-z] [a-zA-Z0-9_]*
+    {return text()}
+
+INT_GT0 "Integer > 0"
+  = [1-9] [0-9]*
+    {return parseInt(text())}
+
+INT "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(""))}

+ 604 - 0
src/lib/parser/mp2.pegjs

@@ -0,0 +1,604 @@
+{
+  /*global parse, options, offset, line, column, text, SyntaxError*/
+  //TODO: the 'MP2-parser.rig' takes a scope and bakes it into the output we will do that elsewhere
+  //TODO: the 'MP2-parser.rig' checks for various error scenarios that we might bake into this
+  //TODO: when we get to bool_expr might look into using the `ast-types` npm to make any code generation easier (escodegen)
+  var opts = arguments[2] || options,
+    fileName = opts.fileName || "NOFILENAME",
+    locations = opts.locations !== undefined ? opts.locations : false,
+    comments = opts.comments !== undefined ? opts.comments : false;
+
+  var uniqueNumber = 0,
+    getUniqueNumber = function getUniqueNumber() {
+      return uniqueNumber++;
+    };
+
+  var err = function err(msg, suffix) {
+      if (typeof msg === "number") msg = "ERR" + msg + ": " + errors[msg];
+      if (suffix) msg += suffix;
+      return new SyntaxError(msg, null, null, offset(), line(), column());
+  };
+
+  var ast = {
+
+    errors: [], // tracks all of the non-grammatical errors
+
+    rulesById: {}, // track all of the rules by their ID
+
+    Base: function Base(type) {
+      this.type = type;
+      if (locations) {
+        this.location = {
+          pos: offset(),
+          line: line(),
+          col: column(),
+          len: text().length,
+        };
+      }
+      if (comments && type !== "Comment") {
+        //TODO: not attached in right place
+         this.comments = ast._comments;
+         ast._comments = [];
+      }
+    },
+
+    _comments: [],
+    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;
+    },
+
+    Schema: function Schema(includes, statements) {
+      ast.Id.call(this, "Schema", fileName);
+      this.includes = includes;
+      this.statements = statements;
+    },
+
+    Include: function Include(id) {
+      throw err(12, "INCLUDE");
+      ast.Id.call(this, "Include", id);
+    },
+
+    Rule: function Rule(id, ruleType, patterns, buildBlock) {
+      ast.Id.call(this, "Rule", id);
+      this.ruleType = ruleType;
+      // this.refId = "Comp_" + getUniqueNumber(); // aka "work_name" in RIGAL code
+      this.patterns = patterns;
+      // this.buildBlock = buildBlock;
+    },
+
+    RootRule: function RootRule(id, patterns, buildBlock) {
+      ast.Rule.call(this, id, "RootRule", patterns, buildBlock);
+    },
+
+    CompositeRule: function CompositeRule(id, patterns, buildBlock) {
+      ast.Rule.call(this, id, "CompositeRule", patterns, buildBlock);
+    },
+
+    Event: function Event(id) {
+      ast.Id.call(this, "Event", id);
+    },
+
+    Sequence: function Sequence(body) { // aka pattern_list
+      ast.Base.call(this, "Sequence");
+      // this.refId = "Sq_" + getUniqueNumber(); // aka "name" in RIGAL code
+      this.body = body;
+    },
+
+    Alternative: function Alternative(body) {
+      ast.Base.call(this, "Alternative");
+      // this.refId = "Alt_" + getUniqueNumber(); // aka "name" in RIGAL code
+      this.body = body;  // aka "patterns" in the RIGAL codye
+    },
+
+    Probability: function Probability(p) {
+      throw err(12, "PROBABILITY");
+      ast.Base.call(this, "Probability");
+      this.p = p;
+    },
+
+    MinMax: function MinMax(min, max) {
+      ast.Base.call(this, "MinMax");
+      this.min = min;
+      this.max = max;
+    },
+
+    Iterator: function Iterator(scope, body) {
+      ast.Base.call(this, "Iterator");
+      // this.refId = "Itr_" + getUniqueNumber(); // aka "name" in RIGAL code
+      this.scope = scope;
+      this.body = body;
+    },
+
+    Set: function Set(body) {
+      ast.Base.call(this, "Set");
+      // this.refId = "Set_" + getUniqueNumber(); // aka "name" in RIGAL code
+      this.body = body;
+    },
+
+    SetIterator: function SetIterator(scope, body) {
+      ast.Base.call(this, "SetIterator");
+      // this.refId = "SetIterator_" + getUniqueNumber(); // aka "name" in RIGAL code
+      this.scope = scope;
+      this.body = body;
+    },
+
+    Optional: function Optional(body) {
+      ast.Base.call(this, "Optional");
+      // this.refId = "Opt_" + getUniqueNumber(); // aka "name" in RIGAL code
+      this.body = body;
+    },
+
+    Coordinate: function Coordinate(async, srcs, body) {
+      ast.Base.call(this, "Coordinate");
+      // this.refId = "Coordinate_" + getUniqueNumber(); // aka "work" in RIGAL code
+      this.async = async;
+      this.srcs = srcs;
+      this.body = body;
+    },
+
+    CoordinateSource: function CoordinateSource(id, select, from) {
+      ast.Id.call(this, "CoordinateSource", id);
+      this.select = select;
+      this.from = from || "<this>";
+    },
+
+    ShareAll: function ShareAll(hosts, events) {
+      ast.Base.call(this, "ShareAll");
+      // this.refId = "ShareAll_" + getUniqueNumber(); // aka "work" in RIGAL code
+      this.hosts = hosts;
+      this.events = events;
+    },
+
+    Relationship: function Relationship(src, relType, dst) {
+        ast.Base.call(this, "Relationship");
+        this.relType = relType;
+        this.src = src;
+        this.dst = dst;
+    },
+
+    AddOperation: function AddOperation(rels) {
+      ast.Base.call(this, "Add");
+      this.relationships = rels;
+    },
+
+  };
+
+  parse.ast = ast;
+
+  if (opts.ast) {
+    /*TODO: setup overrides from opts.ast on top of ast*/
+  }
+
+  var errors = {
+    // 1: "wrong schema name $a identifier expected/ !", //NOTE: probably not applicable if we make SCHEMA go away (or at least optional silliness)
+    // 2: "wrong event pattern $a / !",  //NOTE: errors handled by our generated parser
+    3: "trigger event $a should not appear in WHEN pattern list / !",
+    //// 4: "detected around token $a/ !", //NOTE: errors handled by our generated parser
+    5: "defined event name should not appear in rule body: ",
+    //TODO: 6: "recursion for event $a is detected/ !", //TODO: check for this
+    // 7: "syntax error in probability definition detected at $a/ !", //NOTE: just took out of parser for now
+    // 8: "schema name $a should be the same as input parameter/ !", //NOTE: disabled this restriction
+    // 9: "keyword 'DO' is expected in COORDINATE when actual token is $a / !", //NOTE: errors handled by our generated parser
+    // 10: "keyword 'OD' is expected in COORDINATE when actual token is $a / !", //NOTE: errors handled by our generated parser
+    // 11: "semicolon is expected when actual token is $a / !", //NOTE: errors handled by our generated parser
+    12: "not implemented yet: ",
+    // 13: "':' is expected when actual token is $a / !", //NOTE: errors handled by our generated parser
+    // 14: "incorrect variable name $a in the COORDINATE source / !", //NOTE: errors handled by our generated parser
+    // 15: "derivation for root $a should be completed before composition/ !", //NOTE: handled by 21
+    // 16: "wrong relation name $a in the ADD composition/ !", //NOTE: errors handled by our generated parser
+    17: "variable is not defined: ",
+    //TODO: 18: "shared event $a has not been defined in any grammar rule / !", //TODO: not sure how to recreate to test this
+    19: "variable has been defined twice: ",
+    //NOTE: additional errors added by us
+    20: "rule defined more than once: ",
+    21: "reference to undefined item: ",
+    22: "reference to non-root event: ",
+  };
+
+} //jshint ignore:start
+
+// GRAMMAR
+start
+  =
+  !{ ast.errors = []; }
+  m:model
+  {
+    m.errors = ast.errors;
+    return m;
+  }
+
+model
+  = schema
+  /*TODO:NOT_YET:  ( ( assertion / query ) __ semi )*  */
+
+schema
+  =
+  (SCHEMA schema_id semi?)? /*NOTE: optional in ours for now*/
+  includes:schema_include*
+  statements:schema_statement*
+  /*TODO:NOT_YET: build_block?*/
+  EOF
+    { return new ast.Schema(includes, statements); }
+schema_include "INCLUDE statement" /*NOTE: renamed from 'include_clause' in 'MP2-parser.rig' file*/
+  = INCLUDE id:schema_id semi
+    { return new ast.Include(id); }
+schema_statement
+  =
+  s:(
+    rule
+    / composition_operation
+    /*TODO:NOT_YET: / new_schema*/
+  )
+  semi
+    { return s }
+
+rule
+  = r:ROOT? id:root_id !{ ast.ruleId = id.id; } colon pl:pattern_list? /*TODO:NOT_YET:build_block?*/
+    {
+      var rule = new (r ? ast.RootRule : ast.CompositeRule)(id, pl);
+      if (id.id in ast.rulesById) ast.errors.push(err(20, id.id));
+      ast.rulesById[rule.id.id] = rule;
+      return rule;
+    }
+
+pattern_list
+  = list:pattern_unit+
+    { return new ast.Sequence(list); }
+
+pattern_unit
+  =
+  id:event_id
+    {
+      if (id.id === ast.ruleId) ast.errors.push(err(5,id));
+      return id;
+    }
+  / alternative
+  / iterator
+  / iterator_plus
+  / set
+  / set_iterator
+  / set_iterator_plus
+  / optional
+  /*TODO:NOT_YET:  / "<|" pattern_list when_clause "|>" */
+  /*NOTE: empty handled by using optional pattern lists*/
+
+alternative
+  // = parenL list:alternative_list+ parenR
+  = parenL /*probability*/ item:pattern_list tail:(pipe /*probability*/ item:pattern_list {return item})* parenR
+    { return new ast.Alternative([item].concat(tail)); }
+
+probability /*TODO:NOT_YET:*/
+ = ltlt f:float gtgt
+    { return new ast.Probability(f); }
+
+
+iterator_scope_any /*iteration_scope iff min>=0*/
+  = lt a:(integer to)? b:integer gt
+    { return new ast.MinMax(a !== null ? a[0] : b, b); }
+
+iterator_scope_some /*iteration_scope iff min>=1*/
+  = lt a:(integer_gt0 to)? b:integer_gt0 gt
+    { return new ast.MinMax(a !== null ? a[0] : b, b); }
+
+iterator
+  = begin_iter s:iterator_scope_any? pl:pattern_list end_iter
+    { return new ast.Iterator(s !== null ? s : new ast.MinMax(0, null), pl); }
+
+iterator_plus
+  = begin_itpl s:iterator_scope_some? pl:pattern_list end_itpl
+    { return new ast.Iterator(s !== null ? s : new ast.MinMax(1, null), pl); }
+
+
+
+set
+ = braceL pl:pattern_lists_by_comma? braceR
+   { return new ast.Set(pl); }
+
+pattern_lists_by_comma
+  = item:pattern_list tail:(comma item:pattern_list { return item; })*
+    { return [item].concat(tail); }
+
+
+set_iterator
+  = begin_seti s:iterator_scope_any? pl:pattern_list end_seti
+    { return new ast.SetIterator(s !== null ? s : new ast.MinMax(0, null), pl); }
+
+set_iterator_plus
+  = begin_setp s:iterator_scope_some? pl:pattern_list end_setp
+    { return new ast.SetIterator(s !== null ? s : new ast.MinMax(1, null), pl); }
+
+optional
+  = bracketL /*TODO:NOT_YET:probability?*/ pl:pattern_list bracketR
+    { return new ast.Optional(pl); }
+
+/*
+NOTE: not yet...
+when_clause
+  = "WHEN" when_units_by_comma
+when_unit
+  = probability? event_id "==>" pattern_list
+*/
+
+
+
+
+/**
+ * COMPOSITION OPERATIONS
+ */
+
+composition_operation
+  = shared_composition
+  / coordinate_composition
+  /*TODO:NOT_YET:/ ENSURE bool_expr*/
+
+
+shared_composition
+  = share:root_or_var_by_exunionstr_by_comma SHARE_ALL on:event_id_by_comma
+    { return new ast.ShareAll(share, on); }
+
+root_or_var
+  = id:root_id
+    {
+      if (!(id.id in ast.rulesById)) ast.errors.push(err(21, id.id)); //TODO: move to errors
+      else if (ast.rulesById[id.id].ruleType !== "RootRule") ast.errors.push(err(22, id.id)); //TODO: move to errors
+      return id;
+    }
+  / variable
+
+root_or_var_by_exunionstr
+  = item:root_or_var tail:(exunionstr item:root_or_var { return item; })*
+    { return [item].concat(tail); }
+
+root_or_var_by_exunionstr_by_comma
+ = item:root_or_var_by_exunionstr tail:(comma item:root_or_var_by_exunionstr { return item; })*
+    { return [item].concat(tail); }
+
+event_id_by_comma
+ = item:event_id tail:(comma item:event_id)*
+    { return [item].concat(tail); }
+
+
+coordinate_composition
+  =
+  COORDINATE async:asyncstr?
+  srcs:coordination_source_by_comma
+  DO
+    body:do_body_statement+
+  OD
+    { return new ast.Coordinate(Boolean(async), srcs, body); }
+
+coordination_source_by_comma
+  =
+  head:(
+    item:coordination_source
+      {
+        ast._srcs = {};
+        ast._srcs[item.id] = 1;
+        return [item];
+      }
+  )
+  tail:(
+    comma item:coordination_source
+      {
+        if (item.id in ast._srcs) ast.errors.push(err(19, item.id));
+        ast._srcs[item.id] = 1;
+        return item;
+      }
+  )*
+    { return head.concat(tail); }
+
+do_body_statement
+  = item:(
+    /*TODO:NOT_YET: variable colon new_instance*/
+    add_relation
+    /*TODO:NOT_YET:/ MAP_composition*/
+    / shared_composition
+    / coordinate_composition
+  )
+  semi
+    { return item }
+
+coordination_source
+  = v:variable colon s:selection_pattern f:(FROM (THIS / root_or_var))?
+    {
+      var frm = f ? f[1] : null;
+      if (frm && frm[0] === "$" && !(frm in ast._srcs)) ast.errors.push(err(17, frm));
+      return new ast.CoordinateSource(v, s, frm);
+    }
+
+add_relation
+  = ADD rels:relationships_by_comma
+    { return new ast.AddOperation(rels); }
+relationships_by_comma
+  =
+  head:(
+    item:relationship
+      {
+        if (!(item.src in ast._srcs)) ast.errors.push(err(17, item.src));
+        if (!(item.dst in ast._srcs)) ast.errors.push(err(17, item.dst));
+        return [item];
+      }
+  )
+  tail:(
+    comma item:relationship
+      {
+        if (!(item.src in ast._srcs)) ast.errors.push(err(17, item.src));
+        if (!(item.dst in ast._srcs)) ast.errors.push(err(17, item.dst));
+        return item;
+      }
+  )*
+    { return head.concat(tail); }
+
+relationship
+  = start:variable relType:(IN / PRECEDES / CONTAINS / FOLLOWS) second:variable
+    { return new ast.Relationship(start, relType, second); }
+
+selection_pattern
+  = event_id
+  / alternative_of_event_names
+
+alternative_of_event_names
+  = parenL alts:event_id_by_pipe parenR
+    { return new ast.Alternative(alts); }
+event_id_by_pipe
+  = item:event_id tail:(pipe item:event_id {return item;})*
+    { return [item].concat(tail); }
+
+
+
+/*TODO:NOT_YET:
+MAP_composition
+  = MAP
+    { throw err(12, "MAP"); }
+*/
+
+
+/*TODO:NOT_YET:
+build_block "BUILD block"
+  =
+  BUILD braceL
+  (
+    (
+      composition_operation
+    / plain_attribute_declaration
+    / event_attribute_declaration
+    )
+  semi
+  )*
+  braceR
+    { throw err(12, "BUILD"); }
+plain_attribute_declaration
+  = !{ throw err(12, "BUILD"); }
+event_attribute_declaration
+  = !{ throw err(12, "BUILD"); }
+*/
+
+
+
+
+//LEX
+
+schema_id "SCHEMA identifier"
+  = id
+
+keyword /*NOTE: list from the MP2-parser.rig definition of "keyword", though it seems incomplete*/
+  = "WHEN" / "COORDINATE" / "ENSURE" / "DO" / "OD" /  "FROM"
+
+root_id "ROOT identifier"
+  = !keyword id:id
+  { return new ast.Event(id); }
+
+event_id "EVENT identifier"
+  = !keyword id:id
+  { return new ast.Event(id); }
+
+variable "VARIABLE"
+  = _ "$" id:id _
+    { return "$" + id; }
+
+id
+  = _ a:[A-Za-z] b:[a-zA-Z0-9_]* _
+    { return a + b.join(""); }
+
+integer
+  = _ a:[0-9]+ _
+    { return a.join(""); }
+
+integer_gt0
+  = _ a:[1-9] b:[0-9]* _
+    { return a + b.join(""); }
+
+float
+  = _ a:[0-9]+ b:("." b:[0-9]+)?
+    { return parseFloat(b ? a.join("") + "." + b[1].join("") : a.join("")); }
+  / _ "." b:[0-9]+
+    { return parseFloat("." + b.join("")); }
+
+begin_iter  = _ "(*" _
+end_iter    = _ "*)" _
+begin_itpl  = _ "(+" _
+end_itpl    = _ "+)" _
+begin_seti  = _ "{*" _
+end_seti    = _ "*}" _
+begin_setp  = _ "{+" _
+end_setp    = _ "+}" _
+
+asyncstr    = _ "<!>" _
+exunionstr    = _ "|+|" _
+to          = _ ".." _
+
+parenL      = _ "(" _
+parenR      = _ ")" _
+braceL      = _ "{" _
+braceR      = _ "}" _
+bracketL    = _ "[" _
+bracketR    = _ "]" _
+lt          = _ "<" _
+gt          = _ ">" _
+ltlt        = _ "<<" _
+gtgt        = _ ">>" _
+colon       = _ ":" _
+semi        = _ ";" _
+comma       = _ "," _
+pipe        = _ "|" _
+
+SCHEMA     = _ "SCHEMA" __
+INCLUDE    = _ "INCLUDE" __
+ROOT       = _ "ROOT" __
+SHARE_ALL  = _ "SHARE" __ "ALL" __
+COORDINATE = _ "COORDINATE" __
+FROM       = _ "FROM" __
+
+ADD        = _ "ADD" __
+IN         = _ "IN" __                      { return "IN"; }
+CONTAINS   = _ "CONTAINS" __                { return "CONTAINS"; }
+PRECEDES   = _ "PRECEDES" __                { return "PRECEDES"; }
+FOLLOWS    = _ "FOLLOWS" __                 { return "FOLLOWS"; }
+THIS       = _ "this" _                     { return "this"; }
+
+DO         = _ "DO" _
+OD         = _ "OD" _
+
+BUILD      = _ "BUILD" _
+NEW        = _ "NEW" _
+MAP        = _ "MAP" _
+ENSURE     = _ "ENSURE" _
+
+EOF
+  = !.
+
+
+_ "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:comment_block_char* "*/"
+    { return new ast.Comment(true, c.join("")); }
+comment_block_char
+  = !"*/" c:.
+    { return c; }

+ 8 - 0
src/www/.jshintrc

@@ -0,0 +1,8 @@
+{
+	"extends": "../.jshintrc",
+	"browser": true,
+	"jquery": true,
+	"globals": {
+		"angular": true
+	}
+}