Просмотр исходного кода

MPIDE-27: implement second pass over parsed AST

The PEG parser now runs over the syntax tree a second time to resolve
any outstanding issues that couldn't be handled during the initial
parse. This includes extended error reporting, a flatter output
structure, and a more complete list of behaviors.

The ambiguity between expression and interaction '==' has been resolved
by mandating interactions to be of the `system:pattern` format. The
equality operator has been restored to '=='.
Austin Meagher 10 лет назад
Родитель
Сommit
60a2d0b856
1 измененных файлов с 108 добавлено и 49 удалено
  1. 108 49
      src/lib/parser/modellang.pegjs

+ 108 - 49
src/lib/parser/modellang.pegjs

@@ -5,18 +5,88 @@
         locations = opts.locations !== undefined ? opts.locations : false,
         comments = opts.comments !== undefined ? opts.comments : false;
 
-    var err = function err(msg, suffix) {
+    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, null, null, offset(), line(), column());
+        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: [],
+        };
+
+        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 "Order":
+                case "Join":
+                    model.interactions.push(statement);
+                    break;
+                case "Trigger":
+                    model.triggers.push(statement);
+                    break;
+                default: break;
+            }
+        }
+
+        for (var r in ast.references) {
+            var ref = ast.references[r];
+
+            if (!model.behaviors[ref])
+                model.behaviors[ref] = { type:"Behavior", id:ref, body:[] };
+        }
+
+        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 (!model.systems[sys.system])
+                    model.errors.push(err("WARNING: reference to an undefined system (" + sys.system + ")", null, sys.location));
+            });
+        }
+
+        return model;
     };
 
     var ast = {
 
-        errors: [], // tracks all of the non-grammatical errors
         comments: [],
-        eventRefs: [],
-        properties: [],
+        errors: [],
+        references: [],
 
         Base: function Base(type) {
             this.type = type;
@@ -60,11 +130,13 @@
         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) {
@@ -94,20 +166,13 @@
 
     };
 
-    var errors = {
-        0: "System has been defined twice: ",
-        1: "Behavior has been defined twice: ",
-        2: "mixed operators used in single Interaction",
-    };
-
 } //jshint ignore:start
 
 
 
 start
-    = !{ ast.errors = []; }
-    s:statement* EOF
-    { return { errors: ast.errors, statements: s, comments: ast.comments }; }
+    = s:statement*
+    { return model(s);  }
 
 statement
     = s:( system_statement
@@ -118,20 +183,25 @@ statement
     { return s; }
 
 system_statement
-    = _SYSTEM_ _COLON_ id:system_id _EQ_ body:behavior_pattern
+    = _SYSTEM_ _COLON_ id:system_id
+    !{ ast.toplevelname = id; }
+    _EQ_ body:behavior_pattern
     { return new ast.System(id, body); }
 
 behavior_statement
-    = _BEHAVIOR_ _COLON_ id:behavior_id _EQ_ body:behavior_pattern
+    = _BEHAVIOR_ _COLON_ id:behavior_id
+    !{ ast.toplevelname = id; }
+    _EQ_ body:behavior_pattern
     { return new ast.Behavior(id, body); }
 
 interaction_statement
-    = _INTERACTION_ _COLON_ first:( system_item_selector / interaction_expr )
-    type:interaction_type !{ast.t=type} second:( system_item_selector / interaction_expr )
+    = _INTERACTION_ _COLON_ first:( system_item_selector / expression )
+    type:interaction_type second:( system_item_selector / expression )
+    !{ ast.interactionType = type; }
     tail:(
         type2:interaction_type
-        !{ if (type2 != ast.t) ast.errors.push(err("Incompatible Interaction Types in same Interaction Statement")); }
-        tail2:( system_item_selector / interaction_expr )
+        !{ if (type2 !== ast.interactionType) ast.errors.push(err("ERROR: Mixed interaction types", null, { expected:ast.interactionType, found:type2 })); }
+        tail2:( system_item_selector / expression )
         { return tail2; }
     )*
     { return new ast.Interaction(type, [first, second].concat(tail)); }
@@ -141,12 +211,11 @@ interaction_type
     { return t; }
 
 system_item_selector
-    = !([a-z0-9 ]i+ operator)
-    _ sys:system_id sub:(":" _ su:("*" / behavior_pattern) { return su; } )? _
-    { return new ast.Selector(sys, sub); }
+    = !([a-z0-9 ]i+ operator) _ sys:system_id ":" _ p:behavior_pattern _
+    { return new ast.Selector(sys, p); }
 
-interaction_expr
-    = term:term op:operator expr:interaction_expr
+expression
+    = term:term op:operator expr:expression
     { return new ast.Expression(op, term, expr); }
     / term
 
@@ -160,7 +229,7 @@ operator
     / _ o:"<" _ { return o; }
     / _ o:">=" _ { return o; }
     / _ o:"<=" _ { return o; }
-    / _ o:"~=" _ { return o; }
+    / _ o:"==" _ { return o; }
 
 behavior_pattern
     = alternation
@@ -176,10 +245,7 @@ sequence
 
 behavior_pattern_item
     = _ i:( group / event_ref ) q:quantifier? _
-    {
-        if (q === null) q = {min:1, max:1};
-        i.scope = q; return i;
-    }
+    { return scopify(i, q); }
 
 group
     = _ async:ASYNC? "(" _ body:behavior_pattern _ ")"
@@ -193,21 +259,29 @@ quantifier
 
 quantifier_range
     = "{" _ lo:INT_GTE0 hi:( u:_COMMA_ v:INT_GT0? { return {u:u, v:v}; } )? _ "}" _
-    { var m = hi ? hi.v || null : lo; return {min:lo, max:m}; }
+    { return minmaxify(lo, hi); }
 
 trigger
     = _WHEN_ _COLON_ selector:( system_item_selector / expression ) "{" body:embedded_code "}"
     { return new ast.Trigger(selector, body); }
 
-/* TODO: dies on nested braces */
+/* FIXME: dies on nested braces */
 embedded_code
     = (![{}] .)*
     { return text().trim(); }
 
 behavior_id = id:ID { return id; }
 system_id   = id:ID { return id; }
-event_ref    = id:ID { if (ast.eventRefs.indexOf(id) == -1) ast.eventRefs.push(id); return { type: "unknown", id:id }; }
-property    = id:ID { if (ast.properties.indexOf(id) == -1) ast.properties.push(id); return id; }
+property    = id:ID { return id; }
+/* FIXME: it's possible, though not correct, to reference systems from a behavior; this will mark them as behaviors */
+event_ref   = id:ID
+    {
+        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
@@ -273,18 +347,3 @@ COMMENT_LINE "Comment Line"
 COMMENT_BLOCK "Comment Block"
     = "/*" c:(!"*/" c:. {return c})* "*/"
     { return new ast.Comment(true, c.join("")); }
-
-
-EOF = !. {
-    //-- SECOND PASS --//
-    // TODO: collect accurate dictionaries of systems, behaviors, and atoms
-    // TODO: mutate Orders and Joins into an array of constraints
-    /* output?:
-        {
-            errors,
-            systems,
-            behaviors,
-            constraints
-        }
-    */
-}