/* Copyright (c) 2008 Alessandro Warth */ // TODO: make returns inside "in" statements work, e.g., function() { in w1 { return arr.printString() } }() // in { ... } -> {if ((_inR = function($world) { ...; return _nothing }()) != _nothing) return _inR} // TODO: for-in, (pre/post)-(in/de)crements, and modified assignments eval(readFile("Worlds_Library")) ometa WJSParser <: BSJSParser { isKeyword :x = ?(x == 'abort') | ?(x == 'world') | ?(x == 'scope') | super('isKeyword', x), primExprHd = "world" -> ['world'] | "scope" -> ['scope'] | "new" "world" -> ['newWorld'] | super('primExprHd'), stmt = "abort" -> ['abort'] | "try" block:tb "else" block:eb -> ['wtry', tb, eb] | "in" expr:w block:b -> ['in', w, b] | super('stmt') } makeFunction = function(fs, body) { return '(function() { var $staticScope = $scope;'+ ' return function($world, $this) {' + ' var $scope = $staticScope.makeChild();' + fs + body + '}})()' } ometa WJSTranslator <: BSJSTranslator { initialize = { self.level = 0 }, fargs = [anything*:fs] -> { var ss = [] fs.each(function(v, i) { ss.push('$scope.decl($world, "' + v + '", arguments[' + (i + 2) + ']);') }) ss.join('') }, trans = ['world'] -> '$world' | ['scope'] -> '$scope' | ['this'] -> '$this' | ['var' :n trans:v] -> ('$scope.decl($world, "' + n + '", ' + v + ')') | ['get' :n] -> ('$scope.get($world, "' + n + '")') | ['set' ['get' :n] trans:v] -> ('$scope.set($world, "' + n + '", ' + v + ')') | ['getp' trans:p trans:x] -> ('$world.get(' + x + ', ' + p + ')') | ['set' ['getp' trans:p trans:x] trans:v] -> ('$world.set(' + x + ', ' + p + ', ' + v + ')') | ['call' trans+:f_and_as] -> ('$world.call(null, ' + f_and_as.join(',') + ')') | ['send' :m trans:r trans*:as] -> ('$world.send(' + r + ', "' + m + '"' + as.map(function(a) { return ',' + a }).join('') + ')') | ['newWorld'] -> ('$world.makeChild()') | ['new' :x trans*:as] -> ('newAndInitialize($world, $scope.get($world, "' + x + '")' + as.map(function(a) { return ',' + a }).join('') + ')') | ['func' fargs:fs {self.level++} trans:body {self.level--}] -> makeFunction(fs, body) | ['abort'] -> 'throw abort' | ['wtry' trans:tb trans:te] -> { var r = '$world.tryElse(function($world){' + tb + '; return nothing},' + 'function($world){' + te + '; return nothing})' self.level > 0 ? 'if (($_ = ' + r + ')!==nothing) return $_' : r } | ['in' trans:w trans:b] -> ('(function($world) {' + b + '})(' + w + ')') | ['arr' trans*:xs] -> ('[' + xs.join(',') + '].toWJSArray()') | ['json' trans*:xs] -> ('({' + xs.join(',') + '}).toWJSObject()') | ~['mset' anything*] ~['forIn' anything*] super('trans') } compileWJS = function(code) { var tree = WJSParser.matchAll(code, "topLevel", undefined, function(m, i) { throw fail.delegated({errorPos: i}) }) return WJSTranslator.match(tree, 'trans') } $scope.decl($world, "eval", function($world, $this, s) { return eval(compileWJS(s)) }) $scope.decl($world, "prompt", function($world, $this, s) { return prompt(s) }) $scope.decl($world, "confirm", function($world, $this, s) { return confirm(s) }) translateCode = compileWJS foo = new object() foo.initialize = function(x) { alert("making a foo, x = " + x) } new foo(1234) foo.aaa = 5 new foo(1234).aaa {x: 1}.x function(x, y) { console.log("before x=" + x + ", y=" + y) try { x = 1 y = 2 console.log("during x=" + x + ", y=" + y) abort } else { console.log("aborted") } console.log("after x=" + x + ", y=" + y) }(1234, 4321) // scopes are 1st class scope.has("z") scope.decl("z", 42) scope.has("z") scope.get("z") scope.set("z", 1234) z // testing bool prototype bool.isBool = true true.isBool ({}).isBool // testing number prototype number.fact = function() { return this === 0 ? 1 : this * (this - 1).fact() } 5.fact() // testing array prototype array.push = function(x) { this.length = this.length + 1 this[this.length - 1] = x return x } array.map = function(f) { var r = [] for (var idx = 0; idx < this.length; idx = idx + 1) r.push(f(this[idx])) return r } array.reduce = function(f, z) { var r = z for (var idx = 0; idx < this.length; idx = idx + 1) r = f(r, this[idx]) return r } [1, 2, 3].reduce(function(x, y) { return x + " " + y }, "") // testing string prototype string.shout = function() { alert("<<< " + this + " >>>") } "hello".shout() gensym = function() { gensym.count = gensym.count + 1 return "_gensm_" + gensym.count } gensym.count = 0 scopeProto.unify = function(x, v) { if (this.has(x) && this.get(x) !== v) abort else return this.decl(x, v) } father = function(x, y, s, k) { try { s.unify(x, "abe"); s.unify(y, "homer"); k(s) } else { try { s.unify(x, "homer"); s.unify(y, "bart"); k(s) } else { try { s.unify(x, "homer"); s.unify(y, "lisa"); k(s) } else {}}} } grandfather = function(x, y, s, k) { try { var z = gensym(); father(x, z, s, function(s) { father(z, y, s, k) }) } else {} } father("a", "b", scope, function() { if (confirm("a=" + a + ", b=" + b + "\n\nmore results?")) abort }) grandfather("c", "d", scope, function() { if (confirm("c=" + c + ", c=" + c + "\n\nmore results?")) abort })