modellang.pegjs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. {
  2. /*global parse, options, offset, line, column, text, SyntaxError*/
  3. var opts = arguments[2] || options,
  4. fileName = opts.fileName || "NOFILENAME",
  5. locations = opts.locations !== undefined ? opts.locations : true,
  6. comments = opts.comments !== undefined ? opts.comments : false,
  7. showAst = opts.showAst !== undefined ? opts.showAst : false;
  8. var err = function err(msg, suffix, opts) {
  9. opts = opts || {};
  10. if (typeof msg === "number") msg = "ERR" + msg + ": " + errors[msg];
  11. if (suffix) msg += suffix;
  12. return new SyntaxError(msg,
  13. opts.expected,
  14. opts.found,
  15. offset(),
  16. opts.line || line(),
  17. opts.col || column()
  18. );
  19. };
  20. var scopify = function scopify(i, q) {
  21. if (q === null) q = {min:1, max:1};
  22. i.scope = q;
  23. return i;
  24. }
  25. var minmaxify = function minmaxify(lo, hi) {
  26. var m = hi ? hi.v || null : lo;
  27. return {min:lo, max:m};
  28. }
  29. var model = function model(statements) {
  30. var model = {
  31. systems: {},
  32. behaviors: {},
  33. interactions: [],
  34. triggers: [],
  35. init: [],
  36. };
  37. if (showAst) model.ast = statements;
  38. // accumulate varying statements into a flatter structure
  39. for (var s in statements) {
  40. var statement = statements[s];
  41. switch(statements[s].type) {
  42. case "System":
  43. model.systems[statement.id] = statement;
  44. break;
  45. case "Behavior":
  46. model.behaviors[statement.id] = statement;
  47. break;
  48. case "Then":
  49. case "Join":
  50. model.interactions.push(statement);
  51. break;
  52. case "Trigger":
  53. model.triggers.push(statement);
  54. break;
  55. case "Init":
  56. model.init.push(statement);
  57. default: break;
  58. }
  59. }
  60. // add atomic events to behaviors list
  61. for (var r in ast.references) {
  62. var ref = ast.references[r];
  63. if (!model.behaviors[ref])
  64. model.behaviors[ref] = new ast.Behavior(ref, [], [])
  65. }
  66. return warnify(model);
  67. };
  68. var warnify = function warnify(model) {
  69. model.errors = ast.errors;
  70. for (var i in model.interactions) {
  71. model.interactions[i].body.forEach(function (sys) {
  72. if (sys.type === "Selector" && !model.systems[sys.system])
  73. model.errors.push(err("ERROR: reference to an undefined system (" + sys.system + ")", null, sys.location));
  74. });
  75. }
  76. for (var t in model.triggers) {
  77. var sys = model.triggers[t].on;
  78. if (sys.type === "Selector" && !model.systems[sys.system])
  79. model.errors.push(err("ERROR: reference to an undefined system (" + sys.system + ")", null, sys.location));
  80. }
  81. for (var i in model.init) {
  82. var sys = model.init[i].system;
  83. if (sys && !model.systems[sys])
  84. model.errors.push(err("ERROR: reference to an undefined system (" + sys + ")", null, model.init[i].location));
  85. }
  86. for (var b in model.behaviors) {
  87. model.behaviors[b].refs.forEach(function (ref) {
  88. if (model.systems[ref])
  89. model.errors.push(err("ERROR: reference to system (" + ref + ") in a behavior (" + model.behaviors[b].id + ")", null, model.behaviors[b].location));
  90. });
  91. }
  92. return model;
  93. };
  94. var ast = {
  95. comments: [],
  96. errors: [],
  97. references: [],
  98. Base: function Base(type) {
  99. this.type = type;
  100. if (locations) {
  101. this.location = {
  102. pos: offset(),
  103. line: line(),
  104. col: column(),
  105. len: text().length,
  106. };
  107. }
  108. },
  109. Comment: function Comment(block, text) {
  110. ast.Base.call(this, "Comment");
  111. this.block = block;
  112. this.text = text;
  113. ast.comments.push(this);
  114. },
  115. Id: function Id(type, id) {
  116. ast.Base.call(this, type);
  117. this.id = id;
  118. },
  119. System: function System(id, body, refs) {
  120. ast.Id.call(this, "System", id);
  121. this.body = body;
  122. this.refs = refs;
  123. this.properties = {};
  124. this.scope = { min:1, max:1 };
  125. },
  126. Behavior: function Behavior(id, body, refs) {
  127. ast.Id.call(this, "Behavior", id);
  128. this.body = body;
  129. this.refs = refs;
  130. this.properties = {};
  131. },
  132. Interaction: function Interaction(type, body) {
  133. ast.Base.call(this, type);
  134. this.body = body;
  135. },
  136. Sequence: function Sequence(body) {
  137. ast.Base.call(this, "Sequence");
  138. this.body = body;
  139. this.scope = { min:1, max:1 };
  140. },
  141. Alternation: function Alternation(body) {
  142. ast.Base.call(this, "Alternation");
  143. this.body = body;
  144. this.scope = { min:1, max:1 };
  145. },
  146. Group: function Group(async, body) {
  147. ast.Base.call(this, "Group");
  148. this.async = async;
  149. this.body = body;
  150. },
  151. Selector: function Selector(system, pattern, pre, post) {
  152. ast.Base.call(this, "Selector");
  153. this.system = system;
  154. this.pattern = pattern;
  155. this.pre = pre || null;
  156. this.post = post || null;
  157. },
  158. Trigger: function Trigger(selector, body) {
  159. ast.Base.call(this, "Trigger");
  160. this.on = selector;
  161. this.do = body;
  162. },
  163. Init: function Init(system, body) {
  164. ast.Base.call(this, "Init");
  165. this.system = system;
  166. this.do = body;
  167. },
  168. };
  169. } //jshint ignore:start
  170. start
  171. = s:statement*
  172. { return model(s); }
  173. statement
  174. = s:( system_statement
  175. / behavior_statement
  176. / interaction_statement
  177. / trigger
  178. / init )
  179. _SEMI_
  180. { return s; }
  181. system_statement "System"
  182. = _SYSTEM_ _COLON_ id:system_id
  183. !{ ast.toplevelname = id; ast.localrefs = []; }
  184. _EQ_ body:behavior_pattern
  185. { return new ast.System(id, body, ast.localrefs); }
  186. behavior_statement "Behavior"
  187. = _BEHAVIOR_ _COLON_ id:behavior_id
  188. !{ ast.toplevelname = id; ast.localrefs = []; }
  189. _EQ_ body:behavior_pattern
  190. { return new ast.Behavior(id, body, ast.localrefs); }
  191. interaction_statement "Interaction"
  192. = _INTERACTION_ _COLON_ interaction:interaction_body
  193. { return interaction; }
  194. interaction_body
  195. = first:system_item_selector _ type:THEN _
  196. second:system_item_selector rest:(_ THEN _ a:system_item_selector { return a; })*
  197. { return new ast.Interaction(type, [first, second].concat(rest)); }
  198. / first:system_item_selector_sans _ type:JOIN _
  199. second:system_item_selector_sans rest:(_ JOIN _ a:system_item_selector_sans { return a; })*
  200. { return new ast.Interaction(type, [first, second].concat(rest)); }
  201. interaction_type
  202. = _ t:( THEN / JOIN ) _
  203. { return t; }
  204. system_item_selector "Selector"
  205. = !{ ast.selector = true; }
  206. _ pre:( "{" c:embedded_code "}" { return c; } )? _
  207. sys:system_id ":" _ pattern:behavior_pattern
  208. _ post:( "{" c:embedded_code "}" { return c; } )? _
  209. { ast.selector = false; return new ast.Selector(sys, pattern, pre, post); }
  210. system_item_selector_sans "Selector"
  211. = !{ ast.selector = true; }
  212. _ sys:system_id ":" _ pattern:behavior_pattern _
  213. { ast.selector = false; return new ast.Selector(sys, pattern); }
  214. behavior_pattern "Behavior Pattern"
  215. = ( alternation / sequence )+
  216. alternation
  217. = head:behavior_pattern_item tail:( _PIPE_ item:behavior_pattern_item { return item; } )+
  218. {
  219. var body = [head].concat(tail);
  220. for (var i = 0; i < body.length; i++) {
  221. if (body[i].scope.max && body[i].scope.min > body[i].scope.max)
  222. ast.errors.push(err("ERROR: Scope (" + body[i].id + ") lower bound exceeds its upper bound"));
  223. }
  224. return new ast.Alternation(body);
  225. }
  226. sequence
  227. = body:behavior_pattern_item+
  228. {
  229. for (var i = 0; i < body.length; i++) {
  230. if (body[i].scope.max && body[i].scope.min > body[i].scope.max)
  231. ast.errors.push(err("ERROR: Scope (" + body[i].id + ") lower bound exceeds its upper bound"));
  232. }
  233. return new ast.Sequence(body);
  234. }
  235. behavior_pattern_item
  236. = _ i:group q:quantifier? _
  237. { return scopify(i, q); }
  238. / _ i:event_ref q:quantifier? _
  239. { if (ast.localrefs.indexOf(i.id) === -1 && !ast.selector) { ast.localrefs.push(i.id); } return scopify(i, q); }
  240. group "Group"
  241. = _ async:ASYNC? "(" _ body:behavior_pattern _ ")"
  242. { return new ast.Group(async, body); }
  243. quantifier "Quantifier"
  244. = "+" _ { return {min:1, max:null}; }
  245. / "*" _ { return {min:0, max:null}; }
  246. / "?" _ { return {min:0, max:1}; }
  247. / quantifier_range
  248. quantifier_range
  249. = "{" _ lo:INT_GTE0 hi:( u:_COMMA_ v:INT_GT0? { return {u:u, v:v}; } )? _ "}" _
  250. { return minmaxify(lo, hi); }
  251. trigger "Trigger"
  252. = _WHEN_ _COLON_ selector:system_item_selector_sans "{" body:embedded_code "}"
  253. { return new ast.Trigger(selector, body); }
  254. init "Init Block"
  255. = _INIT_ _COLON_ system:system_id? _ "{" body:embedded_code "}"
  256. { return new ast.Init(system, body); }
  257. embedded_code
  258. = [^{}]* ( "{" embedded_code "}" )? [^{}]*
  259. { return text().trim(); }
  260. behavior_id = id:ID { return id; }
  261. system_id = id:ID { return id; }
  262. event_ref = id:ID
  263. {
  264. if (!ast.selector) {
  265. if (id === ast.toplevelname) {
  266. ast.errors.push(err("ERROR: Definition is recursive (" + id + ")"));
  267. ast.toplevelname = null;
  268. } else ast.references.push(id);
  269. }
  270. return { type: "Behavior", id:id };
  271. }
  272. KEYWORD = SYSTEM / BEHAVIOR / INTERACTION / ASYNC
  273. SYSTEM = t:"SYSTEM" { return t; }
  274. BEHAVIOR = t:"BEHAVIOR" { return t; }
  275. INTERACTION = t:"INTERACTION" { return t; }
  276. WHEN = t:"WHEN" { return t; }
  277. INIT = t:"INIT" { return t; }
  278. ASYNC = t:"ASYNC" { return t; }
  279. THEN = "->" { return "Then"; }
  280. JOIN = "==" { return "Join"; }
  281. _SYSTEM_ = _ t:SYSTEM _ { return t; }
  282. _BEHAVIOR_ = _ t:BEHAVIOR _ { return t; }
  283. _INTERACTION_ = _ t:INTERACTION _ { return t; }
  284. _WHEN_ = _ t:WHEN _ { return t; }
  285. _INIT_ = _ t:INIT _ { return t; }
  286. _EQ_ = _ t:"=" _ { return t; }
  287. _COMMA_ = _ t:"," _ { return t; }
  288. _PIPE_ = _ t:"|" _ { return t; }
  289. _COLON_ = _ t:":" _ { return t; }
  290. _SEMI_ = _ t:";" _ { return t; }
  291. ID "Identifier" /*TODO: UNICODE?*/
  292. = !KEYWORD [A-Za-z] [a-zA-Z0-9_]*
  293. { return text(); }
  294. INT "Integer"
  295. = [0-9]+
  296. { return parseInt(text()); }
  297. INT_GT0 "Integer > 0"
  298. = [1-9] [0-9]*
  299. { return parseInt(text()); }
  300. INT_GTE0 "Integer >= 0"
  301. = "0"
  302. { return 0; }
  303. / INT_GT0
  304. _ "Optional Whitespace"
  305. = ([ \t\n\r] / COMMENT)*
  306. { return null; }
  307. __ "Required Whitespace"
  308. = [ \t\n\r] _?
  309. { return null; }
  310. NL "Newline"
  311. = [\n]
  312. { return null; }
  313. COMMENT
  314. = c:COMMENT_LINE NL?
  315. { return c; }
  316. / c:COMMENT_BLOCK NL?
  317. { return c; }
  318. COMMENT_LINE "Comment Line"
  319. = "//" c:[^\n]*
  320. { return new ast.Comment(false, c.join("")); }
  321. COMMENT_BLOCK "Comment Block"
  322. = "/*" c:(!"*/" c:. {return c})* "*/"
  323. { return new ast.Comment(true, c.join("")); }