Playing with Ohm
JavaScript Workspace
X

Menu
JavaScript Workspace
X

Menu
test code
X

Menu
/*global*/
/*
 * Utility functions that help to inspect, enumerate, and create JS objects
 */
;(function(exports) {
"use strict";
// foo
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
depth: 1
reset
asts
X

Menu
show vars
var source1 = Functions.extractBody(function() {
  lively.Object.subclass('Foo', {
    m: function() { return 23 + 1; },
  });
  function testFunction(arg) {
    return 1 + Date.now();
  }
  (function() {
    show("inline code");
  })();
})
var ast1 = Global.ast1 = lively.ast.acorn.parse(source1, {addAstIndex: true});
ast1.body[0].expression.type
// => "CallExpression"
ast1.body[0].expression.callee.property.name
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
This is kind of an experiment to use Ohm for creating a customizable code chunk parser, primarily for JS. This parser should then be used with a lightweight code browser to a) be able to change JS code (also non-lively, non-runtime code) and b) if Lively has access to a runtime that uses the code the parsed chunks should allow to dynamically load those changes into that environment without restarts. Think of: You are using some lib like Ohm and encounter sth. to fix. Instead of having to change the "dead code" and completely reload your environment or at least the lib, you should be able to define scopes and rules for those scopes to update the runtime accordingly.
depth: 1
reset
Ohm Setup
X

Menu
show vars
this.ohmSetupCode = this.ohmSetupCode
  || URL.root.withFilename("core/ohm/setup.js").asWebResource().get().content;
var ohmSetup = {exports: {}};
lively.lang.VM.syncEval(this.ohmSetupCode, {topLevelVarRecorder: ohmSetup})
ohmSetup.exports.reload()
Global.jsModuleParser = ohmSetup.exports;
Global.recognizeAST = ohmSetup.exports.parseJSModule;
ohmSetup.exports.error
// => undefined
this.get("testCode1Parsing") && this.get("testCode1Parsing").doAutoEvalPrintItComments();
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Note: changes of the grammar and setup code will auto-eval and update the test workspaces.
depth: 1
reset
test code parsing
X

Menu
show vars
this.name = "testCode1Parsing";
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
recognizeAST(ast1, 'block');
// => [{
//   astNode: 15,
//   children: [/*...*/],
//   name: "Foo",
//   type: "subclass"
// },{
//   astNode: 26,
//   name: "testFunction",
//   type: "functionDecl"
// },{
//   astNode: 34,
//   astType: "ExpressionStatement",
//   type: "unknownNode"
// }]
recognizeAST(ast1.body[0].expression.arguments[0], 'literal');
// => "Foo"
recognizeAST(ast1.body[0], 'subclass')
// => {
//   astNode: 15,
//   children: [/*...*/],
//   name: "Foo",
//   type: "subclass"
// }
recognizeAST(ast2.body[0].expression.arguments[1], 'objectLiteral');
// => [{
//   astNode: 34,
//   end: 53,
//   name: "prop",
//   propertiesIndex: 0,
//   start: 36,
//   type: "property"
// },{
//   astNode: 34,
//   end: 105,
//   name: "m1",
//   propertiesIndex: 1,
//   start: 59,
//   type: "property"
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Ohm Grammars
X

