├── cases
├── 8.js
├── 5.js
├── 8.js.wrong
├── c.js
├── 1.js
├── 2.js
├── g.js
├── 4.js
├── f.js
├── a.js
├── 6.js
├── 7.js
├── 9.js
├── 9.js.wrong
├── j.js
├── 3.js
├── k.js
├── l.js
├── m.js
├── b.js
├── b.js.wrong
├── e.js.wrong
├── e.js
├── h.js
├── d.js
├── d.js.wrong
├── h.js.wrong
├── nested.html
├── i.js.wrong
└── i.js
├── hw.html
├── README.mdown
├── jx
└── jexpr.js
/cases/8.js:
--------------------------------------------------------------------------------
1 | let {x: 5} in: (display x)
2 |
--------------------------------------------------------------------------------
/cases/5.js:
--------------------------------------------------------------------------------
1 | let {x: 5} in:
2 | display x
3 |
--------------------------------------------------------------------------------
/cases/8.js.wrong:
--------------------------------------------------------------------------------
1 | let {x: 5} in: display x
2 |
--------------------------------------------------------------------------------
/cases/c.js:
--------------------------------------------------------------------------------
1 | display (length (list 1 2 3))
2 |
--------------------------------------------------------------------------------
/cases/1.js:
--------------------------------------------------------------------------------
1 | display "hello world"
2 | display "been there"
3 |
--------------------------------------------------------------------------------
/cases/2.js:
--------------------------------------------------------------------------------
1 | display "hello world"
2 | display "been there"
3 |
--------------------------------------------------------------------------------
/cases/g.js:
--------------------------------------------------------------------------------
1 | let {x: table {cat: "meow"}} in:
2 | display x.cat
3 |
--------------------------------------------------------------------------------
/cases/4.js:
--------------------------------------------------------------------------------
1 | if false
2 | then: (display "hello")
3 | else: (display "world")
4 |
--------------------------------------------------------------------------------
/cases/f.js:
--------------------------------------------------------------------------------
1 | let {msg: "hello"} in:
2 | display ($_ (_$ msg) 'let world')
3 |
--------------------------------------------------------------------------------
/cases/a.js:
--------------------------------------------------------------------------------
1 | macro hello
2 | lambda: x
3 | body: x.hello
4 | display (hello 3)
5 |
--------------------------------------------------------------------------------
/cases/6.js:
--------------------------------------------------------------------------------
1 | let {x: lambda y body: y} in:
2 | display (x 5)
3 | display "done"
4 |
--------------------------------------------------------------------------------
/cases/7.js:
--------------------------------------------------------------------------------
1 | let {x: lambda y z body: z} in:
2 | display (x 5 6)
3 | display "done"
4 |
--------------------------------------------------------------------------------
/cases/9.js:
--------------------------------------------------------------------------------
1 | display
2 | for {x: from 10 to: 13,
3 | y: from 100 to: 104}
4 | expr: (list "hello" x y)
5 |
--------------------------------------------------------------------------------
/cases/9.js.wrong:
--------------------------------------------------------------------------------
1 | display
2 | for {x: from 10 to: 13,
3 | y: from 100 to: 104}
4 | expr: list "hello" x y
5 |
--------------------------------------------------------------------------------
/cases/j.js:
--------------------------------------------------------------------------------
1 | do
2 | display "hello world using fn"
3 | let {f: fn x to: x.length} in:
4 | display (f "meow")
5 |
--------------------------------------------------------------------------------
/cases/3.js:
--------------------------------------------------------------------------------
1 | do
2 | display "hello world"
3 | let {f: lambda x body: x.length} in:
4 | display (f "meow")
5 |
--------------------------------------------------------------------------------
/cases/k.js:
--------------------------------------------------------------------------------
1 | for { i: from 1 to: 10
2 | , j: from 1 to: 10 when: (fn j to: math j * 2 > i)
3 | }
4 | body:
5 | display (list i j)
6 |
--------------------------------------------------------------------------------
/cases/l.js:
--------------------------------------------------------------------------------
1 | for { i: from 1 to: 10
2 | , j: from 1 to: 10 when: (fn j to: math j * 2 > i and i < 5)
3 | }
4 | body:
5 | display (list i j)
6 |
--------------------------------------------------------------------------------
/cases/m.js:
--------------------------------------------------------------------------------
1 | for { i: from 1 to: 10
2 | , j: from 1 to: 10 when: (fn j to: math (j * 2) > i and (i < 5))
3 | }
4 | body:
5 | display (list i j)
6 |
--------------------------------------------------------------------------------
/cases/b.js:
--------------------------------------------------------------------------------
1 | do
2 | define {x: 5}
3 | define {fn: lambda y body: (table {x: x, y: y})}
4 | define {x: 10}
5 | display (fn 10)
6 | display x
7 |
--------------------------------------------------------------------------------
/cases/b.js.wrong:
--------------------------------------------------------------------------------
1 | do
2 | define {x: 5}
3 | define {fn: lambda y body: table {x: x, y: y}}
4 | define {x: 10}
5 | display (fn 10)
6 | display x
7 |
--------------------------------------------------------------------------------
/cases/e.js.wrong:
--------------------------------------------------------------------------------
1 | macro hello
2 | lambda: expr
3 | body: $_ (display (list "In macro!" (_$$ meow expr.hello)))
4 | where: {meow: $ "bowow"}
5 | hello "macro" "world!"
6 |
--------------------------------------------------------------------------------
/cases/e.js:
--------------------------------------------------------------------------------
1 | macro hello
2 | lambda: expr
3 | body: ($_ (display (list "In macro!" (_$$ meow ($_ (list (_$ expr.hello)))))))
4 | where: {meow: $ "bowow"}
5 | hello "macro" "world!"
6 |
--------------------------------------------------------------------------------
/cases/h.js:
--------------------------------------------------------------------------------
1 | let {greet: lambda msg body:
2 | display "Hello lambda world!"
3 | display msg
4 | display keywords.lockword}
5 | in: (greet "Planett earth rocks!" lockword: "haha!")
6 |
7 |
--------------------------------------------------------------------------------
/cases/d.js:
--------------------------------------------------------------------------------
1 | apply (lambda msg body:
2 | display "hello lambda world ..."
3 | display msg
4 | display keywords)
5 |
6 | args: (list "planet earth rocks!")
7 | keywords: (table (global "cooling ftw!"))
8 |
9 |
--------------------------------------------------------------------------------
/cases/d.js.wrong:
--------------------------------------------------------------------------------
1 | apply (lambda msg body:
2 | display "hello lambda world ..."
3 | display msg
4 | display keywords)
5 |
6 | args: list "planet earth rocks!"
7 | keywords: table (global "cooling ftw!")
8 |
9 |
--------------------------------------------------------------------------------
/cases/h.js.wrong:
--------------------------------------------------------------------------------
1 | let {greet: lambda msg
2 | body: display "Hello lambda world!"
3 | display msg
4 | display keywords.lockword}
5 | in: greet "Planett earth rocks!" lockword: "haha!"
6 |
7 |
--------------------------------------------------------------------------------
/cases/nested.html:
--------------------------------------------------------------------------------
1 |
8 |
9 |
--------------------------------------------------------------------------------
/cases/i.js.wrong:
--------------------------------------------------------------------------------
1 | let {ruler: lambda arg
2 | body: if keywords.double_rule
3 | then: display "==================="
4 | else: display "-------------------"
5 | display arg
6 | , another: "yeow"}
7 | in: ruler (list "An important message!" another)
8 | double_rule: true
9 |
--------------------------------------------------------------------------------
/cases/i.js:
--------------------------------------------------------------------------------
1 | let {ruler: lambda arg body:
2 | if keywords.double_rule
3 | then: (display "===================")
4 | else: (display "-------------------")
5 | display arg
6 | , another: "yeow"}
7 | in:
8 | ruler (list "An important message!" another)
9 | double_rule: true
10 |
--------------------------------------------------------------------------------
/hw.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/README.mdown:
--------------------------------------------------------------------------------
1 | An attempt at using JSON to notate the abstract syntax tree of a language, thus
2 | yielding a language capable of natural macros just like the lisp family.
3 |
4 | The current compiler implementation is written in "stream of thought" style.
5 | Works, but it isn't production code and intended as proof of concept.
6 |
7 | See [the jexpr docco docs](http://srikumarks.github.io/jexpr) for more info.
8 |
9 | The `node.js` script named `jx` can compile jexpr files into Javascript or run
10 | them directly. Run the script with no arguments for info on how to use it.
11 |
12 | The `jexpr.js` file can be script-included in a web page and it will scan and
13 | execute all the script tags with `type="application/x-jexpr"` attribute.
14 |
15 |
--------------------------------------------------------------------------------
/jx:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | J_enable_tests = false;
4 |
5 | var J = require('./jexpr.js')
6 | var fs = require('fs');
7 | var verbose = false;
8 |
9 | if (process.argv.length <= 2) {
10 | process.stderr.write('Usage: jx file1.js file2.js ...\n');
11 | process.stderr.write(' Will run all the code in the given files.\n\n');
12 | process.stderr.write('Usage: jx -C file1.js file2.js ... > out.js\n');
13 | process.stderr.write(' Will compile all the code in the given files\n');
14 | process.stderr.write(' and output the result to stdout.\n\n');
15 | process.stderr.write('Usage: jx -P file1.js file2.js ... > out.js\n');
16 | process.stderr.write(' Will parse all the code in the given files and\n');
17 | process.stderr.write(' write it out as an array of expressions in JSON form.\n\n');
18 | process.stderr.write('Usage: jx file1.js -c file2.js file3.js ...\n');
19 | process.stderr.write(' Will run file1.js, file3.js, and others and \n');
20 | process.stderr.write(' then compile file2.js. Useful to load macros.\n');
21 | process.stderr.write('\nThe "-v" flag turns on verbose dumping of the parsed JSON.\n\n');
22 | } else {
23 | var exprs_to_run = [], exprs_to_compile = [], all_exprs = [];
24 | var compile_next = false, compile_reset = true, parse_only = false;
25 | process.argv.slice(2).forEach(function (f) {
26 | if (f === '-c') {
27 | compile_next = true;
28 | } else if (f === '-C') {
29 | compile_next = true;
30 | compile_reset = false;
31 | } else if (f === '-v') {
32 | verbose = true;
33 | } else if (f === '-P') {
34 | parse_only = true;
35 | } else {
36 | var source = fs.readFileSync(f, 'utf8');
37 | var p = J.parse(source);
38 | var exprs = compile_next ? exprs_to_compile : exprs_to_run;
39 | var expr;
40 | while ((expr = p()) !== undefined) {
41 | if (verbose) {
42 | console.log(JSON.stringify(expr));
43 | }
44 | exprs.push(expr);
45 | all_exprs.push(expr);
46 | }
47 | if (compile_reset) {
48 | compile_next = false;
49 | }
50 | }
51 | })
52 |
53 | if (parse_only) {
54 | process.stdout.write(JSON.stringify(all_exprs));
55 | } else {
56 | var rt = J.runtime();
57 | if (exprs_to_run.length > 0) {
58 | J.eval({do: exprs_to_run}, rt);
59 | }
60 | if (exprs_to_compile.length > 0) {
61 | process.stdout.write(J.compile_to_js({do: exprs_to_compile}));
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/jexpr.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012, Srikumar K. S. (srikumarks.github.com)
2 | //
3 | // Code licensed for use and redistribution with warranties
4 | // (or the lack thereof) as described in the MIT licence.
5 | // License URL: http://www.opensource.org/licenses/MIT
6 |
7 | // This is an attempt at developing a language using JSON objects as containers
8 | // for the AST, similar to how list expressions serve as a representation for
9 | // ASTs in the lisp family of languages. The key idea exploited here is that
10 | // browser based Javascript engines such as V8 always enumerate the keys of an
11 | // object in the same order as they were inserted. Though this is not required
12 | // by ECMAScript, it is considered standard behaviour in browser environments
13 | // and in Node.js too (since it uses V8).
14 | //
15 | // The overall structure of a j-expression is like this -
16 | //
17 | // {operator: [args...],
18 | // keyword1: value1,
19 | // keyword2: value2,
20 | // ...}
21 | //
22 | // .. and we'll call the language "J" here for brevity.
23 | //
24 | // #### Relevant posts
25 | //
26 | // 1. [J-expressions]
27 | // 2. [DSLs using JSON expressions]
28 | //
29 | // [DSLs using JSON expressions]: http://srikumarks.github.com/gyan/2012/04/14/creating-dsls-in-javascript-using-j-expressions/
30 | // [J-expressions]: http://srikumarks.github.com/gyan/2012/04/15/j-expressions/
31 |
32 | var J = (function (enable_tests) {
33 |
34 | // We single out the first key presented in a JSON object as the name of the
35 | // operator.
36 | function operatorName(obj) {
37 | if (obj && obj.constructor === Object) {
38 | for (var k in obj) {
39 | return k;
40 | }
41 | } else {
42 | return undefined;
43 | }
44 | };
45 |
46 | // ## Compilation environments
47 |
48 | // We need an environment structure to remember the scope of bindings
49 | // established as we develop the compiler and as the compiler walks through the
50 | // AST. We start with a simple environment definition with the ability to
51 | // construct an environment with another one as its "parent scope".
52 | var Env = function (base) {
53 | if (base) {
54 | this.base = base;
55 | this.symbols = Object.create(base.symbols);
56 | } else {
57 | this.symbols = {};
58 | }
59 | }
60 |
61 | // We now start with the basic compiler that supports simple object types -
62 | // numbers and booleans. The compiled form of these is simply the JSON
63 | // stringification so that we can insert them into the compiled code directly
64 | // as literals.
65 | function compile_lit(env, expr) {
66 |
67 | if (expr === undefined || expr === null) {
68 | return JSON.stringify(expr);
69 | }
70 |
71 | if (expr.constructor === Number || expr.constructor == Boolean) {
72 | return JSON.stringify(expr);
73 | }
74 |
75 | return undefined;
76 | }
77 |
78 | // Strings are a bit special. We're going to need symbols in our
79 | // language. Since JS doesn't have a separate symbol type, we'll
80 | // just use plain strings as symbols and worry about strings later on.
81 | // This means we're going to need a way to lookup the compile-value of a
82 | // symbol in an environment first. Use a namespace prefix to avoid
83 | // touching the builtin properties.
84 | function lookupSymbol(env, sym) {
85 | return env.symbols['J_' + sym];
86 | }
87 |
88 | // We now add a simple function to define new things into
89 | // a given environment.
90 | function define(env, name, value) {
91 | return env.symbols['J_' + name] = value;
92 | }
93 |
94 | // We can now write our symbol compilation. This looks up
95 | // the value in the environment and just returns it if found.
96 | function compile_sym(env, sym) {
97 | if (sym && sym.constructor === String) {
98 | return lookupSymbol(env, sym);
99 | }
100 |
101 | return undefined;
102 | }
103 |
104 | // A new "variable" in our language will be mapped to a javascript
105 | // variable by attaching a special prefix so that the language
106 | // cannot escape its boundaries. We also use the environment's
107 | // "id" number in the name so that the JS variables associated
108 | // with different environments can be told apart.
109 | var idRx = /^[a-zA-Z_\$][a-zA-Z0-9_\$]*$/;
110 | function varname(env, sym) {
111 | if (!idRx.test(sym)) {
112 | throw "Bad identifier!";
113 | }
114 | return 'var$' + env.id + '$' + sym;
115 | }
116 |
117 | function varnames(env, syms) {
118 | return syms.map(function (sym) {
119 | return varname(env, sym);
120 | });
121 | }
122 |
123 | function newvar(env, sym) {
124 | return define(env, sym, varname(env, sym));
125 | }
126 |
127 | function newvars(env, syms) {
128 | if (typeof syms === 'string') {
129 | return [newvar(env, syms)];
130 | } else {
131 | return syms.map(function (sym) {
132 | return newvar(env, sym);
133 | });
134 | }
135 | };
136 |
137 | // Oops. We haven't defined an environment's ID. Let's patch
138 | // Env to add that.
139 | function patch(oldEnv, change) {
140 | function NewEnv(base) {
141 | oldEnv.call(this, base);
142 | change.call(this, base);
143 | }
144 |
145 | NewEnv.prototype = oldEnv.prototype;
146 | return NewEnv;
147 | }
148 |
149 | var globallyUniqueEnvID = 1;
150 | Env = patch(Env, function (base) {
151 | this.id = (globallyUniqueEnvID++);
152 | });
153 |
154 | // Lets also add some options that we can store and
155 | // inherit over the Env chain.
156 | Env = patch(Env, function (base) {
157 | this.options = (base ? Object.create(base.options) : {});
158 | });
159 |
160 | // We're now ready to process our first J-expression. We treat
161 | // the first key as the symbol standing for an operator, fetch
162 | // the function that implements the operator and just call it.
163 | // Note that if the looked up value is a function, it is really
164 | // a "macro" because we're writing a *compiler*. Actual value
165 | // lookup will yield a string which we can use as a JS expression
166 | // in the compiled result directly.
167 | // {operator: [arguments...], keyword1: value1, ...}
168 | function compile_jexpr(env, jexpr) {
169 | if (jexpr && jexpr.constructor === Object) {
170 | var op = lookupSymbol(env, operatorName(jexpr));
171 | if (op && op.constructor === Function) {
172 | return op(env, jexpr); /* We have a native implementation available.
173 | * We pass the entire body of the expression
174 | * to it without evaluating anything else.
175 | */
176 | }
177 |
178 | if (op && op.constructor === String) {
179 | return compile_apply(env, op, jexpr); /* This is an already compiled value. So just
180 | * treat it as a function and apply it.
181 | */
182 | }
183 |
184 | if (!op && env.options.unsafe) {
185 | // Treat it as a globally available thingie.
186 | return compile_apply(env, operatorName(jexpr), jexpr);
187 | }
188 | } else if (jexpr && jexpr.constructor === Array) {
189 | // This is s-expression fallback case where the operator
190 | // expression is not a string.
191 | var kw = jexpr.keywords;
192 | var jexpr2 = {};
193 | jexpr2['sexpr'] = jexpr.slice(1);
194 | if (kw) {
195 | Object.keys(kw).forEach(function (k) {
196 | jexpr2[k] = kw[k];
197 | });
198 | }
199 | return compile_apply(env, compile(env, jexpr[0]), jexpr2);
200 | }
201 |
202 | return undefined;
203 | }
204 |
205 | // We now need to build our whole compilation function so
206 | // that we can just call it to compile any j-expression
207 | // or primitive type.
208 | function compile(env, expr) {
209 | return compile_jexpr(env, expr)
210 | || compile_sym(env, expr)
211 | || compile_lit(env, expr);
212 | }
213 |
214 | // To help ourselves a bit, let's define a mapping utility
215 | // that applies a two-argument "macro-like" function to
216 | // an array of expressions.
217 | function map(env, fn, exprs) {
218 | return exprs.map(function (expr) {
219 | return fn(env, expr);
220 | });
221 | }
222 |
223 | // The "arguments" of an operator are provided as an array value.
224 | // Each element is compiled in turn and the result used as the
225 | // argument-list of the compiled javascript function.
226 | function compile_args(env, argv) {
227 | if (argv && argv.constructor === Array) {
228 | return map(env, compile_args, argv).join(',');
229 | } else {
230 | // Compile it as a single expression.
231 | return compile(env, argv);
232 | }
233 | }
234 |
235 | // Now we are ready to write the compile_apply, which will
236 | // apply a compiled function by symbol reference to a given
237 | // arguments list.
238 | function compile_apply(env, op, jexpr) {
239 | return op + '(' + compile_args(env, jexpr[operatorName(jexpr)]) + ')';
240 | }
241 |
242 |
243 | // ## Primitives
244 | //
245 | // Ok, so far we have not implemented any primitives. Our first one
246 | // is going to be a mechanism for expressions to return literal JSON
247 | // objects. This is the analog of "quote" in Scheme and we'll use
248 | // the succinct "$" symbol as the key of a jexpr to represent quoted
249 | // forms. We'll insert these primitives into a "primitives" environment.
250 | var Prim = new Env;
251 |
252 | define(Prim, '$', function (env, expr) {
253 | return JSON.stringify(expr.$);
254 | });
255 |
256 | // And now for an ultra simple "display" implementation.
257 | // After all, how are we going to write a "hello world" program
258 | // without this one!
259 | define(Prim, 'display', function (env, expr) {
260 | return '(console.log(' + compile(env, expr.display) + '), null)';
261 | });
262 |
263 | // We're now ready to do a "hello world". But first some helper stuff.
264 | // We're going to be making new environments. So let's make a helper
265 | // method on Env to make a new derived environment.
266 | function subenv(env) {
267 | return new Env(env);
268 | }
269 |
270 | // HELLO WORLD!
271 | if (enable_tests) {
272 | eval(compile(subenv(Prim), {display: {$: "Hello world!"}}));
273 | }
274 |
275 | // Let's wrap that little piece of code into a "run" method
276 | // and insert it into the environment so that programs can
277 | // be run within environments.
278 |
279 | // "run" will run the expressions in a new child environment
280 | // without affecting the target environment.
281 | Env.prototype.run = function () {
282 | var env = subenv(this);
283 | var result;
284 | Array.prototype.forEach.call(arguments, function (expr) {
285 | result = eval(compile(env, expr));
286 | });
287 | return result;
288 | };
289 |
290 |
291 | // Now lets implement some more primitives!
292 | // We'll do the useful "list" macro which will take
293 | // a bunch of arguments and produce an list (a JS array)
294 | // out of them. This has to be a macro because we're
295 | // constructing an array for use at *runtime*.
296 | define(Prim, 'list', function (env, expr) {
297 | if (expr.list.constructor === Array) {
298 | return '['
299 | + expr.list.map(function (e) {
300 | return compile(env, e);
301 | }).join(',')
302 | + ']';
303 | } else {
304 | return '[' + compile(env, expr.list) + ']';
305 | }
306 | });
307 |
308 | // ... and list length
309 | define(Prim, 'length', '(function (x) { return x.length; })');
310 |
311 | if (enable_tests) {
312 | Prim.run({display: {length: [{list: [1,2,3]}]}});
313 | }
314 |
315 | // We'll also put in a macro for constructing tables.
316 | // This has to be a macro because we're going to have
317 | // to evaluate the value fields of the given object.
318 | //
319 | // {table: {x: 2, y: {$: "why?"}}}
320 | //
321 | // should produce what you think it should.
322 | define(Prim, 'table', function (env, expr) {
323 | var keys = Object.keys(expr.table);
324 | return '{'
325 | + keys.map(function (k) {
326 | return JSON.stringify(k) + ':' + compile(env, expr.table[k]);
327 | }).join(',')
328 | + '}';
329 | });
330 |
331 | // ### Let there be lambda
332 | //
333 | // Now for the BIG BOY! The syntax we use for lambda is like this -
334 | //
335 | // {lambda: ["arg1", "arg2", ...],
336 | // body: expr|[expr1, expr2, ..., exprN]}
337 | //
338 | // We turn that into a JS function like this -
339 | //
340 | // function (arg1, arg2, ...) {
341 | // var keywords = this;
342 | // return (expr1, expr2, ... exprN);
343 | // }
344 | //
345 | // We also make "this" available as the special symbol 'keywords'
346 | // with the intention of passing in optional arguments through 'this'.
347 | define(Prim, 'lambda', function (env, expr) {
348 | var env2 = subenv(env);
349 | return '(function ('
350 | + newvars(env2, expr.lambda).join(',')
351 | + ') {'
352 | + 'var ' + newvar(env2, 'keywords') + ' = this;'
353 | + 'return (' + compile_args(env2, expr.body) + ');})';
354 | });
355 |
356 | // Alias 'lambda:body:' as 'fn:to:'.
357 | define(Prim, 'fn', function (env, expr) {
358 | var k = Object.keys(expr);
359 | k.shift();
360 | var expr2 = {lambda: expr.fn};
361 | k.forEach(function (k) {
362 | if (k === 'to') {
363 | expr2['body'] = expr[k];
364 | } else {
365 | expr2[k] = expr[k];
366 | }
367 | });
368 | return lookupSymbol(Prim, 'lambda')(env, expr2);
369 | });
370 |
371 | // ### Optional keyword arguments
372 |
373 | // That was easy! ... but the lambda is unable to make
374 | // use of optional keyword arguments yet and that would be a real waste.
375 | // To support that, at call time, we'll pass the compiled version of
376 | // the call expression body to the lambda as a table so that it can
377 | // access the arguments other than the args array through the local
378 | // "keywords" symbol.
379 |
380 | // First, we need to compile the entire expression as a value.
381 | function compile_exprval(env, expr, keys) {
382 | return '{'
383 | + keys.map(function (k) {
384 | return JSON.stringify(k)
385 | + ':'
386 | + (compile_array(env, expr[k]) || compile(env, expr[k]));
387 | }).join(',')
388 | + '}';
389 | }
390 |
391 | // Now we need to patch compile_apply to check for the presence
392 | // of keywords and if so pass it as the "this" part of a call.
393 | var compile_apply = (function (prevCompileApply) {
394 | return function (env, op, jexpr) {
395 | var keys = Object.keys(jexpr);
396 | if (keys.length === 1) {
397 | return prevCompileApply(env, op, jexpr); // Avoid the overhead of a ".call"
398 | } else {
399 | var opname = operatorName(jexpr);
400 | keys.shift(); // Drop the operator.
401 | return op
402 | + '.call('
403 | + compile_exprval(env, jexpr, keys)
404 | + ','
405 | + compile_args(env, jexpr[opname])
406 | + ')';
407 | }
408 | };
409 | }(compile_apply));
410 |
411 |
412 | // This just compiles the parts of the array and wraps it with the
413 | // array constructor.
414 | function compile_array(env, arr) {
415 | if (arr && arr.constructor === Array) {
416 | return '[' + compile_args(env, arr) + ']';
417 | }
418 |
419 | return undefined;
420 | }
421 |
422 | // And to *use* lambda, we're going to need apply.
423 | //
424 | // {apply: funval, args: listval, keywords: tableval}
425 | define(Prim, 'apply', function (env, expr) {
426 | return compile(env, expr.apply)
427 | + '.apply('
428 | + (expr.keywords ? compile(env, expr.keywords) : 'null')
429 | + ','
430 | + compile(env, expr.args)
431 | + ')';
432 | });
433 |
434 | // call func arg1 arg2 ... kw1: val1 kw2: val2 ...
435 | define(Prim, 'call', function (env, expr) {
436 | var op, argv, keywords;
437 | keywords = Object.keys(expr);
438 | keywords.unshift(); // Drop 'call'.
439 | var keyvals = {};
440 | keyvals.call = expr.call.constructor === Array ? expr.call.slice(1) : [];
441 | keywords.forEach(function (k) { keyvals[k] = expr[k]; });
442 | var op = '(' + compile_args(env, expr.call.constructor === Array ? expr.call[0] : expr.call) + ')';
443 | return compile_apply(env, op, keywords);
444 | });
445 |
446 | // Lets now try a lambda hello world.
447 | if (enable_tests) {
448 | Prim.run({apply: {lambda: ["msg"],
449 | body: [
450 | {display: {$: "Hello lambda world ..."}},
451 | {display: "msg"},
452 | {display: "keywords"}
453 | ]},
454 | args: {list: [{$: "planet earth rocks!"}]},
455 | keywords: {table: {global: {$: "cooling ftw!"}}}});
456 | }
457 |
458 |
459 | // ### "where" clauses
460 | //
461 | // Now let's add something "interesting" to lambda
462 | // - a "where" clause. The idea is that whenever we have an extra
463 | // "where: {key1: val1, key2: val2,..}" entry in a j-expression,
464 | // we make those keys available like local variables within the
465 | // scope of the expression. Let's generalize this feature first.
466 | //
467 | // What we do is to turn {...where: {x: val1, y: val2} ...}
468 | // as a function wrapper like -
469 | //
470 | // (function (x, y) {
471 | // ..expr..
472 | // }(val1, val2))
473 | function whereClause(env, expr, where, macro) {
474 | if (!where) {
475 | return macro(env, expr);
476 | }
477 |
478 | var whereEnv = subenv(env);
479 | var whereVars = Object.keys(where);
480 |
481 | return '(function (' + newvars(whereEnv, whereVars) + ') {'
482 | + 'return (' + macro(whereEnv, expr) + ');}'
483 | + '('
484 | + whereVars.map(function (v) {
485 | return '('+compile_args(env, where[v])+')';
486 | }).join(',')
487 | + '))';
488 | }
489 |
490 | // Now we can add where clause support to lambda.
491 | define(Prim, 'lambda', (function (oldLambda) {
492 | return function (env, expr) {
493 | return whereClause(env, expr, expr.where, oldLambda);
494 | };
495 | }(lookupSymbol(Prim, 'lambda'))));
496 |
497 | // ### Macros
498 | //
499 | // Now we up the game a bit and define the ability to
500 | // write macros. We've already been writing macros,
501 | // so we just need to expose that bit of functionality
502 | // to the language itself. Macros are just lambdas that
503 | // take the entire expression as a single argument
504 | // and return an expression to be used instead.
505 | //
506 | // {macro: "name",
507 | // lambda: ["expr"],
508 | // body: ...,
509 | // where: ...}
510 | define(Prim, 'macro', function (env, expr) {
511 | var macrodefn = eval(lookupSymbol(env, 'lambda')(env, expr));
512 | define(env, expr.macro, function (env, expr) {
513 | var expn = macrodefn(expr);
514 | return compile(env, expn);
515 | });
516 | return 'undefined';
517 | });
518 |
519 | // Woot! We have macros! ... but we can't even write a hello world
520 | // with macros now because we don't have a proper way to construct object
521 | // literals in our language. We can use 'list' and 'table', but yuck!
522 | // we need a quasiquoter!
523 | //
524 | // {$_: {_$: } ...}
525 | //
526 | // We first write a "$_" macro that will quasi quote. We make the
527 | // unquoting mechanism generic by putting a table of unquoters for
528 | // the quasi quoter to look for, right into the environment.
529 | function AddUnquoters(base) {
530 | this.unquoters = base ? Object.create(base.unquoters) : {};
531 | }
532 | Env = patch(Env, AddUnquoters);
533 | AddUnquoters.call(Prim, Prim.base);
534 |
535 | function quasiQuote(env, expr) {
536 | if (expr && expr.constructor === Array) { // Array literal.
537 | return '['
538 | + map(env, quasiQuote, expr).join(',')
539 | + ']';
540 | }
541 |
542 | if (expr && expr.constructor === Object) { // Object literal ...
543 | var unquoter = env.unquoters[operatorName(expr)];
544 | if (unquoter) { // ... but maybe an unquoter here?
545 | return unquoter(env, expr);
546 | } else {
547 | return '{'
548 | + Object.keys(expr).map(function (k) {
549 | return JSON.stringify(k) + ':' + quasiQuote(env, expr[k]);
550 | }).join(',')
551 | + '}';
552 | }
553 | }
554 |
555 | return JSON.stringify(expr); // else literal.
556 | }
557 |
558 | // Quasiquote operator
559 | define(Prim, '$_', function (env, expr) {
560 | return quasiQuote(env, expr.$_);
561 | });
562 |
563 | // Now we add one unquoter '_$'.
564 | Prim.unquoters['_$'] = function (env, expr) {
565 | return compile(env, expr._$);
566 | };
567 |
568 | // Unquote splice is simple enough as well.
569 | // Beware that it can only be used sensibly
570 | // when expanding arrays.
571 | Prim.unquoters['_$$'] = function (env, expr) {
572 | return compile_args(env, expr._$$);
573 | };
574 |
575 | // Hooray! We can now do a macro hello world!
576 | if (enable_tests) {
577 | Prim.run({macro: "hello",
578 | lambda: ["expr"],
579 | body: [{$_: {display: {$: ["In macro!", {_$$: ["meow", "expr"]}]}}}],
580 | where: {meow: {$: "bowow"}}
581 | },
582 | {hello: ["macro", "world!"]});
583 | }
584 |
585 | // ## Going to town!
586 | //
587 | // Now we go to town and add all sorts of bells and whistles.
588 |
589 | // ### let:in:
590 | // First up is a variant on the "where" clause - the "let:in:".
591 | //
592 | // {let: {x: blah, y: bling}, in: expr|[expr1, expr2, ...]}
593 | define(Prim, 'let', function (env, expr) {
594 | return whereClause(env, expr, expr.let, function (envw, expr) {
595 | return compile_args(envw, expr.in);
596 | });
597 | });
598 |
599 | if (enable_tests) {
600 | Prim.run({let: {msg: {$: "hello"}},
601 | in: [{display: {$_: [{_$: "msg"}, "let world"]}}]});
602 | }
603 |
604 | // ### if:then:else:
605 | // {if: cond, then: expr1, else: expr2}
606 | define(Prim, 'if', function (env, expr) {
607 | return '(' + compile(env, expr.if)
608 | + '?' + compile(env, expr.then)
609 | + ':' + compile(env, expr.else)
610 | + ')';
611 | });
612 |
613 |
614 | // ### Generators
615 | // Since JS doesn't support tail call elimination, we need some
616 | // way to loop. For that, it is useful to have generators like
617 | // in python - basically functions that you can call repeatedly
618 | // to get a sequence of values. Our protocol will be that the
619 | // generator is considered to end when the function returns
620 | // 'undefined', and we can pass in a bool value of 'true' to
621 | // reset the generator.
622 |
623 | // {from: ix1, to: ix2, step: dix}
624 | // Usual defaults apply.
625 | define(Prim, 'from', function (env, body) {
626 | function iterator(comp) {
627 | return '(function (reset) {'
628 | + 'if (reset) {i = from + step; return from;}\n'
629 | + 'var result = i;'
630 | + 'return (i ' + comp + ' to ? ((i += step), result) : undefined);})';
631 | }
632 |
633 | return '((function (from, to, step) {var i = from; '
634 | + 'if (to === undefined) {'
635 | + 'to = from + step * 1e16;'
636 | + '}\n'
637 | + 'return (step > 0 ?' + iterator('<') + ':' + iterator('>') + ');})('
638 | + compile(env, body.from) + ','
639 | + (body.to ? compile(env, body.to) : 'undefined') + ','
640 | + (body.step ? compile(env, body.step) : '1')
641 | + '))';
642 | });
643 |
644 | // {in: list, from: ix1, to: ix2, step: dix}
645 | // Similar to from: but steps through array.
646 | define(Prim, 'in', function (env, body) {
647 | function iterator(comp) {
648 | return '(function (reset) {'
649 | + 'if (reset) {i = from + step; return arr[from];}\n'
650 | + 'var result = arr[i];'
651 | + 'return (i ' + comp + ' to ? ((i += step), result) : undefined);})';
652 | }
653 |
654 | return '((function (arr, from, to, step) {var i = from; '
655 | + 'if (to === undefined) {'
656 | + 'to = (step > 0 ? arr.length : -1);'
657 | + '}\n'
658 | + 'return (step > 0 ?' + iterator('<') + ':' + iterator('>') + ');})('
659 | + compile(env, body.in) + ','
660 | + compile(env, body.from) + ','
661 | + (body.to ? compile(env, body.to) : 'undefined') + ','
662 | + (body.step ? compile(env, body.step) : '1')
663 | + '))';
664 | });
665 |
666 | // ### Looping using for:
667 | //
668 | // {for: {x: gen1, y: gen2,...},
669 | // when: cond,
670 | // expr: value|[expr1, expr2, ...],
671 | // where: {...}}
672 | // {for: {x: gen1, y: gen2,...},
673 | // when: cond,
674 | // body: stmt|[stmt1, stmt2,...],
675 | // where: {...}}
676 | //
677 | // The "expr" version produces an array with those values, whereas the "body"
678 | // and "dosync" versions are for side effects only. An extra "sync: true|false"
679 | // keyword can be specified to indicate whether only synchronous computations
680 | // are being done within - i.e. whether any closures are being created within
681 | // the body of the loop that warrants wrapping the body in a function. "sync:"
682 | // defaults to "false" so it is always safe in the default case.
683 | //
684 | // TODO: Optimize away the use of generators for the simple integer iteration
685 | // cases.
686 | //
687 | define(Prim, 'for', function (env, expr) {
688 | var numForms = (expr.expr ? 1 : 0) + (expr.body ? 1 : 0) + (expr.dosync ? 1 : 0);
689 | if (numForms !== 1) {
690 | throw new Error('for: Only one of expr: body: or dosync: can be specified.');
691 | }
692 |
693 | return whereClause(env, expr, expr.where, function (env, expr) {
694 | var env2 = subenv(env);
695 | var envb = subenv(env2);
696 | var iters = Object.keys(expr.for);
697 | return '(function () {'
698 | + iters.map(function (ivar) {
699 | var v = newvar(env2, ivar);
700 | var gen_v = 'gen_' + v; /* Use an extra "gen_" prefix
701 | * for variables that hold
702 | * generators.
703 | */
704 |
705 | return 'var ' + v + ', ' + gen_v + ' = (' + compile_args(env2, expr.for[ivar]) + ');';
706 | }).join('')
707 | + (expr.expr ? 'var __result = [];' : '')
708 | // No need to wrap into a function if calculating expression.
709 | + (expr.sync ? '' : ('\nfunction __body('
710 | + newvars(envb, iters).join(',')
711 | + ') {'
712 | + (expr.expr
713 | ? ('__result.push((' + compile_args(envb, expr.expr) + '))')
714 | : ('(' + compile_args(envb, expr.body) + ')'))
715 | + '}\n'))
716 | + iters.map(function (ivar) {
717 | var v = varname(env2, ivar);
718 | var gen_v = 'gen_' + v;
719 | return '\nfor(' + v + ' = ' + gen_v + '(true);'
720 | + v + ' !== undefined; '
721 | + v + ' = ' + gen_v + '()) {';
722 | }).join('')
723 | + (expr.when
724 | ? ('if (' + compile_args(env2, expr.when) + ') {')
725 | : '')
726 | + (expr.sync
727 | ? (expr.expr
728 | ? ('__result.push((' + compile_args(env2, expr.expr) + '))')
729 | : ('(' + compile_args(env2, expr.body) + ')'))
730 | : ('__body(' + varnames(env2, iters).join(',') + ');'))
731 | + (expr.when ? '}' : '')
732 | + iters.map(function (ivar) { return '\n}'; }).join('')
733 | + (expr.expr ? '\nreturn __result;' : '')
734 | + '}())';
735 | });
736 | });
737 |
738 | if (enable_tests) {
739 | Prim.run({for: {x: {from: 1, to: 4},
740 | y: {from: 100, to: 104}},
741 | body: [{display: {$_: [{_$: "x"}, {_$: "y"}]}}]});
742 | }
743 |
744 | // ### Let's support some math as well.
745 | // {expr: "x + y", where: {x: val1, y: val2}}
746 | // The expression can only see the variables in the where clause.
747 | // UNSAFE!
748 | define(Prim, 'expr', function (env, expr) {
749 | if (!env.options.unsafe) {
750 | throw "Unsafe expression! " + JSON.stringify(expr);
751 | }
752 | if (expr.where) {
753 | var vars = Object.keys(expr.where);
754 | return '(function (' + vars.join(',') + ') {'
755 | + 'return (' + expr.expr + ');}'
756 | + '('
757 | + vars.map(function (v) { return compile(env, expr.where[v]); }).join(',')
758 | + '))';
759 | } else {
760 | return '(' + expr.expr + ')';
761 | }
762 | });
763 |
764 | // ### Some higher order functions?
765 |
766 | // {map: fn, list: listval}
767 | define(Prim, 'map', function (env, expr) {
768 | return '(' + compile(env, expr.list) + '.map(' + compile(env, expr.map) + '))';
769 | });
770 |
771 | // {reduce: fn, list: listval, init: value}
772 | define(Prim, 'reduce', function (env, expr) {
773 | return '(' + compile(env, expr.list) + '.reduce('
774 | + compile(env, expr.reduce) + ', '
775 | + compile(env, expr.init)
776 | + '))';
777 | });
778 |
779 | // {filter: fn, list: listval}
780 | define(Prim, 'filter', function (env, expr) {
781 | return '(' + compile(env, expr.list) + '.filter(' + compile(env, expr.filter) + '))';
782 | });
783 |
784 | // ### Dot notation
785 | // It is useful to refer to object parts directly using
786 | // the dot notation. Just change lookupSymbol to directly
787 | // support it.
788 | lookupSymbol = (function (lookup) {
789 | var forbiddenProperties = {};
790 | return function (env, sym) {
791 | var parts = sym.split('.');
792 | if (parts.length === 1) {
793 | return lookup(env, sym);
794 | } else {
795 | parts[0] = lookup(env, parts[0]);
796 | parts.forEach(function (p,i) {
797 | if (i > 0) {
798 | if (forbiddenProperties[p]) {
799 | throw "Forbidden javascript property '" + p + "' accessed!";
800 | }
801 | }
802 | });
803 | if (parts[0]) {
804 | return parts.join('.');
805 | } else {
806 | return undefined;
807 | }
808 | }
809 | };
810 | }(lookupSymbol));
811 |
812 | if (enable_tests) {
813 | Prim.run({let: {x: {table: {cat: {$: "meow"}}}},
814 | in: {display: "x.cat"}});
815 |
816 | // Try the lambda example again with dot notation access.
817 | // Lets now try a lambda hello world.
818 | Prim.run({let: {greet: {lambda: ["msg"],
819 | body: [
820 | {display: {$: "Hello lambda world ..."}},
821 | {display: "msg"},
822 | {display: "keywords.lockword"}
823 | ]}},
824 | in: [{greet: [{$: "Planet earth rocks!"}],
825 | lockword: {$: "haha!"}}]});
826 | }
827 |
828 | // ## Defines and blocks
829 | // It will certainly be convenient to be able to write do blocks
830 | // for walking through steps and introduce definitions along the way,
831 | // process them etc. A simple macro for that would work on --
832 | //
833 | // {do: [stmt1, stmt2, ...],
834 | // where: {...}}
835 | //
836 | // and allow define statements in the mix, like this -
837 | //
838 | // {define: {name1: value1, name2, value2,...}}
839 | //
840 | // We translate such a "do" block into a
841 | // (function () {...}())
842 | // form.
843 | define(Prim, 'do', function (env, expr) {
844 | return whereClause(env, expr, expr.where, function (env, expr) {
845 | var result = '(function () {';
846 | var stmts = expr.do;
847 | if (stmts.constructor !== Array) {
848 | stmts = [stmts];
849 | }
850 |
851 | stmts.forEach(function (stmt, i) {
852 | if (stmt && operatorName(stmt) === 'define') {
853 | env = subenv(env); /* It is a define statement. Make a new environment.
854 | * This is an important step to ensure that new
855 | * definitions don't override older ones.
856 | */
857 |
858 | Object.keys(stmt.define).forEach(function (varname) {
859 | result += 'var ' + newvar(env, varname) + ' = ';
860 | result += compile(env, stmt.define[varname]) + ';';
861 | });
862 | } else {
863 | result += (i+1 < expr.do.length ? '' : 'return ')
864 | + compile(env, stmt) + ';';
865 | }
866 | });
867 | return result + '}())';
868 | });
869 | });
870 |
871 | if (enable_tests) {
872 | Prim.run({do: [
873 | {define: {x: 5}},
874 | {define: {fn: {lambda: ["y"], body: [{table: {x: "x", y: "y"}}]}}},
875 | {define: {x: 10}},
876 | {display: {fn: [10]}},
877 | {display: "x"}
878 | ]});
879 | }
880 |
881 | // ### Accessors
882 | // We don't have any accessor functions for working with
883 | // object and array properties yet. Let's add a general purpose
884 | // "get" and "put".
885 |
886 | // {get: [obj, key1, key2, ...]}
887 | define(Prim, 'get', function (env, expr) {
888 | return expr.get.map(function (e, i) {
889 | var ce = compile(env, e);
890 | return (i > 0 ? ('['+ce+']') : ce);
891 | }).join('');
892 | });
893 |
894 | // {put: [obj, key1, key2, ...], value: val}
895 | define(Prim, 'put', function (env, expr) {
896 | if (expr.put.constructor === String) {
897 | return '(' + compile(env, expr.put) + ' = ' + compile(env, expr.value) + ')';
898 | } else if (expr.put.constructor === Array) {
899 | return '('
900 | + lookupSymbol(env, 'get')(env, expr.put)
901 | + ' = '
902 | + compile(env, expr.value)
903 | + ')';
904 | }
905 | });
906 |
907 | // ### Resolving power differences
908 | //
909 | // There is a asymmetry between lambda and macro that is uncomfortable.
910 | // It is that using a lambda always requires its arguments to be
911 | // wrapped into an array (other than keywords) whereas macros are able
912 | // to work with free forms better. Ideally, they shouldn't have differences
913 | // in form at usage time and should be able to work with all forms.
914 | // One simple solution to this is to auto-promote single non-array
915 | // arguments into one-element arrays at call time. We patch compile_apply
916 | // to resolve this.
917 | //
918 | // With this patch, you can have the following lambda -
919 | //
920 | // {let: {ruler: {lambda: ["arg"],
921 | // body: [{if: "keywords.double_rule",
922 | // then: {display: {$: "================="}}
923 | // else: {display: {$: "-----------------"}}},
924 | // {display: "arg"}]}}
925 | // ...}
926 | //
927 | // which can be called like this -
928 | //
929 | // {ruler: {$: "An important message"}, double_rule: true}
930 | //
931 | // and "applied" like this -
932 | //
933 | // {apply: "ruler",
934 | // args: {list: [{$: "An important message"}]},
935 | // keywords: {table: {double_rule: true}}}
936 | //
937 | var compile_apply = (function (prevCompileApply) {
938 | return function (env, op, jexpr) {
939 | var opname = operatorName(jexpr);
940 | var head = jexpr[opname];
941 | if (head && head.constructor === Array) {
942 | return prevCompileApply(env, op, jexpr); // Safe. Old behaviour applies.
943 | } else {
944 | jexpr[opname] = [jexpr[opname]]; /* Transform the main argument into a
945 | * one-element array.
946 | * HACK: We hack this by destructively modifying
947 | * jexpr since the next time around we won't then
948 | * get into this branch.
949 | */
950 | return prevCompileApply(env, op, jexpr);
951 | }
952 | };
953 | }(compile_apply));
954 |
955 | if (enable_tests) {
956 | Prim.run({let: {ruler: {lambda: ["arg"],
957 | body: [{if: "keywords.double_rule",
958 | then: {display: {$: "======================="}},
959 | else: {display: {$: "-----------------------"}}},
960 | {display: "arg"}]}},
961 | in: [{ruler: {$: "An important message!"}, double_rule: true}]});
962 | }
963 |
964 | // This uniformity lets us turn 'display' into a function much more simply!
965 | define(Prim, 'display', 'console.log');
966 |
967 | // Can we turn map/reduce/filter into functions as well?
968 | // This looks possible, but I'm not sure about the resulting
969 | // efficiency, so I'll leave them as macros for now and leave
970 | // it to YOU to figure that out.
971 | //
972 | // define(Prim, 'map', '(function (fn) { return this.list.map(fn); })');
973 | // define(Prim, 'reduce', '(function (fn) { return this.list.reduce(fn, this.init); })');
974 | // define(Prim, 'filter', '(function (fn) { return this.list.filter(fn); })');
975 | //
976 | // Many others that we've written as macros should similarly be
977 | // expressed as functions .. except for such runtime performance considerations.
978 | // The disadvantage to how we've been doing this up to here, is
979 | // that we cannot use the macros with "apply" in a program. That's
980 | // a pretty BIG disadvantage, but I'm waving my hands and saying
981 | // "you can always wrap a lambda around it" :)
982 | //
983 | // Have fun!
984 |
985 |
986 | // ## A runtime environment?
987 | // So far, we don't have the notion of a runtime and all "functions"
988 | // are actually macros and all is not well in this world just yet.
989 | // We need some way to provide an environment that exposes symbol
990 | // bindings to some piece of compiled code that we then evaluate
991 | // using eval().
992 | //
993 | // We use a very simple model of a language runtime - which is a
994 | // function that takes in a piece of compiled code and evaluates
995 | // it using eval! The function is free to introduce new bindings
996 | // in its local environment which then become accessible to eval.
997 | // In other words, we just treat "eval" itself as a runtime.
998 | //
999 | // Here is a sample runtime that redefines "map", "reduce"
1000 | // and "filter" as functions instead of the macros that we defined
1001 | // them to be. What is returned from a call to the runtime is
1002 | // a compiled Javascript function, which when you call will result
1003 | // in the expression being evaluated. This returned function is
1004 | // of the form -
1005 | // function (param) { return something; }
1006 | // and you can pass in any object for the "param". The expression
1007 | // you supply will be able to safely access this object as the
1008 | // direct symbol "param". If you omit this argument, then accessing
1009 | // "param" in your expression will result in "undefined".
1010 | //
1011 | // Take a look at the sample function definitions. They access
1012 | // the regular arguments through the usual JS arguments and access
1013 | // the keyword argument provided through "this".
1014 | function hofRT(parentEnv, expr) {
1015 |
1016 | var defs = {
1017 | map: function (fn) {
1018 | return this.list.map(fn);
1019 | },
1020 |
1021 | reduce: function (fn) {
1022 | return this.list.reduce(fn, this.init);
1023 | },
1024 |
1025 | filter: function (fn) {
1026 | return this.list.filter(fn);
1027 | }
1028 | };
1029 |
1030 | var env = subenv(parentEnv);
1031 |
1032 | Object.keys(defs).forEach(function (fn) {
1033 | define(env, fn, "__runtime__." + fn);
1034 | });
1035 |
1036 | return eval('(function (__runtime__) { return (function (' + newvar(env, 'param') + ') { '
1037 | + 'return (' + compile(env, expr) + ');'
1038 | + '}); })')(defs);
1039 | }
1040 |
1041 | if (enable_tests) {
1042 | console.log("Testing map function in hofRT..");
1043 | console.log(hofRT(Prim, {map: {lambda: ["x"], body: {table: {x: "x"}}},
1044 | list: {list: [1,2,3]}})());
1045 | }
1046 |
1047 | // The pattern expressed in hofRT can be encapsulated as a generic thing where
1048 | // you have a "runtime maker" function to which you pass in an object
1049 | // containing the definitions you want to make visible when running the
1050 | // code and you get back a function that can run expressions with those
1051 | // definitions. In this case, we make it so that calling the returned
1052 | // runtime function with an expression does not evaluate it like eval
1053 | // does, but compiles it and returns the compiled result as a function
1054 | // that you can then call as many times as you want.
1055 | //
1056 | // So the calling sequence goes like this --
1057 | //
1058 | // var rt = J.runtime({...definitions...});
1059 | // var proc = rt({...jexpr...});
1060 | // proc(param1);
1061 | // proc(param2);
1062 | // ...
1063 | //
1064 | function runtime(env, definitions) {
1065 |
1066 | var rtenv = subenv(env); // New compiler env holds the definitions.
1067 | Object.keys(definitions).forEach(function (fn) {
1068 | define(rtenv, fn, 'runtime$' + rtenv.id + '$.' + fn);
1069 | });
1070 |
1071 | return function (expr) {
1072 | var env = subenv(rtenv); // Make a new one so that each run is independent.
1073 |
1074 | return eval('(function (__runtime__) { return (function (' + newvar(env, 'param') + ') {'
1075 | + 'var runtime$' + rtenv.id + '$ = __runtime__;'
1076 | + 'return (' + compile(env, expr) + ');'
1077 | + '}); })')(definitions);
1078 | };
1079 | }
1080 |
1081 | // ## Standard library
1082 |
1083 | // With the above notion of runtime, we can define a "standard library"
1084 | // that implements as functions some of what we wrote above as macros.
1085 | var standardLibrary = {
1086 | display: (function () {
1087 | var map = Array.prototype.map;
1088 | var stringify = JSON.stringify;
1089 | return function () {
1090 | console.log(map.call(arguments, stringify).join(''));
1091 | };
1092 | }()),
1093 |
1094 | from: function (fromIx) {
1095 | var step = this.step === undefined ? 1 : this.step;
1096 | var toIx = this.to === undefined ? (fromIx + step * 1e16) : this.to;
1097 | var i = fromIx;
1098 | var index = 0;
1099 |
1100 | // We support an optional "when:" field using which
1101 | // the user can supply a predicate that filters the
1102 | // stream of results. The predicate has the signature -
1103 | // value -> index -> Bool
1104 | if (this.when && this.when.constructor === Function) {
1105 | var when = this.when;
1106 | return function (reset) {
1107 | var result, resultIx;
1108 |
1109 | if (reset) {
1110 | i = fromIx;
1111 | index = 0;
1112 | }
1113 |
1114 | while (step >= 0 ? (i < toIx) : (i > toIx)) {
1115 | result = i;
1116 | resultIx = index;
1117 | i += step;
1118 | index += 1;
1119 |
1120 | // Use the function to filter the result.
1121 | if (when(result, resultIx)) {
1122 | return result;
1123 | }
1124 | }
1125 |
1126 | return undefined; // Indicates end of iteration.
1127 | };
1128 | } else {
1129 | return function (reset) {
1130 | var result;
1131 |
1132 | if (reset) {
1133 | i = fromIx;
1134 | }
1135 |
1136 | if (step >= 0 ? (i < toIx) : (i > toIx)) {
1137 | result = i;
1138 | i += step;
1139 | return result;
1140 | }
1141 |
1142 | return undefined; // Indicates end of iteration.
1143 | };
1144 | }
1145 | },
1146 |
1147 | in: function (arr) {
1148 | var fromIx = this.from === undefined ? 0 : this.from;
1149 | var step = this.step === undefined ? 1 : this.step;
1150 | var toIx = this.to === undefined ? (step >= 0 ? (fromIx + arr.length) : -1) : this.to;
1151 | var i = fromIx;
1152 |
1153 | // We support an optional "when:" field using which
1154 | // the user can supply a predicate that filters the
1155 | // stream of results. The predicate has the signature -
1156 | // value -> index -> array -> Bool
1157 | if (this.when && this.when.constructor === Function) {
1158 | var when = this.when;
1159 | return function (reset) {
1160 | var resultIx;
1161 |
1162 | if (reset) {
1163 | i = fromIx;
1164 | }
1165 |
1166 | while (step >= 0 ? (i < toIx) : (i > toIx)) {
1167 | resultIx = i;
1168 | i += step;
1169 | if (when(arr[resultIx], resultIx, arr)) {
1170 | return arr[resultIx];
1171 | }
1172 | }
1173 |
1174 | return undefined; // Indicates end of iteration.
1175 | };
1176 | } else {
1177 | return function (reset) {
1178 | var resultIx;
1179 |
1180 | if (reset) {
1181 | i = fromIx;
1182 | }
1183 |
1184 | if (step >= 0 ? (i < toIx) : (i > toIx)) {
1185 | resultIx = i;
1186 | i += step;
1187 | return arr[resultIx];
1188 | } else {
1189 | return undefined; // Indicates end of iteration.
1190 | }
1191 | };
1192 | }
1193 | },
1194 |
1195 | map: function (fn) {
1196 | return this.list.map(fn);
1197 | },
1198 |
1199 | reduce: function (fn) {
1200 | return this.list.reduce(fn, this.init);
1201 | },
1202 |
1203 | filter: function (fn) {
1204 | return this.list.filter(fn);
1205 | }
1206 | };
1207 |
1208 | // ... but then we'll need some way of combining multiple
1209 | // such definitions lists into a single one before we can use
1210 | // the Env.prototype.runtime call to make a runtime. We'll also
1211 | // need to insert the standard definitions before any custom
1212 | // definitions are loaded. Let's therefore patch the runtime function
1213 | // to accept multiple definitions objects and merge them all into a
1214 | // single pile before making a runtime.
1215 | runtime = (function (runtime) {
1216 | return function (env) {
1217 | var definitions = copyValues(standardLibrary, {});
1218 | copyValues(Array.prototype.slice.call(arguments, 1), definitions);
1219 | return runtime(env, definitions);
1220 | };
1221 | }(runtime))
1222 |
1223 | function copyValues(source, target) {
1224 | if (source.constructor === Array) {
1225 | source.forEach(function (d) {
1226 | copyValues(d, target);
1227 | });
1228 | } else if (source.constructor === Function) {
1229 | source(target); /* When a function is passed, I pass it the target
1230 | * and let it deal with inserting primitives. That
1231 | * way, the function can make use of what was defined
1232 | * before it was called, such as the standardLibrary.
1233 | */
1234 | } else if (source instanceof Object) {
1235 | Object.keys(source).forEach(function (k) {
1236 | target[k] = source[k];
1237 | });
1238 | }
1239 |
1240 | return target;
1241 | }
1242 |
1243 | if (enable_tests) {
1244 | console.log("Testing standardLibrary..");
1245 | runtime(Prim)(
1246 | {let: {},
1247 | in: [{display: {map: {lambda: ["x"], body: {table: {x: "x"}}},
1248 | list: {list: [1,2,3]}}},
1249 | {display: {for: {x: {from: 1, to: 10}}, expr: {table: {x: "x"}}}}]}
1250 | )();
1251 | }
1252 |
1253 | // ## A *different* model of a runtime ##
1254 | //
1255 | // Actually, I don't quite like the above model of the runtime, because
1256 | // I can't now compile code in one place and run it in
1257 | // another place. To fix that, I need some way to indicate that
1258 | // a symbol whose value is unknown at compile time is expected to
1259 | // be resolved at runtime. That's most easily done by patching
1260 | // lookupSymbol. Note that lookupSymbol will *always* succeed now
1261 | // for syntactically valid symbols. Also, we use the "R_" prefix just
1262 | // so we don't walk all over the JS proprietary properties.
1263 | lookupSymbol = (function (oldLookupSymbol) {
1264 | var symRE = /^[a-zA-Z_\$][a-zA-Z0-9_\.\$]*$/;
1265 | return function (env, sym) {
1266 | return oldLookupSymbol(env, sym)
1267 | || (symRE.test(sym) ? ('__jexpr_runtime__.R_' + sym) : undefined);
1268 | };
1269 | }(lookupSymbol));
1270 |
1271 | // ... then I need to define a top level block compiler that will
1272 | // do the necessary wrapping.
1273 | function compile_to_js(env, exprArr) {
1274 | return '(function (__jexpr_runtime__, ' + newvar(env, 'param') + ') {'
1275 | + 'return (' + compile_args(env, exprArr) + ');'
1276 | + '})';
1277 | }
1278 |
1279 | // Now the runtime building can be independent of
1280 | // the compilation environment which may no longer exist.
1281 | // Much cleaner!
1282 | function makeRuntime() {
1283 | var definitions = copyValues(standardLibrary, {});
1284 | copyValues(Array.prototype.slice.call(arguments, 0), definitions);
1285 | var prefixed = {};
1286 | Object.keys(definitions).forEach(function (k) {
1287 | prefixed['R_' + k] = definitions[k];
1288 | });
1289 | return prefixed;
1290 | }
1291 |
1292 | // Now the calling sequence is -
1293 | //
1294 | // eval(compile_to_js(env, [expr..]))(makeRuntime(...), {...params...})
1295 | //
1296 | // Though this DRAMATICALLY alters how a runtime is defined, the
1297 | // actual definition of the runtime such as `standardLibrary` remains
1298 | // the same.
1299 |
1300 | // Undefine the definitions moved to the standardLibrary so that
1301 | // they can be overridden by user runtime definitions.
1302 | Object.keys(standardLibrary).forEach(function (key) {
1303 | define(Prim, key, undefined);
1304 | });
1305 |
1306 | // Math functions are safe.
1307 | // FIXME: ... but actually not. Math.constructor and such stuff
1308 | // is now exposed to the language! This is actually a general
1309 | // problem with allowing the dot syntax without restrictions.
1310 | standardLibrary.Math = Math;
1311 |
1312 | // ### Operators
1313 | //
1314 | // The language is pretty bare and we don't even have basic
1315 | // addition, subtraction, boolean operations, etc. within the
1316 | // language. We need to expose some JS functionality here.
1317 | //
1318 | // `n` is the arity of the operator, which can be 2 or undefined,
1319 | // for the moment.
1320 | function defineOperator(Prim, n, opjname, opname) {
1321 | opname = opname || opjname;
1322 | define(Prim, opjname, function (env, expr) {
1323 | var argv = expr[opjname];
1324 | console.assert((n === undefined) || (argv.length <= n));
1325 | return '(' + argv.slice(0, n).map(function (arg) {
1326 | return '(' + compile(env, arg) + ')';
1327 | }).join(' ' + opname + ' ') + ')';
1328 | });
1329 | }
1330 |
1331 | defineOperator(Prim, 2, '<');
1332 | defineOperator(Prim, 2, '<=');
1333 | defineOperator(Prim, 2, '>');
1334 | defineOperator(Prim, 2, '>=');
1335 | defineOperator(Prim, 2, '>>');
1336 | defineOperator(Prim, 2, '<<');
1337 | defineOperator(Prim, undefined, '+');
1338 | defineOperator(Prim, undefined, '-');
1339 | defineOperator(Prim, undefined, '*');
1340 | defineOperator(Prim, undefined, '/');
1341 | defineOperator(Prim, undefined, '%');
1342 | defineOperator(Prim, 2, 'is', '===');
1343 | defineOperator(Prim, 2, 'isnot', '!==');
1344 | defineOperator(Prim, 2, 'isin', 'in');
1345 |
1346 | // Short circuiting 'and' and 'or'
1347 | defineOperator(Prim, undefined, 'and', '&&');
1348 | defineOperator(Prim, undefined, 'or', '||');
1349 |
1350 | define(Prim, 'not', function (env, expr) {
1351 | return '(!' + compile(env, expr.not) + ')';
1352 | });
1353 |
1354 | // We can add infix operator support by writing a macro.
1355 | // Since such infix is usually used only in math-y code,
1356 | // we'll just call the macro `math`.
1357 |
1358 | // This array defines the precedences sequence, from the highest
1359 | // to lowest precedence.
1360 | var operatorPrecedenceSeq = ['*', '%', '/', '+', '-', '<<', '>>', 'is', 'isnot', 'isin', '<', '<=', '>', '>=', 'and', 'or'];
1361 |
1362 | var operatorRE = /^(<<|>>|<=|>=|<|>|===|==|\+|\-|\*|\/|\%|\band\b|\bor\b|\bisnot\b|\bisin\b|\bis\b)$/;
1363 |
1364 | // Given a sequence of terms, processing the operators in them is
1365 | // a simple fold over the precedence sequence.
1366 | function processInfixOperators(seq) {
1367 | if (seq.constructor === Array) {
1368 | return operatorPrecedenceSeq.reduce(processInfixOperator, seq);
1369 | } else {
1370 | return seq;
1371 | }
1372 | }
1373 |
1374 | // Rewrites the `seq` so that infix usages of the operator `op`
1375 | // are rewritten so that the operator is at its rightful head
1376 | // position.
1377 | function processInfixOperator(seq, op) {
1378 | var i, j, N, args, result = [], opexpr, e, en;
1379 | for (i = 0, N = seq.length; i < N; ++i) {
1380 | e = seq[i];
1381 | if (i > 0 && e === op) { // Collect arguments.
1382 | args = [result.pop()];
1383 | for (j = i + 1; j < N; j += 2) {
1384 | if (seq[j-1] === op) {
1385 | args.push(seq[j]);
1386 | } else {
1387 | break;
1388 | }
1389 | }
1390 |
1391 | opexpr = {};
1392 | opexpr[op] = args;
1393 | result.push(opexpr);
1394 |
1395 | i = j - 2;
1396 | } else if (e.constructor === Array) {
1397 | result.push(processInfixOperators(e)[0]);
1398 | } else if (e.constructor === Object && !(en = operatorName(e)).match(operatorRE)) {
1399 | // (a - b) will get parsed as {"a": ["-", "b"]), but
1400 | // within parens, you could have an operator at head position
1401 | // as well, like (- a b), or a processed one like {"-": ["a", "b"]}
1402 | // which should both be left alone.
1403 | result.push(processInfixOperators([en].concat(e[en]))[0]);
1404 | } else {
1405 | result.push(e);
1406 | }
1407 | }
1408 | return result;
1409 | }
1410 |
1411 | // Now for the actual `math` macro that can rewrite
1412 | //
1413 | // math a + b * (d - (c % d))
1414 | //
1415 | // into
1416 | //
1417 | // {'+': ['a', {'*': ['b', {'-': ['d', {'%': ['c', 'd']}]}]}]}
1418 | //
1419 | // Note that `processInfixOperators`, when it succeeds, will yield an
1420 | // array of one expression, which we extract using the `[0]`.
1421 | define(Prim, 'math', function (env, expr) {
1422 | return compile(env, processInfixOperators(expr.math)[0]);
1423 | });
1424 |
1425 | // ## Limiting exposure in the exports
1426 | //
1427 | // I'd like the ability to be very very selective about what
1428 | // gets exposed in the environment so that at some point I can
1429 | // safely expose compilation environments at runtime. To do this,
1430 | // I create a "frozen wrapper" around a given environment that poses
1431 | // no extra running overhead for the internal machinery.
1432 | //
1433 | // The exposed functionality is all here.
1434 | //
1435 | // `J` is the name of the exposed variable containing this API.
1436 | function freeze(env) {
1437 | return Object.freeze({
1438 |
1439 | // `J.subenv()` makes a new environment with J as its parent. That new
1440 | // sub-environment also gets this very same API.
1441 | subenv: function () {
1442 | return freeze(subenv(env));
1443 | },
1444 |
1445 | // `J.option(name, [value])` gets/sets environment options. Currently the
1446 | // only option exposed is 'unsafe' which can be set to true/false to permit
1447 | // unsafe expressions at compilation time.
1448 | option: function (optName, optVal) {
1449 | return (arguments.length === 1
1450 | ? env.options[optName]
1451 | : (env.options[optName] = optVal));
1452 | },
1453 |
1454 | // `J.define(name, value)` puts a symbol definition into the compilation
1455 | // environment. Defining symbols in a sub-environment does not affect
1456 | // symbol lookup in parent environments.
1457 | define: function (name, value) {
1458 | return define(env, name, value);
1459 | },
1460 |
1461 | // `J.compile_to_js(expr,...)` Returns the compiled Javascript source for the
1462 | // given expression as a string. Evaluating this string will give you a function
1463 | // of the form --
1464 | //
1465 | // function (runtime, param) {..}
1466 | compile_to_js: function () {
1467 | return compile_to_js(env, Array.prototype.slice.call(arguments, 0));
1468 | },
1469 |
1470 | // `J.compile(expr,...)` Returns the compiled closure that you can pass to `J.eval`,
1471 | // or call yourself.
1472 | compile: function () {
1473 | return eval(compile_to_js(env, Array.prototype.slice.call(arguments, 0)));
1474 | },
1475 |
1476 | // `J.runtime(defns...)` will collect all the supplied runtime definitions
1477 | // into a single object and return it. The `standardLibrary` is included
1478 | // by default.
1479 | //
1480 | // Specifying definitions has a lot of flexibility -
1481 | //
1482 | // 1. You can give a table of name->defn mappings,
1483 | // 2. You can give a function(table) which is then
1484 | // applied to the table of already loaded definitions
1485 | // so you can add new ones that make use of older ones.
1486 | // 3. You can pass an array of such tables or functions
1487 | // and it steps through such arrays recursively. This
1488 | // helps with "componentizing" the runtime.
1489 | runtime: makeRuntime,
1490 |
1491 | // `J.eval(expr, runtime, param)` wraps it all together. You can either
1492 | // pass in a compiled expression (as a closure in the form returned by
1493 | // `J.compile(expr)` or a j-expression which will then be compiled and
1494 | // evaluated. If given a j-expression, this is equivalent to -
1495 | //
1496 | // eval(J.compile(expr))(runtime, param)
1497 | eval: function (expr, rt, param) {
1498 | rt = rt || makeRuntime();
1499 | param = param || {};
1500 | if (expr.constructor === Function) {
1501 | return expr(rt, param); // Already a compiled expression.
1502 | } else {
1503 | return eval(compile_to_js(env, [expr]))(rt, param); // Need to compile.
1504 | }
1505 | },
1506 |
1507 | // `J.parse(string)` will make a parser for the given string containig
1508 | // a jsonx expression (= JSON with unquoted identifier strings allowed).
1509 | // Each call of the resultant function will parse the next JSONx object in
1510 | // the string and return it, finally returning `undefined`.
1511 | parse: jsonx,
1512 |
1513 | // `J.runPageScripts(window, runtime)` will scan the given window's document
1514 | // for script tags with `type="application/x-jexpr"` attribute set and evaluate
1515 | // all of them.
1516 | runPageScripts: function (window, runtime) {
1517 | var scripts = window.document.querySelectorAll('script[type="application/x-jexpr"]');
1518 | var scriptText = '';
1519 | var i, N;
1520 | for (i = 0, N = scripts.length; i < N; ++i) {
1521 | scriptText += scripts[i].text + '\n';
1522 | }
1523 | var e = this.parse(scriptText);
1524 | var rt = this.runtime(runtime || window);
1525 | var expr;
1526 | for (expr = e(); expr; expr = e()) {
1527 | this.eval(expr, rt);
1528 | }
1529 | }
1530 | });
1531 | }
1532 |
1533 | // for debugging.
1534 | function show(label, x) {
1535 | console.log(label + ':\t' + JSON.stringify(x));
1536 | return x;
1537 | }
1538 |
1539 | // ## JSONx parser
1540 |
1541 | //
1542 | // This is a parser for a (highly) modified JSON (called JSONx) where unquoted identifiers
1543 | // are automatically treated as strings. Identifiers begin within alphabetic
1544 | // or underscore or dollar and can contain alphanumeric or underscore or
1545 | // dollar or period in the middle. Consecutive periods are not allowed.
1546 | // It is the basis for the J "programming language" whose AST *is* a
1547 | // JSON-serializable form, as opposed to a language whose AST is *represented*
1548 | // in JSON-serializable form. (This property enables macros in the language.)
1549 | //
1550 | // I just took Douglas Crockford's reference implementation and modified it
1551 | // to parse JSONx. (So, thanks a mil for the basic JSON parser Douglas!)
1552 | //
1553 | // This is valid JSONx - `{one.two: [buckle, 'my', "shoe"]}` - and is equivalent
1554 | // to the pure JSON `{"one.two": ["buckle", "my", {"$": "shoe"}]}`. (Yes, now
1555 | // you know I have a kid!)
1556 | //
1557 | // This bit of code doesn't follow the "stream of thought" style and it evolved
1558 | // to a point where it now has support for optional tab-syntax. Here is a summary -
1559 | //
1560 | // A "term" is of the form -
1561 | //
1562 | // ... keyword1: keyword2: ...
1563 | //
1564 | // .. which can be written like this as well -
1565 | //
1566 | // ...
1567 | // keyword1:
1568 | // keyword2:
1569 | //
1570 | // The key syntax ideas are a) line breaks begin terms or continue keyword
1571 | // parts of a term (based on indentation >= term) and b) parentheses contain
1572 | // terms. See the "cases" directory for some ad hoc examples.
1573 | //
1574 | var jsonx = (function () {
1575 |
1576 | // Error object with some info about source context.
1577 | // We keep the current source parsing state in an object
1578 | // that is passed here as `state`.
1579 | function error(desc, state) {
1580 | var e = new Error(desc);
1581 | e.name = 'JSONx_SyntaxError';
1582 | e.text = state.text;
1583 | e.at = state.at;
1584 | e.line = state.line.slice(0);
1585 | throw e;
1586 | }
1587 |
1588 | // Copying a parsing state is useful to do some kinds
1589 | // of look ahead in a recursive descent parser.
1590 | function clone(state) {
1591 | var copy = {};
1592 | copy.text = state.text;
1593 | copy.at = state.at;
1594 | copy.ch = state.ch;
1595 | copy.line = [column(state)];
1596 | copy.toString = state.toString;
1597 | return copy;
1598 | }
1599 |
1600 | // State copy is also useful for look ahead.
1601 | function copy(stateFrom, stateTo) {
1602 | stateTo.text = stateFrom.text;
1603 | stateTo.at = stateFrom.at;
1604 | stateTo.ch = stateFrom.ch;
1605 | stateTo.line.pop();
1606 | stateTo.line.push.apply(stateTo.line, stateFrom.line);
1607 | return stateTo;
1608 | }
1609 |
1610 | // Make a state for parsing the `text` starting
1611 | // from `at`. Info about the lines are kept in
1612 | // an array. The values of the array give the column
1613 | // offset processed on that line.
1614 | function mkState(text, at) {
1615 | return {
1616 | text: text,
1617 | at: at || 0,
1618 | line: [0],
1619 | toString: function () { return this.text.substr(this.at, 10); }
1620 | };
1621 | }
1622 |
1623 | // Advance the parse state by `cols` columns.
1624 | function advance(state, cols) {
1625 | state.line[state.line.length - 1] += cols;
1626 | }
1627 |
1628 | function resetcol(state) {
1629 | // state.line[state.line.length - 1] = 0;
1630 | }
1631 |
1632 | // Gets the current column. This is stored as the
1633 | // last element of the line array.
1634 | function column(state) {
1635 | return state.line[state.line.length - 1];
1636 | }
1637 |
1638 | // Gets next character from current parse state
1639 | // and advances the parse state.
1640 | function getChar(state) {
1641 | state.ch = state.text.charAt(state.at++);
1642 | switch (state.ch) {
1643 | case '\t': advance(state, 4); break;
1644 | case '\n': state.line.push(0); break;
1645 | default: advance(state, 1); break;
1646 | }
1647 | return state.ch;
1648 | }
1649 |
1650 | // Ungets the last getChar().
1651 | function ungetChar(state) {
1652 | switch (state.ch) {
1653 | case '\t': advance(state, -4); break;
1654 | case '\n': state.line.pop(); break;
1655 | default: advance(state, -1); break;
1656 | }
1657 | return state.ch = state.text.charAt((--state.at) - 1);
1658 | }
1659 |
1660 | // Peeks ahead. Cheap implementation using getChar()
1661 | // followed by ungetChar().
1662 | function look(state) {
1663 | var ch = getChar(state);
1664 | ungetChar(state);
1665 | return ch;
1666 | }
1667 |
1668 | // Returns a substring starting from the current parse state
1669 | // - i.e. the "rest of the text".
1670 | function rest(state) {
1671 | return state.text.substr(state.at);
1672 | }
1673 |
1674 | // Takes `n` characters from the current parse state and returns
1675 | // them as a string, while advancing the parse state by `n` characters.
1676 | function take(state, n) {
1677 | var i = state.at;
1678 | state.at += n;
1679 | advance(state, n);
1680 | return state.text.substr(i, n);
1681 | }
1682 |
1683 | // Tells if the text is finished.
1684 | function end(state) {
1685 | return state.at >= state.text.length;
1686 | }
1687 |
1688 | // A parser for a regular expression `exp`.
1689 | // The result of the parse, if successful, is
1690 | // mapped using the optional `mapper` function
1691 | // provided.
1692 | function re(state, exp, mapper) {
1693 | var m = rest(state).match(exp), s;
1694 | if (m) {
1695 | s = take(state, m[0].length);
1696 | return mapper ? mapper(s) : s;
1697 | } else {
1698 | return '';
1699 | }
1700 | }
1701 |
1702 | // A parser combinator that turns a parser into a
1703 | // logging parser. Inefficient, but this is only for
1704 | // internal debugging at the moment.
1705 | function logp(p) {
1706 | return function (state) {
1707 | var s = p(state);
1708 | console.log(p.name + '[' + state.at + '] :\t\t<<' + s + '>>');
1709 | return s;
1710 | };
1711 | }
1712 |
1713 | // Parses one character and succeeds if the character is
1714 | // the given `ch`.
1715 | function charp(state, ch) {
1716 | if (end(state)) {
1717 | return '';
1718 | }
1719 | var c = getChar(state);
1720 | if (ch === c) {
1721 | return ch;
1722 | } else {
1723 | ungetChar(state);
1724 | return '';
1725 | }
1726 | }
1727 |
1728 | // Parses an integer or floating point number.
1729 | function number(state) {
1730 | return re(state, /^\-?[0-9]+(\.[0-9]+)?([eE][\-\+]?[0-9]+)?/,
1731 | function (s) { return +s; });
1732 | }
1733 |
1734 | // Parses a 4-digit hex code for unicode chars.
1735 | function hex4(state) {
1736 | return re(state, /^[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]/,
1737 | function (s) {
1738 | return parseInt(s, 16);
1739 | });
1740 | }
1741 |
1742 | // List of escape codes and their values.
1743 | var escapees = {
1744 | '"': '"',
1745 | "'": "'",
1746 | '\\': '\\',
1747 | '/': '/',
1748 | b: '\b',
1749 | f: '\f',
1750 | n: '\n',
1751 | r: '\r',
1752 | t: '\t'
1753 | };
1754 |
1755 | // Parses an escapee.
1756 | function escapee(state) {
1757 | var ch = getChar(state);
1758 | if (escapees[ch]) {
1759 | return escapees[ch];
1760 | } else {
1761 | ungetChar(state);
1762 | error('Unknown escapee <<' + ch + '>>');
1763 | }
1764 | }
1765 |
1766 | // Pases a JSON string, but can be surrounded by
1767 | // either '"' or '\''.
1768 | function string(state, surroundedBy) {
1769 |
1770 | surroundedBy = surroundedBy || '"';
1771 | var string = '';
1772 |
1773 | if (charp(state, surroundedBy)) {
1774 | while (!end(state)) {
1775 | if (charp(state, surroundedBy)) {
1776 | return string;
1777 | } else if (charp(state, '\\')) {
1778 | if (charp(state, 'u')) {
1779 | string += String.fromCharCode(hex4(state));
1780 | } else {
1781 | string += escapee(state);
1782 | }
1783 | } else {
1784 | string += getChar(state);
1785 | }
1786 | }
1787 | }
1788 |
1789 | return string;
1790 | }
1791 |
1792 | // An "identistring" is an identifier-like string, but with
1793 | // allowance for period characters in the middle.
1794 | function identistring(state) {
1795 | return re(state, /^([a-zA-Z_\$][a-zA-Z0-9_\$]*)(\.[a-zA-Z_\$][a-zA-Z0-9_\$]*)*/);
1796 | }
1797 |
1798 | // Some operators we support.
1799 | function operatorp(state) {
1800 | // <, >, <=, >=, <<, >>, +, -, *, /, %
1801 | return re(state, /^(<<|>>|<=|>=|<|>|\+|\-|\*|\/|\%)/);
1802 | }
1803 |
1804 | // Support the standard '//' comment form.
1805 | var commentRE = /^\/\/[^\n]*/;
1806 |
1807 | // Parses that skips white space and comments.
1808 | function white(state) {
1809 | while (!end(state)) {
1810 | switch (state.text.charAt(state.at)) {
1811 | case ' ': state.at++; advance(state, 1); continue;
1812 | case '\t': state.at++; advance(state, 4); continue;
1813 | case '\n': state.at++; state.line.push(0); continue;
1814 | default:
1815 | if (re(state, commentRE)) {
1816 | continue;
1817 | } else {
1818 | return state;
1819 | }
1820 | }
1821 | }
1822 | return state;
1823 | }
1824 |
1825 | // Parses some known value key words of the JS language.
1826 | function word(state) {
1827 | return re(state, /^(true|false|null)\b/,
1828 | function (s) {
1829 | switch (s) {
1830 | case 'true': return true;
1831 | case 'false': return false;
1832 | case 'null': return null;
1833 | }
1834 | });
1835 | }
1836 |
1837 | // Parses the array syntax. Note that the array contents
1838 | // are full JSONx "terms" and can therefore be expressions
1839 | // that are compiled instead of just values.
1840 | function array(state) {
1841 | var array = [];
1842 | if (charp(state, '[')) {
1843 | white(state);
1844 | if (charp(state, ']')) {
1845 | return array;
1846 | }
1847 |
1848 | while (!end(state)) {
1849 | resetcol(state);
1850 | array.push(term(state));
1851 | white(state);
1852 | if (charp(state, ']')) {
1853 | return array;
1854 | } else if (charp(state, ',')) {
1855 | white(state);
1856 | } else {
1857 | error('Expecting ] or ,', state);
1858 | }
1859 | }
1860 | }
1861 |
1862 | error('Bad array', state);
1863 | }
1864 |
1865 | // Parses the object syntax. Object values can also be
1866 | // JSONx "terms". Support object keys with any string
1867 | // representation.
1868 | function object(state) {
1869 | var object = {}, key;
1870 | if (charp(state, '{')) {
1871 | white(state);
1872 | if (charp(state, '}')) {
1873 | return object;
1874 | }
1875 |
1876 | while (!end(state)) {
1877 | key = string(state, '"') || string(state, "'") || identistring(state);
1878 | white(state);
1879 | if (charp(state, ':')) {
1880 | if (Object.hasOwnProperty.call(object, key)) {
1881 | error('Duplicate key "' + key + '"', state);
1882 | }
1883 | resetcol(state);
1884 | object[key] = term(state);
1885 | white(state);
1886 | if (charp(state, '}')) {
1887 | return object;
1888 | } else if (charp(state, ',')) {
1889 | white(state);
1890 | } else {
1891 | error('Expecting } or ,', state);
1892 | }
1893 | } else {
1894 | error('Expecting :', state);
1895 | }
1896 | }
1897 | error('Bad object', state);
1898 | } else {
1899 | return '';
1900 | }
1901 | }
1902 |
1903 | // A "term" is of one of the following forms -
1904 | //
1905 | //
1906 | // "string"
1907 | // 'multi-part identifier'
1908 | // identifier
1909 | // a.nested.reference
1910 | // [a,b,c]
1911 | // {"key":"val",...}
1912 | // (term)
1913 | // true|false|null
1914 | // head arg1 arg2 arg3 ... argN kw1: kwa1 kwa2 ... kwaI kw2: kwb1 kwb2 ... kwbJ ...
1915 | // -- The arg1 arg2 .. are not head terms whereas the keyword arguments are
1916 | // -- head terms. This means "head: arg1 arg2 kw1: v1 v2" translates to
1917 | // -- {head: [arg1, arg2], kw1: {v1: v2}}
1918 | // -- if v1 can be a head - i.e. is an identifier.
1919 | function term(state, head, kwterm) {
1920 | var ch, s, t, cs, cs1, cs2, a, t2, kw;
1921 | white(state);
1922 | if (head && column(state) <= column(head)) {
1923 | /* Indentation gone out of scope. */
1924 | return undefined;
1925 | }
1926 | if (end(state)) {
1927 | return undefined;
1928 | }
1929 | ch = look(state);
1930 | switch (ch) {
1931 | case '{': return object(state);
1932 | case '[': return array(state);
1933 | case '"': return {$: string(state, '"')};
1934 | case '\'': return string(state, '\'');
1935 | case '(':
1936 | cs = clone(state);
1937 | if (charp(cs, '(') && (resetcol(cs), (t = term(cs)) !== undefined)) {
1938 | if (white(cs), charp(cs, ')')) {
1939 | copy(cs, state);
1940 | return t;
1941 | } else {
1942 | /* Perhaps more arguments? If so, turn it into
1943 | a plain array for later processing, say, using 'math'. */
1944 | t2 = argv(cs, state);
1945 |
1946 | /* process keywords. */
1947 | kw = {};
1948 | while (optKeywordPart(cs, kw, state)) {};
1949 |
1950 | if (white(cs), charp(cs, ')')) {
1951 | t2.unshift(t);
1952 | t = t2;
1953 | t.keywords = kw;
1954 | } else {
1955 | error('Expecting ) in term', cs);
1956 | }
1957 |
1958 | copy(cs, state);
1959 | return t;
1960 | }
1961 | } else {
1962 | error('Bad term', state);
1963 | }
1964 | case ')':
1965 | case ',':
1966 | case '}':
1967 | case ']':
1968 | return undefined;
1969 | default:
1970 | if (ch === '-') {
1971 | s = number(state);
1972 | if (s) { return s; }
1973 | }
1974 |
1975 | if (ch >= '0' && ch <= '9') {
1976 | return number(state);
1977 | }
1978 |
1979 | cs = clone(state);
1980 | if (word(cs) !== '') {
1981 | return word(state);
1982 | }
1983 |
1984 | // A full term. Not a word.
1985 | cs = clone(state);
1986 | if (s = (identistring(cs) || operatorp(cs))) {
1987 | cs2 = clone(cs);
1988 | if (!charp(cs2, ':')) {
1989 | if (!head || state.line.length > 1 || kwterm) {
1990 | // This is a head term and we need to collect argv and keywords
1991 | // if there is no prior head, a line break has occurred after
1992 | // the previous term or this is a keyword term.
1993 | t = {};
1994 | try {
1995 | t[s] = argv(cs2, state);
1996 | } catch (e) {
1997 | // Only argv throws error. optKeywordPart doesn't.
1998 | // A single term without argv is a value. (Going pure functional syntax here!)
1999 | t = s;
2000 | }
2001 | // process keywords.
2002 | while (optKeywordPart(cs2, t, state)) {};
2003 | copy(cs2, state);
2004 | return t;
2005 | } else {
2006 | copy(cs2, state);
2007 | return s;
2008 | }
2009 | } else {
2010 | return undefined;
2011 | }
2012 | } else {
2013 | return undefined;
2014 | }
2015 | }
2016 | }
2017 |
2018 | // Parse the arguments of a term's head. The result is an array.
2019 | function argv(state, head, kwterm) {
2020 | var result = [];
2021 | var cs = clone(state);
2022 | var t;
2023 | while ((t = term(cs, head, kwterm)) !== undefined) {
2024 | result.push(t);
2025 | }
2026 |
2027 | if (result.length === 0) {
2028 | error('Term expected', state);
2029 | } else {
2030 | copy(cs, state);
2031 | return result.length === 1 ? result[0] : result;
2032 | }
2033 | }
2034 |
2035 | // Parse the optional keywords of a term.
2036 | function optKeywordPart(state, t, head) {
2037 | var cs = clone(state);
2038 | white(cs);
2039 | if (head && column(cs) < column(head)) {
2040 | // This keyword no longer applies to the given head
2041 | // due to the indentation going back to a shallower nesting.
2042 | return undefined;
2043 | }
2044 | var cs2 = clone(cs);
2045 | var kw = optKeyword(cs2), a;
2046 | if (kw !== undefined) {
2047 | a = argv(cs2, head, true);
2048 | t[kw] = (a.length === 1) ? a[0] : a;
2049 | copy(cs2, state);
2050 | return t;
2051 | } else {
2052 | return undefined;
2053 | }
2054 | }
2055 |
2056 | // Parse one keyword.
2057 | function optKeyword(state) {
2058 | var stateC = clone(state);
2059 | var str = identistring(stateC);
2060 | if (str && charp(stateC, ':')) {
2061 | copy(stateC, state);
2062 | return str;
2063 | } else {
2064 | return undefined;
2065 | }
2066 | }
2067 |
2068 | // Finally, the full parser. You pass in a text to be parsed
2069 | // as JSONx and you get a function back. Every time you call
2070 | // this function, you'll get the next term in the text, until
2071 | // there are no more, in which case the return value will be
2072 | // 'undefined'.
2073 | return function (text) {
2074 | var state = mkState(text, 0);
2075 | // You can call the returned function several
2076 | // times until you get a null. Multiple
2077 | // JSONx expressions in the string will be
2078 | // parsed and returned in sequence.
2079 | return function () {
2080 | white(state);
2081 | return end(state) ? undefined : term(state);
2082 | };
2083 | };
2084 |
2085 | }());
2086 |
2087 | return freeze(Prim);
2088 |
2089 | }(function () {
2090 | // Check whether J_enable_tests has been set. If it is set,
2091 | // some stupid examples in the "stream of thought" code will be
2092 | // evaluated and the results will be printed out.
2093 | try {
2094 | return global.J_enable_tests || false;
2095 | } catch (e) {
2096 | }
2097 |
2098 | try {
2099 | return window.J_enable_tests || false;
2100 | } catch (e) {
2101 | }
2102 |
2103 | return false;
2104 | }()));
2105 |
2106 | // Are we in node.js? If so set the exports variable.
2107 | // Otherwise just shut up and return.
2108 | try {
2109 | module.exports = J;
2110 | } catch (e) {
2111 | }
2112 |
2113 | // Are we in a browser? If so execute any script tags
2114 | // with `type="application/x-jexpr"`. Permit the scripts
2115 | // access to the enumerable properties of the `window`
2116 | // object.
2117 | try {
2118 | window.document.querySelectorAll;
2119 | window.console = window.console; // Also give access to the console.
2120 | J.subenv().runPageScripts(window, window); // subenv() 'cos we don't want residues.
2121 | } catch (e) {
2122 | }
2123 |
--------------------------------------------------------------------------------