Menu
JavaScriptModuleParser {
  ident = letter alnum*
  topLevel = blockWithModuleDef | block
  blockWithModuleDef = {
    body: [
      (~moduleAsImmediateFunction unknownNode)*
      moduleAsImmediateFunction
      unknownNode*
    ],
    ...
  }
  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  // ast helpers
  moduleAsImmediateFunction = {
    expression: {
      callee: {type: 'FunctionExpression', body: block, ...}, ...
    }, ...
  }
  unknownNode = {type: [ident], ...}
  literal = {value: [ident], ...}
  block = {body: [knownPattern*], ...}
  objectLiteral = {
    type: 'ObjectExpression',
    properties: [keyValProperty*],
    ...
  }
  keyValProperty = {
    key: {name: [ident], ...},
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
http://lively-web.org/core/ohm/setup.js
X

Menu
http://lively-web.org/core/ohm/setup.js
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
save
load
remove
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*global exports,ohm*/
// Kind of an experimental Ohm setup for JS code chunk parsing, supports
// dynamic development in
// http://lively-web.org/users/robertkrahn/2014-11-02_AST-matching-withOhm.html
// but can also be loaded into other worlds without much fuss
if (typeof exports === "undefined") throw new Error("Error loading ohm setup: no exports obj");
if (typeof ohm === "undefined") throw new Error("Error loading ohm setup: no ohm obj");
function installGrammars() {
  var grammars = {install: function (namegrammar) { this[name] = grammar; }};
  var grammarsDef = $morph && $morph("OhmGrammars") && $morph("OhmGrammars").grammars;
  if (grammarsDef) {
    Object.extend(grammars, grammarsDef);
  } else {
// the recipe is only used when the "OhmGrammars" workspace isn't avaialable to
// provide a delicious fresh grammar
(function(ohm, optNamespace) {
  var b = ohm._builder();
  b.setName('JavaScriptModuleParser');
  b.setRuleDescription(undefined); b.define('ident', b.seq(b.app('letter'), b.many(b.app('alnum'), 0)));
  b.setRuleDescription(undefined); b.define('topLevel', b.alt(b.app('blockWithModuleDef'), b.app('block')));
  b.setRuleDescription(undefined); b.define('blockWithModuleDef', b.obj([{name: 'body', pattern: b.listy(b.seq(b.many(b.seq(b.not(b.app('moduleAsImmediateFunction')), b.app('unknownNode')), 0), b.app('moduleAsImmediateFunction'), b.many(b.app('unknownNode'), 0)))}], true));
  b.setRuleDescription(undefined); b.define('moduleAsImmediateFunction', b.obj([{name: 'expression', pattern: b.obj([{name: 'callee', pattern: b.obj([{name: 'type', pattern: b.prim('FunctionExpression')}, {name: 'body', pattern: b.app('block')}], true)}], true)}], true));
  b.setRuleDescription(undefined); b.define('unknownNode', b.obj([{name: 'type', pattern: b.listy(b.app('ident'))}], true));
  b.setRuleDescription(undefined); b.define('literal', b.obj([{name: 'value', pattern: b.listy(b.app('ident'))}], true));
  b.setRuleDescription(undefined); b.define('block', b.obj([{name: 'body', pattern: b.listy(b.many(b.app('knownPattern'), 0))}], true));
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
logger
X

Menu
{"data":{"action":"insertText","range":{"start":{"row":4,"column":32},"end":{"row":4,"column":34}},"text":"{}"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":31},"end":{"row":4,"column":32}},"text":" "}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":28},"end":{"row":4,"column":29}},"text":" "}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":27},"end":{"row":4,"column":28}},"text":"s"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":26},"end":{"row":4,"column":27}},"text":"e"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":24},"end":{"row":4,"column":25}},"text":"a"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":23},"end":{"row":4,"column":24}},"text":"t"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":22},"end":{"row":4,"column":23}},"text":"s"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":21},"end":{"row":4,"column":22}},"text":"."}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":20},"end":{"row":4,"column":21}},"text":"s"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":18},"end":{"row":4,"column":19}},"text":"o"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":16},"end":{"row":4,"column":17}},"text":"t"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":13},"end":{"row":4,"column":14}},"text":" "}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":12},"end":{"row":4,"column":13}},"text":":"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":11},"end":{"row":4,"column":12}},"text":"s"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":10},"end":{"row":4,"column":11}},"text":"e"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":9},"end":{"row":4,"column":10}},"text":"t"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":8},"end":{"row":4,"column":9}},"text":"a"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":7},"end":{"row":4,"column":8}},"text":"t"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":6},"end":{"row":4,"column":7}},"text":"s"}}
{"data":{"action":"insertText","range":{"start":{"row":5,"column":0},"end":{"row":5,"column":4}},"text":"    "}}
{"data":{"action":"removeText","range":{"start":{"row":3,"column":4},"end":{"row":3,"column":7}},"text":"// "}}
{"data":{"action":"removeText","range":{"start":{"row":3,"column":20},"end":{"row":4,"column":0}},"text":"\n"}}
{"data":{"action":"removeText","range":{"start":{"row":4,"column":0},"end":{"row":4,"column":4}},"text":"    "}}
{"data":{"action":"removeText","range":{"start":{"row":4,"column":4},"end":{"row":4,"column":21}},"text":"// this is a test"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":20},"end":{"row":4,"column":21}},"text":"t"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":18},"end":{"row":4,"column":19}},"text":"e"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":17},"end":{"row":4,"column":18}},"text":"r"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":15},"end":{"row":4,"column":16}},"text":"a"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":14},"end":{"row":4,"column":15}},"text":" "}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":10},"end":{"row":4,"column":11}},"text":"s"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":12},"end":{"row":4,"column":13}},"text":"s"}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":10},"end":{"row":4,"column":11}},"text":" "}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":4},"end":{"row":4,"column":7}},"text":"// "}}
{"data":{"action":"insertText","range":{"start":{"row":4,"column":0},"end":{"row":4,"column":4}},"text":"    "}}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
ObjectEditor -- logger>>logTextChange
X

Menu
// changed at Tue Nov 18 2014 21:36:56 GMT-0800 (PST) by robertkrahn
this.addScript(function logTextChange(change) {
  this.textString = JSON.stringify(change) + "\n" + this.textString
  // this.textString = Objects.inspect(change, {maxDepth: 4}) + "\n" + this.textString
  
  var code = this.get("exptEditor").textString;
  var ast = lively.ast.acorn.parse(code, {locations: true});
  var scopes = lively.ast.query.scopes(ast);
  var tree = Strings.printTree(scopes,
    function(s) { return code.slice(s.node.start,s.node.end).truncate(45).replace(/\n/g, ""); },
    function(s) { return s.subScopes; })
  tree
  scopes.node.body
  lively.ast.query.
  scopes.subScopes
});
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
<lively.morphic.CodeEditor#E4B84... - logger>
Tag:
run
save
Tests

-- ALL --
logTextChange
Scripts
-
+
-
Connections
+
-- ALL --
all
Text Window
X

Menu
var fsm = {
  create: function(options) {
    options = options || {};
    var fsm = {
      states: options.states || {}
    };
    return fsm;
  }
}
this.get("logger").logTextChange
delete this.get("logger").logTextChange
lively.bindings.connect(this, 'textChange', this.get("logger"), 'logTextChange');
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX