make[2]: Entering directory `/home/stas/moz/code/l20n/js' Coverage

Coverage

49%
1009
495
514

l20n.js

75%
414
312
102
LineHitsSource
11(function() {
21 'use strict';
3
41 var DEBUG = false;
5
61 var L20n;
7
81 if (typeof exports !== 'undefined') {
91 L20n = exports;
101 L20n.Parser = require('./parser.js');
111 L20n.Compiler = require('./compiler.js');
12 } else {
130 L20n = this.L20n = {};
14 }
15
161 L20n.getContext = function L20n_getContext() {
1711 return new Context();
18 };
19
20 // clear the Resource cache which stores unprocessed LOL files and which is
21 // shared across all contexts; additionally, each context also has its own
22 // cache for ProcessedResources, which needs to be invalidated independently.
231 L20n.invalidateCache = function L20n_invalidateCache() {
2411 return resCache.invalidate();
25 };
26
27 // define variables and functions whose implementation is
28 // environment-dependent. Test suites can change the implementation to
29 // support server-side XHR, or to add logic to how URLs are resolved. See
30 // /tests/lib/context.js for an example.
311 var env = {
32 DEBUG: DEBUG,
33 getURL: function getURL(url) {
34 // in DEBUG mode, bypass the server cache
350 return DEBUG ? url + "?" + Date.now() : url;
36 },
37 // for now, we only use node to run tests, so it's safe to assume the test
38 // suite will provide its own env anyways. in the future, however, when we
39 // decide to support node, we should use fs.readFile and fs.readFileSync
40 XMLHttpRequest: typeof exports === 'undefined' ? XMLHttpRequest : null,
41 }
42
431 Object.defineProperty(L20n, "env", {
440 get : function() { return env; },
451 set : function(obj) { env = obj; },
46 enumerable : false,
47 configurable : false,
48 });
49
501 function debug() {
5172 if (env.DEBUG) {
520 console.log.apply(this, arguments);
53 }
54 }
55
561 var globals = {
57 get hour() {
580 return new Date().getHours();
59 },
60 get os() {
610 if (/^MacIntel/.test(navigator.platform)) {
620 return 'mac';
63 }
640 if (/^Linux/.test(navigator.platform)) {
650 return 'linux';
66 }
670 if (/^Win/.test(navigatgor.platform)) {
680 return 'win';
69 }
700 return 'unknown';
71 },
72 };
73
741 function EventEmitter() {
7513 this._listeners = {};
76 }
77
781 EventEmitter.prototype.emit = function ee_emit() {
7912 var args = Array.prototype.slice.call(arguments);
8012 var type = args.shift();
8112 var typeListeners = this._listeners[type];
8212 if (!typeListeners || !typeListeners.length) {
830 return false;
84 }
8512 typeListeners.forEach(function(listener) {
8612 listener.apply(this, args);
87 }, this);
8812 return true;
89 }
90
911 EventEmitter.prototype.addEventListener = function ee_add(type, listener) {
9213 if (!this._listeners[type]) {
9313 this._listeners[type] = [];
94 }
9513 this._listeners[type].push(listener);
9613 return this;
97 }
98
991 EventEmitter.prototype.removeEventListener = function ee_remove(type, listener) {
1000 var typeListeners = this._listeners[type];
1010 var pos = typeListeners.indexOf(listener);
1020 if (pos === -1) {
1030 return this;
104 }
1050 listeners.splice(pos, 1);
1060 return this;
107 }
108
1091 function Cache(ctx, ctor) {
11014 this.ctx = ctx;
11114 this.ctor = ctor;
11214 this.items = {};
113 }
114
1151 Cache.prototype.get = function Cache_get(id) {
11634 if (!this.items[id]) {
11734 this.items[id] = new this.ctor(id, this.ctx, this);
118 }
11934 return this.items[id];
120 };
121
1221 Cache.prototype.invalidate = function Cache_invalidate() {
12311 this.items = {};
12411 return true;
125 }
126
127 // keep one cache of Resources for all created contexts
1281 var resCache = new Cache(null, Resource);
129
130
1311 function _ifComplete(self, fn) {
13229 return function ifComplete() {
13325 if (self.isComplete()) {
13412 fn.apply(self, arguments);
135 }
136 }
137 }
138
1391 function _fire(callbacks, args) {
14025 callbacks.forEach(function(callback) {
14125 callback.apply(this, args);
142 });
14325 callbacks.length = 0;
144 }
145
1461 function Resource(id) {
14717 var ast = null;
14817 var imports = null;
14917 var callbacks = [];
15017 var xhr;
151
152 // when the resource is downloading, calling its get method will only add
153 // the specified callback to callbacks, without initiating a new XHR
15417 var isDownloading = false;
155 // resource is corrupted when the XHR returned an error; the resource
156 // must not be used.
15717 var isCorrupted = false;
158
15917 function downloadAsync(url, callback, fallback) {
16017 debug('Async GET ', url);
16117 xhr = new env.XMLHttpRequest();
16217 xhr.overrideMimeType('text/plain');
16317 xhr.addEventListener('load', function() {
16416 if (xhr.status == 200) {
16513 debug(url, 'fetched');
16613 callback(xhr.responseText);
167 } else {
1683 debug(url, 'failed to fetch');
1693 isCorrupted = true;
1703 fallback();
171 }
172 });
17317 xhr.addEventListener('abort', function() {
1740 debug('XHR aborted for ', url);
175 });
17617 xhr.addEventListener('error', function() {
1770 debug('XHR error for ', url);
1780 fallback();
179 });
18017 xhr.open('GET', env.getURL(url), true);
18117 xhr.send('');
182 }
183
18417 function downloadSync(url, callback, fallback) {
1850 debug('Sync GET ', url);
1860 xhr = new env.XMLHttpRequest();
1870 xhr.overrideMimeType('text/plain');
1880 xhr.open('GET', env.getURL(url), false);
1890 xhr.send('');
1900 if (xhr.status == 200) {
1910 debug(url, 'fetched');
1920 callback(xhr.responseText);
193 } else {
1940 debug(url, 'failed to fetch');
1950 isCorrupted = true;
1960 fallback();
197 }
198 }
199
20017 function parse(data) {
20113 ast = L20n.Parser.parse(data).body;
20213 imports = ast.filter(function(elem) {
20325 return elem.type == 'ImportStatement';
204 });
20513 _fire(callbacks, [ast, imports]);
206 }
207
20817 this.abortXHR = function() {
2093 debug('aborting XHR for ', id);
2103 xhr.abort();
211 };
212
21317 this.get = function r_get(callback, fallback, async) {
21417 if (ast) {
2150 callback(ast, imports);
21617 } else if (isCorrupted) {
2170 fallback();
218 } else {
21917 callbacks.push(callback);
22017 if (!isDownloading) {
22117 isDownloading = true;
22217 if (async) {
22317 downloadAsync(id, parse, fallback);
224 } else {
2250 downloadSync(id, parse, fallback);
226 }
227 }
228 }
229 }
230 }
231
2321 function ProcessedResource(id, ctx, cache) {
233
23430 var rawast = [];
23530 var totalImports = 0;
23630 var callbacks = [];
237
23830 this.id = id;
23930 this.ctx = ctx;
24030 this.ast = [];
24130 this.importedResources = []; // imported resources in order
242
243 // a preprocessed resource is ready when all its child resources are ready
244 // and it has been preprocessed to include their ASTs.
24530 this.isReady = false;
246 // a preprocessed resource is corrupted when its resource is corrupted, or
247 // any of its child preprocessed resources is corrupted too; a corrupted
248 // preprocessed resource must not be used.
24930 this.isCorrupted = false;
250
25130 this.dirname = id ? id.split('/').slice(0, -1).join('/') : null;
25230 this.resource = id ? resCache.get(id) : null;
253
25430 var self = this;
255
25630 function relativeToSelf(url) {
2577 if (url[0] == '/') {
2580 return url;
2597 } else if (self.dirname) {
260 // strip the trailing slash if present
2613 if (self.dirname[self.dirname.length - 1] == '/') {
2620 self.dirname = self.dirname.slice(0, self.dirname.length - 1);
263 }
2643 return self.dirname + '/' + url;
265 } else {
2664 return './' + url;
267 }
268 }
269
27030 function normalizeURL(url) {
27117 var normalized = [];
27217 var parts = url.split('/');
27317 parts.forEach(function(part, i) {
27462 if (part == '.') {
275 // don't do anything
27658 } else if (part == '..' && normalized[normalized.length - 1]) {
277 // remove the last element of `normalized`
2780 normalized.splice(normalized.length - 1, 1);
279 } else {
28058 normalized.push(part);
281 }
282 });
28317 return normalized.join('/');
28430 };
285
28630 function _expandUrn(urn, vars) {
28710 return urn.replace(/(\{\{\s*(\w+)\s*\}\})/g,
288 function(match, p1, p2, offset, string) {
28918 if (vars.hasOwnProperty(p2)) {
29018 return vars[p2];
291 }
2920 return p1;
293 });
294 }
295
29630 function resolveURI(uri) {
29716 if (!/^l10n:/.test(uri)) {
2987 var url = normalizeURL(relativeToSelf(uri));
2997 return [url];
300 }
3019 if (self.ctx.settings.locales === null) {
3020 throw "Can't use schema uris without settings.locales";
303 }
3049 var match = uri.match(/^l10n:(?:([^:]+):)?([^:]+)/);
3059 if (match === null) {
3060 throw "Malformed resource scheme: " + uri;
307 }
3089 var vars = {
309 'locale': self.ctx.getLocale(),
310 'app': '',
311 'resource': match[2]
312 };
3139 if (self.ctx.settings.schemes === null) {
3142 if (match[1] !== undefined) {
3150 throw "Can't use schema uris without settings.schemes";
316 }
3172 uri = [normalizeURL(_expandUrn(match[2], vars))];
318 } else {
3197 vars['app'] = match[1];
3207 uri = [];
3217 for (var i in self.ctx.settings.schemes) {
3228 var expanded = _expandUrn(self.ctx.settings.schemes[i], vars);
3238 uri.push(normalizeURL(expanded));
324 }
325 }
3269 return uri;
327 }
328
32930 function preprocess() {
3302 if (this.isReady) {
3310 return;
332 }
3332 rawast.forEach(function(node) {
3345 if (node.type == 'ImportStatement') {
3352 var importedAST = this.importedResources.shift().ast;
3362 this.ast = self.ast.concat(importedAST);
337 } else {
3383 this.ast.push(node);
339 }
340 }, this);
3412 this.isReady = true;
3422 _fire(callbacks);
343 }
344
34530 this.abortImports = function() {
3465 if (this.resource) {
3473 this.resource.abortXHR();
348 }
3495 this.importedResources.forEach(function(imported) {
3503 imported.abortImports();
351 });
352 };
353
35430 this.load = function pr_load(callback, fallback, nesting, async) {
35517 if (this.isReady) {
3560 callback();
35717 } else if (this.isCorrupted) {
3580 fallback();
359 } else {
36017 callbacks.push(callback);
36117 this.resource.get(function resolveImports(ast, imports) {
36213 rawast = ast;
36313 totalImports = imports.length;
36413 if (totalImports) {
3653 imports.forEach(function(node){
3663 debug('importing', node.uri.content)
3673 self.importResource(node.uri.content,
368 _ifComplete(self, preprocess),
369 fallback, nesting + 1, async);
370 });
371 } else {
37210 self.ast = rawast;
37310 self.isReady = true;
37410 _fire(callbacks);
375 }
376 }, fallback, async);
377 }
378 }
379
38030 function importFirstURL(urls, callback, fallback, nesting, async) {
38117 var url = urls.shift();
38217 var imported = cache.get(url);
38317 self.importedResources.push(imported);
38417 imported.load(
385 function importSucceeded() {
38612 debug('calling callback,', url, ' loaded OK');
38712 callback();
388 },
389 // `importFailed` is the fallback function which is called when the
390 // imported resource couldn't be loaded. There are two ways in which
391 // the load function can fail:
392 // 1. the resource file could not be found at the specified URL and
393 // there are more locations to look for it in; in this case, look for
394 // the resource in a different location,
395 // 2. the resource file could not be found and there are no more URLs
396 // to try; in this case, the context cannot be integral and a subctx
397 // must be created
398 // The second type of failure can happen for the resource that is
399 // currently being imported, as well as for any other nested import
400 // inside of it. The `importFailed` fallback will be also called from
401 // this resource's `importResource` method (or this resource's
402 // children's `importResource` method) and the `integrityError`
403 // argument is a flag which, when `true`, indicates that the second
404 // type of failure has occurred somewhere deeper in the import tree.
405 function importFailed(integrityError) {
4064 debug('calling fallback,', url, ' failed to load');
4074 if (integrityError || urls.length == 0) {
4083 debug('no more scheme URLs to try, creating a subcontext');
409 // fallback to the subctx; true bubbles the integrityError up to
410 // the parent fallback
4113 fallback(true);
412 } else {
4131 imported.isCorrupted = true;
414 // remove the imported resource from imports, so that we don't
415 // check if it's ready anymore
4161 var pos = self.importedResources.indexOf(imported);
4171 if (pos > -1) {
4181 self.importedResources.splice(pos, 1);
419 }
4201 importFirstURL(urls, callback, fallback, nesting, async);
421 }
422 }, nesting, async);
423 }
424
42530 this.importResource = function pr_importResource(uri,
426 callback,
427 fallback,
428 nesting,
429 async) {
43016 nesting = nesting || 0;
43116 if (nesting > 7) {
4320 throw "Too many nested imports";
433 }
434 // in case the uri is defined in the l10n: scheme, resolve it
43516 var urls = resolveURI(uri);
43616 importFirstURL(urls, callback, fallback, nesting, async);
437 }
438
43930 this.isComplete = function pr_isComplete() {
4402 if (this.importedResources.length != totalImports) {
4410 return false;
442 }
4432 for (var i in this.importedResources) {
4442 if (!this.importedResources[i].isReady) {
4450 return false;
446 }
447 }
4482 return true;
449 }
450 }
451
4521 function Context() {
453
45413 var entries = {}; // resource entries
45513 var ctxdata = {}; // context variables
45613 var emitter = new EventEmitter();
45713 var settings = {
458 'schemes': null, // path schemes
459 'locales': null, // list of locale codes in priority order
460 'timeout': 500, // timeout for asynchronous resource loading
461 };
462
463 // when context is frozen, no more resources can be added to it
46413 var isFrozen = false;
465 // context is integral when all the resources have been downloaded
46613 var isIntegral = false;
467 // context is ready when it's integral and compiled
46813 var isReady = false;
469
470 // a subcontext can be created when 1) the context is not integral, or 2)
471 // the context is ready, but an entity couldn't be found in it
47213 var subctx;
473
474 // contrary to the resCache, the cache for processed resources is
475 // per-context, because ProcessedResources keep reference to their parent
476 // context
47713 var cache = new Cache(this, ProcessedResource);
478
479 // Context is a top-level resource that imports other resources. The only
480 // difference is that a regular resource is a file, and the EOF marks the
481 // moment when the resource no longer accepts new imports. For the
482 // context's meta resource we need the `freeze` method to simulate this
483 // behavior.
48413 var meta = new ProcessedResource(null, this, cache);
485 // the URIs of all resources that have been addResource'd; used for
486 // creating a subctx
48713 var uris = [];
488 // uris' length, basically; used in m_isComplete to make sure we're
489 // checking all resources for readiness
49013 var totalResources = 0;
491
49213 var self = this;
493
49413 meta.isComplete = function m_isComplete() {
49523 if (!isFrozen) {
4960 return false;
497 }
49823 if (meta.importedResources.length != totalResources) {
4990 return false;
500 }
50123 for (var i in this.importedResources) {
50223 if (!this.importedResources[i].isReady) {
50313 return false;
504 }
505 }
50610 isIntegral = true;
50710 return true;
508 }
509
51013 function compile() {
51110 if (isReady) {
5120 return;
513 }
514 // flatten the AST
51510 meta.ast = meta.importedResources.reduce(function(prev, curr) {
51610 return prev.concat(curr.ast);
517 }, []);
51810 L20n.Compiler.compile(meta.ast, entries, globals);
51910 isReady = true;
52010 debug('context ready, current locale is ', self.getLocale());
52110 emitter.emit('ready');
522 }
523
52413 function getArgs(data) {
52510 var args = Object.create(ctxdata);
52610 if (data) {
52710 for (var i in data) {
5280 args[i] = data[i];
529 }
530 }
53110 return args;
532 }
533
53413 function createSubContext(async) {
5352 var subctx = new Context();
5362 subctx.settings.schemes = self.settings.schemes;
5372 subctx.settings.locales = self.settings.locales.slice(1);
5382 if (!subctx.getLocale()) {
5390 emitter.emit('error');
5400 throw "None of the requested locales was available.";
541 }
5422 debug('subcontext\'s locale is ', subctx.getLocale());
5432 uris.forEach(function(uri) {
5442 subctx.addResource(uri, async);
545 });
5462 subctx.freeze();
5472 return subctx;
548 }
549
55013 function invalidateOnIntegrityError() {
5512 isIntegral = false;
5522 meta.abortImports();
5532 debug('invalidated context\'s locale was ', self.getLocale());
5542 subctx = createSubContext(true);
5552 subctx.addEventListener('ready', function() {
5562 isReady = true;
5572 emitter.emit('ready');
558 });
559 }
560
56113 function getSync(id, data) {
56212 if (!isReady) {
5630 throw "Error: context not ready";
564 }
56512 if (!isIntegral) {
5662 return subctx.get(id, data);
567 }
56810 var entity = entries[id];
56910 if (!entity) {
5700 debug('entity', id, 'not found, using subcontext');
5710 if (!subctx) {
5720 debug('creating subcontext');
5730 subctx = createSubContext(false);
574 }
5750 return subctx.get(id, data);
576 }
57710 if (!entity || entity.local) {
5780 throw "No such entity: " + id;
579 }
58010 var args = getArgs(data);
58110 return entity.toString(args);
58213 };
583
58413 function getAsync(id, data, callback, defaultValue) {
58511 if (isReady) {
5860 callback(getSync(id, data));
5870 return;
588 }
58911 var overdue = false;
59011 var timeout = setTimeout(function() {
5911 overdue = true;
592 // defaultValue is just a string, can't interpolate context data
5931 callback(defaultValue);
594 }, settings.timeout);
595
59611 self.addEventListener('ready', function(event) {
597 // if the event fired too late, the default value has already been used
59810 if (overdue) {
5990 return;
600 }
60110 clearTimeout(timeout);
60210 callback(getSync(id, data));
603 });
60413 };
605
60613 this.__defineSetter__('data', function(data) {
6070 ctxdata = data;
608 });
609
61013 this.__defineGetter__('data', function() {
6110 return ctxdata;
612 });
613
61413 Object.defineProperty(this, 'settings', {
615 value: Object.create(Object.prototype, {
616 locales: {
61711 get: function() { return settings.locales },
618 set: function(val) {
6199 if (!Array.isArray(val)) {
6200 throw "Locales must be a list";
621 }
6229 if (val.length == 0) {
6230 throw "Locales list must not be empty";
624 }
6259 if (settings.locales !== null) {
6260 throw "Can't overwrite locales";
627 }
6289 settings.locales = val;
6299 Object.freeze(settings.locales);
630 },
631 configurable: false,
632 enumerable: true,
633 },
634 schemes: {
63526 get: function() { return settings.schemes },
636 set: function(val) {
6377 if (!Array.isArray(val)) {
6380 throw "Schemes must be a list";
639 }
6407 if (val.length == 0) {
6410 throw "Scheme list must not be empty";
642 }
6437 if (settings.schemes !== null) {
6440 throw "Can't overwrite schemes";
645 }
6467 settings.schemes = val;
6477 Object.freeze(settings.schemes);
648 },
649 configurable: false,
650 enumerable: true,
651 },
652 timeout: {
6530 get: function() { return settings.timeout },
654 set: function(val) {
6550 if (typeof(val) !== 'number') {
6560 throw "Timeout must be a number";
657 }
6580 settings.timeout = val;
659 },
660 configurable: false,
661 enumerable: true,
662 },
663 }),
664 writable: false,
665 enumerable: false,
666 configurable: false,
667 });
668
66913 this.getLocale = function ctx_getLocale() {
67025 if (!settings.locales || settings.locales.length == 0) {
6713 return null;
672 }
67322 return settings.locales[0];
674 };
675
676 // clear this context's ProcessedResources cache
67713 this.invalidateCache = function ctx_invalidateCache() {
6780 return cache.invalidate();
679 };
680
68113 this.addResource = function ctx_addResource(uri, async) {
68213 if (isFrozen) {
6830 throw "Context is frozen, can't add more resources";
684 }
68513 if (async === undefined) {
68611 async = true;
687 }
68813 uris.push(uri);
68913 meta.importResource(uri, _ifComplete(meta, compile),
690 invalidateOnIntegrityError, 0, async);
69113 return true;
692 };
693
69413 this.freeze = function ctx_freeze() {
69513 isFrozen = true;
69613 totalResources = uris.length;
69713 _ifComplete(meta, compile)();
69813 return true;
699 };
700
70113 this.get = function ctx_get(id, data, callback, fallback) {
70213 if (callback === undefined) {
7032 return getSync(id, data);
704 }
70511 getAsync(id, data, callback, fallback);
70611 return null;
707 };
708
70913 this.getAttribute = function ctx_getAttribute(id, attr, data) {
7100 if (!isReady) {
7110 throw "Error: context not ready";
712 }
7130 var entity = entries[id];
7140 if (!entity || entity.local) {
7150 throw "No such entity: " + id;
716 }
7170 var attribute = entity.attributes[attr]
7180 if (!attribute || attribute.local) {
7190 throw "No such attribute: " + attr;
720 }
7210 var args = getArgs(data);
7220 return attribute.toString(args);
723 };
724
72513 this.getAttributes = function ctx_getAttributes(id, data) {
7260 if (!isReady) {
7270 throw "Error: context not ready";
728 }
7290 var entity = entries[id];
7300 if (!entity || entity.local) {
7310 throw "No such entity: " + id;
732 }
7330 var args = getArgs(data);
7340 var attributes = {};
7350 for (var attr in entity.attributes) {
7360 var attribute = entity.attributes[attr];
7370 if (!attribute.local) {
7380 attributes[attr] = attribute.toString(args);
739 }
740 }
7410 return attributes;
742 }
743
74413 this.addEventListener = function ctx_addEventListener(type, listener) {
74513 return emitter.addEventListener(type, listener);
746 }
747
74813 this.removeEventListener = function ctx_removeEventListener(type, listener) {
7490 return emitter.removeEventListener(type, listener)
750 }
751 }
752}).call(this);

parser.js

31%
365
114
251
LineHitsSource
11(function() {
21 'use strict';
3
41 var Parser;
5
61 if (typeof exports !== 'undefined') {
71 Parser = exports;
8 } else {
90 Parser = this.L20n.Parser = {};
10 }
11
121 Parser.parse = function parse(string) {
1313 var lol = {type: 'LOL',
14 body: []}
1513 content = string
1613 get_ws();
1713 while (content) {
1825 lol.body.push(get_entry())
1925 get_ws();
20 }
2113 return lol
22 }
23
241 var content = null;
251 var patterns = {
26 id: /^([_a-zA-Z]\w*)/,
27 value: /^(["'])([^'"]*)(["'])/,
28 ws: /^\s+/
29 };
30
31
321 function get_ws() {
3391 content = content.replace(patterns['ws'], '')
34 }
35
361 function get_entry() {
3725 var entry
3825 if (content[0] == '<') {
3922 content = content.substr(1)
4022 var id = get_identifier()
4122 if (content[0] == '(') {
420 entry = get_macro(id)
4322 } else if (content[0] == '[') {
440 var index = get_index()
450 entry = get_entity(id, index)
46 } else {
4722 entry = get_entity(id);
48 }
493 } else if (content.substr(0,2) == '/*') {
500 entry = get_comment();
513 } else if (content.substr(0,6) == 'import') {
523 entry = get_importstatement()
53 } else {
540 throw "ParserError at get_entry"
55 }
5625 return entry
57 }
58
591 function get_importstatement() {
603 content = content.substr(6)
613 get_ws()
623 if (content[0] != '(') {
630 throw "ParserError"
64 }
653 content = content.substr(1)
663 get_ws()
673 var uri = get_string()
683 get_ws()
693 if (content[0] != ')') {
700 throw "ParserError"
71 }
723 content = content.substr(1)
733 var impStmt = {
74 type: 'ImportStatement',
75 uri: uri
76 }
773 return impStmt
78 }
79
801 function get_identifier() {
8122 if (content[0] == '~') {
82 // this expression
83 }
8422 var match = patterns['id'].exec(content)
8522 if (!match)
860 throw "ParserError at get_identifier"
8722 content = content.substr(match[0].length)
8822 var identifier = {type: 'Identifier',
89 name: match[0]}
9022 return identifier
91 }
92
931 function get_entity(id, index) {
9422 var ch = content[0]
9522 get_ws();
9622 if (content[0] == '>') {
97 // empty entity
98 }
9922 if (!/\s/g.test(ch)) {
1000 throw "ParserError at get_entity"
101 }
10222 var value = get_value(true)
10322 get_ws()
10422 var attrs = get_attributes()
10522 var entity = {
106 type: 'Entity',
107 id: id,
108 value: value,
109 index: index || [],
110 attrs: attrs,
111 local: (id.name[0] == '_')
112 }
11322 return entity
114 }
115
1161 function get_macro(id) {
1170 if (id.name[0] == '_') {
1180 throw "ParserError at get_macro"
119 }
1200 var idlist = []
1210 content = content.substr(1)
1220 get_ws()
1230 if (content[0] == ')') {
1240 content = content.substr(1)
125 } else {
1260 while (1) {
1270 idlist.push(get_variable())
1280 get_ws()
1290 if (content[0] == ',') {
1300 content = content.substr(1)
1310 get_ws()
1320 } else if (content[0] == ')') {
1330 content = content.substr(1)
1340 break
135 } else {
1360 throw "ParserError at get_macro"
137 }
138 }
139 }
1400 get_ws()
141 // we should check if the ws was empty and throw ParserError here
1420 if (content[0] != '{') {
1430 throw "ParserError at get_macro"
144 }
1450 content = content.substr(1)
1460 get_ws()
1470 var exp = get_expression()
1480 get_ws()
1490 if (content[0] != '}') {
1500 throw "ParserError at get_macro"
151 }
1520 content = content.substr(1)
1530 get_ws()
1540 var attrs = get_attributes()
1550 var macro = {
156 'type': 'Macro',
157 'id': id,
158 'args': idlist,
159 'expression': exp
160 }
161 // macro.attrs
1620 return macro
163 }
164
1651 function get_value(none) {
16622 var c = content[0]
16722 var value
16822 if (c == '"' || c == "'") {
16922 var ccc = content.substr(3)
17022 var quote = (ccc == '"""' || ccc == "'''")?ccc:c
171 //var value = get_string()
17222 value = get_complex_string(quote)
1730 } else if (c == '[') {
1740 value = get_array()
1750 } else if (c == '{') {
1760 value = get_hash()
177 }
17822 return value
179 }
180
1811 function get_string() {
1823 var match = patterns['value'].exec(content)
1833 if (!match) {
1840 throw "ParserError at get_string"
185 }
1863 content = content.substr(match[0].length)
1873 return {type: 'String', content: match[2]}
188 }
189
1901 function get_complex_string(quote) {
19122 var str_end = quote[0]
19222 var literal = new RegExp("^([^\\\{"+str_end+"]+)")
19322 var obj = []
19422 var buffer = ''
19522 content = content.substr(quote.length)
19622 var i = 0
19722 while (content.substr(0, quote.length) != quote) {
19822 i++;
19922 if (i>20)
2000 break
20122 if (content[0] == str_end) {
2020 buffer += content[0]
2030 content = content.substr(1)
204 }
20522 if (content[0] == '\\') {
2060 var jump = content.substr(1, 3) == '{{' ? 3 : 2;
2070 buffer += content.substr(1, jump)
2080 content = content.substr(jump)
209 }
21022 if (content.substr(0, 2) == '{{') {
2110 content = content.substr(2)
2120 if (buffer) {
2130 var string = {type: 'String', content: buffer}
2140 obj.push(string)
2150 buffer = ''
216 }
2170 get_ws()
2180 var expr = get_expression()
2190 obj.push(expr)
2200 if (content.substr(0, 2) != '}}') {
2210 throw "ParserError at get_complex_string"
222 }
2230 content = content.substr(2)
224 }
22522 var m = literal.exec(content)
22622 if (m) {
22722 buffer = m[1]
22822 content = content.substr(m[0].length)
229 }
230 }
23122 if (buffer) {
23222 var string = {type: 'String', content: buffer}
23322 obj.push(string)
234 }
23522 content = content.substr(quote.length)
23622 if (obj.length == 1 && obj[0].type == 'String') {
23722 return obj[0]
238 }
2390 var cs = {type: 'ComplexString', content: obj}
2400 return cs
241 }
242
2431 function get_hash() {
2440 content = content.substr(1)
2450 get_ws()
2460 if (content[0] == '}') {
2470 var h = {type: 'Hash', content: []}
2480 return h
249 }
2500 var hash = []
2510 while (1) {
2520 var defitem = false
2530 if (content[0] == '*') {
2540 content = content.substr(1)
2550 defitem = true
256 }
2570 var hi = get_kvp('HashItem')
2580 hi['default'] = defitem
2590 hash.push(hi)
2600 get_ws()
2610 if (content[0] == ',') {
2620 content = content.substr(1)
2630 get_ws()
2640 } else if (content[0] == '}') {
2650 break
266 } else {
2670 throw "ParserError in get_hash"
268 }
269 }
2700 content = content.substr(1)
2710 var h = {type: 'Hash', content: hash}
2720 return h
273 }
274
2751 function get_kvp(cl) {
2760 var key = get_identifier()
2770 get_ws()
2780 if (content[0] != ':') {
2790 throw "ParserError"
280 }
2810 content = content.substr(1)
2820 get_ws()
2830 var val = get_value()
2840 var kvp = {type: cl, key: key, value: val}
2850 return kvp
286 }
287
2881 function get_attributes() {
28922 if (content[0] == '>') {
29022 content = content.substr(1)
29122 return {}
292 }
2930 var attrs = {}
2940 while (1) {
2950 var attr = get_kvp('Attribute')
2960 attr.local = attr.key.name[0] == '_'
2970 attrs[attr.key.name] = attr
2980 var ch = content[0]
2990 get_ws()
3000 if (content[0] == '>') {
3010 content = content.substr(1)
3020 break
3030 } else if (!/^\s/.test(ch)) {
3040 throw "ParserError"
305 }
306 }
3070 return attrs
308 }
309
3101 function get_index() {
3110 content = content.substr(1)
3120 get_ws()
3130 var index = []
3140 if (content[0] == ']') {
3150 content = content.substr(1)
3160 return index
317 }
3180 while (1) {
3190 var expression = get_expression()
3200 index.push(expression)
3210 get_ws()
3220 if (content[0] == ',') {
3230 content = content.substr(1)
3240 } else if (content[0] == ']') {
3250 break
326 } else {
3270 throw "ParserError in get_index"
328 }
329 }
3300 content = content.substr(1)
3310 return index
332 }
333
3341 function get_expression() {
3350 return get_conditional_expression()
3360 var exp = get_primary_expression()
3370 get_ws()
3380 return exp
339 }
340
3411 function get_conditional_expression() {
3420 var or_expression = get_or_expression()
3430 get_ws()
3440 if (content[0] != '?') {
3450 return or_expression
346 }
3470 content = content.substr(1)
3480 var consequent = get_expression()
3490 get_ws()
3500 if (content[0] != ':') {
3510 throw "ParserError in get_conditional_expression"
352 }
3530 content = content.substr(1)
3540 get_ws()
3550 var alternate = get_expression()
3560 var cons_exp = {
357 'type': 'ConditionalExpression',
358 'test': or_expression,
359 'consequent': consequent,
360 'alternate': alternate
361 }
3620 return cons_exp
363 }
364
3651 function get_prefix_expression(token, token_length, cl, op, nxt) {
3660 var exp = nxt()
3670 get_ws()
3680 while (token.indexOf(content.substr(0, token_length)) !== -1) {
3690 var t = content.substr(0, token_length)
3700 content = content.substr(token_length)
3710 get_ws()
3720 op.token = t
3730 cl.left = exp
3740 cl.right = nxt()
3750 get_ws()
376 }
3770 return exp
378 }
379
3801 function get_or_expression() {
3810 var token=['||',]
3820 var cl = {
383 'type': 'LogicalExpression',
384 }
3850 var op = {
386 'type': 'LogicalOperator',
387 }
3880 return get_prefix_expression(token, 2, cl, op, get_and_expression)
389 }
390
3911 function get_and_expression() {
3920 var token=['&&',]
3930 var cl = {
394 'type': 'LogicalExpression',
395 }
3960 var op = {
397 'type': 'LogicalOperator',
398 }
3990 return get_prefix_expression(token, 2, cl, op, get_equality_expression)
400 }
401
4021 function get_equality_expression() {
4030 var token=['==',]
4040 var cl = {
405 'type': 'BinaryExpression',
406 }
4070 var op = {
408 'type': 'BinaryOperator',
409 }
4100 return get_prefix_expression(token, 2, cl, op, get_member_expression)
411 }
412
4131 function get_member_expression() {
4140 var exp = get_primary_expression()
4150 var match = content.match(/^(\w*)/)
4160 var ws_post_id = ""
417
4180 if (match) {
4190 ws_post_id = match[1]
4200 content = content.substr(ws_post_id.length)
421 }
4220 get_ws()
4230 var matched = false
4240 while (1) {
4250 if (['.[', '..'].indexOf(content.substr(2)) !== -1) {
4260 exp = get_attr_expression(exp, ws_post_id)
4270 matched = true
4280 } else if (['[', '.'].indexOf(content[0]) !== -1) {
4290 exp = get_property_expression(exp, ws_post_id)
4300 matched = true
4310 } else if (content[0] == '(') {
4320 exp = get_call_expression(exp, ws_post_id)
4330 matched = true
434 } else {
4350 break
436 }
437 }
4380 if (!matched) {
4390 content = ws_post_id + content
440 }
4410 return exp
442 }
443
4441 function get_primary_expression() {
445 // number
4460 var match = content.match(/^(\d+)/)
4470 if (match) {
4480 var d = parseInt(match[1])
4490 content = content.substr(match[1].length)
4500 return {
451 'type': 'Literal',
452 'value': d
453 }
454 }
455 // value
4560 if (["'",'"','{','['].indexOf(content[0]) !== -1) {
4570 return get_value()
458 }
459 // variable
4600 if (content[0] == '$') {
4610 return get_variable()
462 }
463 // globals
4640 if (content[0] == '@') {
4650 content = content.substr(1)
4660 var id = get_identifier()
4670 var ge = {type: 'GlobalsExpression', id: id}
4680 return ge
469 }
4700 return get_identifier()
471 }
472
4731 function get_variable() {
4740 content = content.substr(1)
4750 var id = get_identifier()
4760 var ve = {type: 'VariableExpression', id: id}
4770 return ve
478 }
479
4801 function get_property_expression(idref, ws_post_id) {
4810 var d= content[0]
4820 if (d == '[') {
4830 content = content.substr(1)
4840 get_ws()
4850 var exp = get_member_expression()
4860 get_ws()
4870 if (content[0] != ']') {
4880 throw "ParserError in get_property_expression"
489 }
4900 content = content.substr(1)
4910 var prop = {
492 'type': 'PropertyExpression',
493 'expression': idref,
494 'property': exp,
495 'computed': true
496 }
4970 return prop
4980 } else if (d == '.') {
4990 content = content.substr(1)
5000 var prop = get_identifier()
5010 var pe = {
502 'type': 'PropertyExpression',
503 'expression': idref,
504 'property': prop,
505 'computed': false
506 }
5070 return pe
508 } else {
5090 throw "ParserError in get_property_expression"
510 }
511 }
512
5131 function get_call_expression(callee, ws_post_id) {
5140 var mcall = {
515 'type': 'CallExpression',
516 'callee': callee,
517 'arguments': [],
518 }
5190 content = content.substr(1)
5200 get_ws()
5210 if (content[0] == ')') {
5220 content = content.substr(1)
5230 return mcall
524 }
5250 while (1) {
5260 var exp = get_expression()
5270 mcall.arguments.push(exp)
5280 get_ws()
5290 if (content[0] == ',') {
5300 content = content.substr(1)
5310 get_ws()
5320 } else if (content[0] == ')') {
5330 break
534 } else {
5350 throw "ParserError in get_call_expression"
536 }
537 }
5380 content = content.substr(1)
5390 return mcall
540 }
541
5421 function get_comment() {
5430 var pos = content.indexOf('*/')
5440 if (pos === -1) {
5450 throw "ParserError in get_comment"
546 }
5470 var c = content.substr(2, pos-2)
5480 content = content.substr(pos+2)
5490 return {
550 'type': 'Comment',
551 'content': c
552 }
553 }
554
555}).call(this);

compiler.js

30%
230
69
161
LineHitsSource
1// This is L20n's on-the-fly compiler. It takes the AST produced by the parser
2// and uses it to create a set of JavaScript objects and functions representing
3// entities and macros and other expressions.
4//
5// The module defines a `Compiler` singleton with a single method: `compile`.
6// The result of the compilation is stored on the `entries` object passed as
7// the second argument to the `compile` function. The third argument is
8// `globals`, an object whose properties provide information about the runtime
9// environment, e.g., the current hour, operating system etc.
10//
11// Main concepts
12// -------------
13//
14// **Entities** and **attributes** are objects which are publicly available.
15// Their `toString` method is designed to be used by the L20n context to get
16// a string value of the entity, given the context data passed to the method.
17//
18// All other symbols defined by the grammar are implemented as expression
19// functions. The naming convention is:
20//
21// - capitalized first letters denote **expressions constructors**, e.g.
22// `PropertyExpression`.
23// - camel-case denotes **expression functions** returned by the
24// constructors, e.g. `propertyExpression`.
25//
26// ### Constructors
27//
28// The constructor is called for every node in the AST. It stores the
29// components of the expression which are constant and do not depend on the
30// calling context (an example of the latter would be the data passed by the
31// developer to the `toString` method).
32//
33// ### Expression functions
34//
35// The constructor, when called, returns an expression function, which, in
36// turn, is called every time the expression needs to be evaluated. The
37// evaluation call is context-dependend. Every expression function takes three
38// mandatory arguments and one optional one:
39//
40// - `locals`, which stores the information about the currently evaluated
41// entity (`locals.__this__`). It also stores the arguments passed to macros.
42// - `env`, which combines `entries` (all other entities and macros) and
43// `globals` passed to `Compiler.compile`.
44// - `data`, which is an object with data passed to the context by the
45// developer. The developer can define data on the context, or pass it on
46// a per-call basis.
47// - `key` (optional), which is a number or a string passed to an
48// `ArrayLiteral` or a `HashLiteral` expression denoting the member of the
49// array or the hash to return. The member will be another expression function
50// which can then be evaluated further.
51//
52//
53// Bubbling up the new _current_ entity
54// ------------------------------------
55//
56// Every expression function returns an array [`newLocals`, `evaluatedValue`].
57// The reason for this, and in particular for returning `newLocals`, is
58// important for understanding how the compiler works.
59//
60// In most of the cases. `newLocals` will be the same as the original `locals`
61// passed to the expression function during the evaluation call. In some
62// cases, however, `newLocals.__this__` will reference a different entity than
63// `locals.__this__` did. On runtime, as the compiler traverses the AST and
64// goes deeper into individual branches, when it hits an `identifier` and
65// evaluates it to an entity, it needs to **bubble up** this find back to the
66// top expressions in the chain. This is so that the evaluation of the
67// top-most expressions in the branch (root being at the very top of the tree)
68// takes into account the new value of `__this__`.
69//
70// To illustrate this point, consider the following example.
71//
72// Two entities, `brandName` and `about` are defined as such:
73//
74// <brandName {
75// short: "Firefox",
76// long: "Mozilla {{ ~ }}"
77// }>
78// <about "About {{ brandName.long }}">
79//
80// Notice two `complexString`s: `about` references `brandName.long`, and
81// `brandName.long` references its own entity via `~`. This `~` (meaning, the
82// current entity) must always reference `brandName`, even when called from
83// `about`.
84//
85// The AST for the `about` entity looks like this:
86//
87// [Entity]
88// .id[Identifier]
89// .name[unicode "about"]
90// .index
91// .value[ComplexString] <1>
92// .content
93// [String] <2>
94// .content[unicode "About "]
95// [PropertyExpression] <3>
96// .expression[Identifier] <4>
97// .name[unicode "brandName"]
98// .property[Identifier]
99// .name[unicode "long"]
100// .computed[bool=False]
101// .attrs
102// .local[bool=False]
103//
104// During the compilation the compiler will walk the AST top-down to the
105// deepest terminal leaves and will use expression constructors to create
106// expression functions for the components. For instance, for `about`'s value,
107// the compiler will call `ComplexString()` to create an expression function
108// `complexString` <1> which will be assigned to the entity's value. The
109// `ComplexString` construtor, before it returns the `complexString` <1>, will
110// in turn call other expression constructors to create `content`:
111// a `stringLiteral` and a `propertyExpression`. The `PropertyExpression`
112// contructor will do the same, etc...
113//
114// When `entity.toString(ctxdata)` is called by a third-party code, we need to
115// resolve the whole `complexString` <1> to return a single string value. This
116// is what **resolving** means and it involves some recursion. On the other
117// hand, **evaluating** means _to call the expression once and use what it
118// returns_.
119//
120// `toString` sets `locals.__this__` to the current entity, `about` and tells
121// the `complexString` <1> to _resolve_ itself.
122//
123// In order to resolve the `complexString` <1>, we start by resolving its first
124// member <2> to a string. As we resolve deeper down, we bubble down `locals`
125// set by `toString`. The first member of `content` turns out to simply be
126// a string that reads `About `.
127//
128// On to the second member, the propertyExpression <3>. We bubble down
129// `locals` again and proceed to evaluate the `expression` field, which is an
130// `identifier`. Note that we don't _resolve_ it to a string; we _evaluate_ it
131// to something that can be further used in other expressions, in this case, an
132// **entity** called `brandName`.
133//
134// Had we _resolved_ the `propertyExpression`, it would have resolve to
135// a string, and it would have been impossible to access the `long` member.
136// This leads us to an important concept: the compiler _resolves_ expressions
137// when it expects a primitive value (a string, a number, a bool). On the
138// other hand, it _evaluates_ expressions (calls them only once) when it needs
139// to work with them further, e.g. in order to access a member of the hash.
140//
141// This also explains why in the above example, once the compiler hits the
142// `brandName` identifier and changes the value of `locals.__this__` to the
143// `brandName` entity, this value doesn't bubble up all the way up to the
144// `about` entity. All components of any `complexString` are _resolved_ by
145// the compiler until a primitive value is returned. This logic lives in the
146// `_resolve` function.
147
148//
149// Inline comments
150// ---------------
151//
152// Isolate the code by using an immediately-invoked function expression.
153// Invoke it via `(function(){ ... }).call(this)` so that inside of the IIFE,
154// `this` references the global object.
1551(function() {
1561 'use strict';
157
1581 var Compiler;
159
160 // Depending on the environment the script is run in, define `Compiler` as
161 // the exports object which can be `required` as a module, or as a member of
162 // the L20n object defined on the global object in the browser, i.e.
163 // `window`.
1641 if (typeof exports !== 'undefined') {
1651 Compiler = exports;
166 } else {
1670 Compiler = this.L20n.Compiler = {};
168 }
169
170 // `Compiler.compile` is the only publicly visible method. It takes three
171 // arguments: `ast`, the AST produced by the parser; `entries`, an object
172 // which will be populted with compiled entities and macros (their `id`s will
173 // be used asthe keys of the `entries` object; and `globals`, an object
174 // whose properties can be accessed to get information about the runtime
175 // environment.
1761 Compiler.compile = function compile(ast, entries, globals) {
177 // `entries` and `globals` are grouped into an `env` object throught the
178 // file
17910 var env = {
180 entries: entries,
181 globals: globals,
182 };
18310 for (var i = 0, entry; entry = ast[i]; i++) {
18421 if (entry.type == 'Entity') {
18521 env.entries[entry.id.name] = new Entity(entry, env);
1860 } else if (entry.type == 'Macro') {
1870 env.entries[entry.id.name] = new Macro(entry);
188 }
189 }
190 }
191
192 // The Entity object.
1931 function Entity(node, env) {
19421 this.id = node.id;
19521 this.value = Expression(node.value);
19621 this.index = [];
19721 node.index.forEach(function(ind) {
1980 this.index.push(Expression(ind));
199 }, this);
20021 this.attributes = {};
20121 for (var key in node.attrs) {
2020 this.attributes[key] = new Attribute(node.attrs[key], this);
203 }
20421 this.local = node.local || false;
20521 this.env = env;
206 }
207 // Entities are wrappers around their value expression. _Yielding_ from the
208 // entity is identical to _evaluating_ its value with the appropriate value
209 // of `locals.__this__`. See `PropertyExpression` for an example usage.
2101 Entity.prototype._yield = function E_yield(data, key) {
2110 var locals = {
212 __this__: this,
213 };
2140 return this.value(locals, this.env, data, key);
215 };
216 // Calling `entity._resolve` will _resolve_ its value to a primitive value.
217 // See `ComplexString` for an example usage.
2181 Entity.prototype._resolve = function E_resolve(data, index) {
21910 index = index || this.index;
22010 var locals = {
221 __this__: this,
222 };
22310 return _resolve(this.value, locals, this.env, data, index);
224 };
225 // `toString` is the only method that is supposed to be used by the L20n's
226 // context.
2271 Entity.prototype.toString = function toString(data) {
22810 return this._resolve(data);
229 };
230
2311 function Attribute(node, entity) {
2320 this.key = node.key.name;
2330 this.local = node.local || false;
2340 this.value = Expression(node.value);
2350 this.entity = entity;
236 }
2371 Attribute.prototype._yield = function A_yield(data, key) {
2380 var locals = {
239 __this__: this.entity,
240 };
2410 return this.value(locals, this.entity.env, data, key);
242 };
2431 Attribute.prototype._resolve = function A_resolve(data, index) {
2440 index = index || this.entity.index;
2450 var locals = {
246 __this__: this.entity,
247 };
2480 return _resolve(this.value, locals, this.entity.env, data, index);
249 };
2501 Attribute.prototype.toString = function toString(data) {
2510 return this._resolve(data);
252 };
253
2541 function Macro(node) {
2550 var expression = Expression(node.expression);
2560 return function(locals, env, data, args) {
2570 node.args.forEach(function(arg, i) {
2580 locals[arg.id.name] = args[i];
259 });
2600 return expression(locals, env, data);
261 };
262 }
263
264 // The 'dispatcher' expression constructor. Other expression constructors
265 // call this to create expression functions for their components. For
266 // instance, `ConditionalExpression` calls `Expression` to create expression
267 // functions for its `test`, `consequent` and `alternate` symbols.
2681 function Expression(node) {
26921 var EXPRESSION_TYPES = {
270 // Primary expressions.
271 'Identifier': Identifier,
272 'ThisExpression': ThisExpression,
273 'VariableExpression': VariableExpression,
274 'GlobalsExpression': GlobalsExpression,
275
276 // Value expressions.
277 'Literal': NumberLiteral,
278 'String': StringLiteral,
279 'Array': ArrayLiteral,
280 'Hash': HashLiteral,
281 'HashItem': HashItem,
282 'ComplexString': ComplexString,
283
284 // Logical expressions.
285 'UnaryExpression': UnaryExpression,
286 'BinaryExpression': BinaryExpression,
287 'LogicalExpression': LogicalExpression,
288 'ConditionalExpression': ConditionalExpression,
289
290 // Member expressions.
291 'CallExpression': CallExpression,
292 'PropertyExpression': PropertyExpression,
293 'AttributeExpression': AttributeExpression,
294 'ParenthesisExpression': ParenthesisExpression,
295 };
296 // An entity can have no value. It will be resolved to `null`.
29721 if (!node) {
2980 return null;
299 }
30021 try {
30121 var expr = EXPRESSION_TYPES[node.type](node);
302 } catch(e) {
3030 throw new Error('Unknown expression type');
304 }
30521 return expr;
306 }
307
3081 function _resolve(expr, locals, env, data, index) {
309 // Bail out early if it's a primitive value or `null`. This is exactly
310 // what we want.
31120 if (!expr ||
312 typeof expr === 'string' ||
313 typeof expr === 'boolean' ||
314 typeof expr === 'number') {
31510 return expr;
316 }
317 // Check if `expr` knows how to resolve itself (if it's an Entity or an
318 // Attribute).
31910 if (expr._resolve) {
3200 return expr._resolve(data, index);
321 }
32210 index = index || [];
32310 var key = index.shift();
324 // `var [locals, current] = expr(...)` is not ES5 (V8 doesn't support it)
32510 var current = expr(locals, env, data, key);
32610 locals = current[0], current = current[1];
32710 return _resolve(current, locals, env, data, index);
328 }
329
3301 function Identifier(node) {
3310 var name = node.name;
3320 return function identifier(locals, env, data) {
3330 var entity = env.entries[name]
3340 return [{ __this__: entity }, entity]
335 };
336 }
3371 function ThisExpression(node) {
3380 return function thisExpression(locals, env, data) {
3390 return [locals, locals.__this__];
340 };
341 }
3421 function VariableExpression(node) {
3430 return function variableExpression(locals, env, data) {
3440 var value = locals[node.id.name];
3450 if (value !== undefined)
3460 return value;
3470 return [locals, data[node.id.name]];
348 };
349 }
3501 function GlobalsExpression(node) {
3510 return function globalsExpression(locals, env, data) {
3520 return [locals, env.globals[node.id.name]];
353 };
354 }
3551 function NumberLiteral(node) {
3560 return function numberLiteral(locals, env, data) {
3570 return [locals, node.value];
358 };
359 }
3601 function StringLiteral(node) {
36121 return function stringLiteral(locals, env, data) {
36210 return [locals, node.content];
363 };
364 }
3651 function ArrayLiteral(node) {
3660 var content = [];
3670 node.content.forEach(function(elem, i) {
3680 content.push(Expression(elem));
369 });
3700 return function arrayLiteral(locals, env, data, key) {
3710 key = _resolve(key, locals, env, data);
3720 if (key && content[key]) {
3730 return [locals, content[key]];
374 } else {
375 // For Arrays, the default key is always 0. The syntax does not allow
376 // specifying a different default with an asterisk, like in hashes.
3770 return [locals, content[0]];
378 }
379 };
380 }
3811 function HashLiteral(node) {
3820 var content = [];
3830 var defaultKey = null;
3840 node.content.forEach(function(elem, i) {
3850 content[elem.key.name] = HashItem(elem);
3860 if (i == 0 || elem['default'])
3870 defaultKey = elem.key.name;
388 });
3890 return function hashLiteral(locals, env, data, key) {
3900 key = _resolve(key, locals, env, data);
3910 if (key && content[key]) {
3920 return [locals, content[key]];
393 } else {
3940 return [locals, content[defaultKey]];
395 }
396 };
397 }
3981 function HashItem(node) {
399 // return the value expression right away
400 // the `key` and the `default` flag logic is done in `HashLiteral`
4010 return Expression(node.value)
402 }
4031 function ComplexString(node) {
4040 var content = [];
4050 node.content.forEach(function(elem) {
4060 content.push(Expression(elem));
407 });
408 // Every complexString needs to have its own `dirty` flag whose state
409 // persists across multiple calls to the given complexString. On the other
410 // hand, `dirty` must not be shared by all complexStrings. Hence the need
411 // to define `dirty` as a variable available in the closure. Note that the
412 // anonymous function is a self-invoked one and it returns the closure
413 // immediately.
4140 return function() {
4150 var dirty = false;
4160 return function complexString(locals, env, data) {
4170 if (dirty) {
4180 throw new Error("Cyclic reference detected");
419 }
4200 dirty = true;
4210 var parts = [];
4220 content.forEach(function resolveElemOfComplexString(elem) {
4230 var part = _resolve(elem, locals, env, data);
4240 parts.push(part);
425 });
4260 dirty = false;
4270 return [locals, parts.join('')];
428 }
429 }();
430 }
431
4321 function UnaryOperator(token) {
4330 if (token == '-') return function negativeOperator(argument) {
4340 return -argument;
435 };
4360 if (token == '+') return function positiveOperator(argument) {
4370 return +argument;
438 };
4390 if (token == '!') return function notOperator(argument) {
4400 return !argument;
441 };
4420 throw new Error("Unknown token: " + token);
443 }
4441 function BinaryOperator(token) {
4450 if (token == '==') return function equalOperator(left, right) {
4460 return left == right;
447 };
4480 if (token == '!=') return function notEqualOperator(left, right) {
4490 return left != right;
450 };
4510 if (token == '<') return function lessThanOperator(left, right) {
4520 return left < right;
453 };
4540 if (token == '<=') return function lessThanEqualOperator(left, right) {
4550 return left <= right;
456 };
4570 if (token == '>') return function greaterThanOperator(left, right) {
4580 return left > right;
459 };
4600 if (token == '>=') return function greaterThanEqualOperator(left, right) {
4610 return left >= right;
462 };
4630 if (token == '+') return function addOperator(left, right) {
4640 return left + right;
465 };
4660 if (token == '-') return function substractOperator(left, right) {
4670 return left - right;
468 };
4690 if (token == '*') return function multiplyOperator(left, right) {
4700 return left * right;
471 };
4720 if (token == '/') return function devideOperator(left, right) {
4730 return left / right;
474 };
4750 if (token == '%') return function moduloOperator(left, right) {
4760 return left % right;
477 };
4780 throw new Error("Unknown token: " + token);
479 }
4801 function LogicalOperator(token) {
4810 if (token == '&&') return function andOperator(left, right) {
4820 return left && right;
483 };
4840 if (token == '||') return function orOperator(left, right) {
4850 return left || right;
486 };
4870 throw new Error("Unknown token: " + token);
488 }
4891 function UnaryExpression(node) {
4900 var operator = UnaryOperator(node.operator.token);
4910 var argument = Expression(node.argument);
4920 return function unaryExpression(locals, env, data) {
4930 return [locals, operator(_resolve(argument, locals, env, data))];
494 };
495 }
4961 function BinaryExpression(node) {
4970 var left = Expression(node.left);
4980 var operator = BinaryOperator(node.operator.token);
4990 var right = Expression(node.right);
5000 return function binaryExpression(locals, env, data) {
5010 return [locals, operator(
502 _resolve(left, locals, env, data),
503 _resolve(right, locals, env, data)
504 )];
505 };
506 }
5071 function LogicalExpression(node) {
5080 var left = Expression(node.left);
5090 var operator = LogicalOperator(node.operator.token);
5100 var right = Expression(node.right);
5110 return function logicalExpression(locals, env, data) {
5120 return [locals, operator(
513 _resolve(left, locals, env, data),
514 _resolve(right, locals, env, data)
515 )];
516 }
517 }
5181 function ConditionalExpression(node) {
5190 var test = Expression(node.test);
5200 var consequent = Expression(node.consequent);
5210 var alternate = Expression(node.alternate);
5220 return function conditionalExpression(locals, env, data) {
5230 if (_resolve(test, locals, env, data)) {
5240 return consequent(locals, env, data);
525 }
5260 return alternate(locals, env, data);
527 };
528 }
529
5301 function CallExpression(node) {
5310 var callee = Expression(node.callee);
5320 var args = [];
5330 node.arguments.forEach(function(elem, i) {
5340 args.push(Expression(elem));
535 });
5360 return function callExpression(locals, env, data) {
5370 var evaluated_args = [];
5380 args.forEach(function(arg, i) {
5390 evaluated_args.push(arg(locals, env, data));
540 });
541 // callee is an expression pointing to a macro, e.g. an identifier
542 // XXX what if it doesn't point to a macro?
5430 var macro = callee(locals, env, data);
5440 locals = macro[0], macro = macro[1];
545 // rely entirely on the platform implementation to detect recursion
5460 return macro(locals, env, data, evaluated_args);
547 };
548 }
5491 function PropertyExpression(node) {
5500 var expression = Expression(node.expression);
5510 var property = node.computed ?
552 Expression(node.property) :
553 node.property.name;
5540 return function propertyExpression(locals, env, data) {
5550 var prop = _resolve(property, locals, env, data);
5560 var parent = expression(locals, env, data);
5570 locals = parent[0], parent = parent[1];
558 // If `parent` is an Entity or an Attribute, evaluate its value via the
559 // `_yield` method. This will ensure the correct value of
560 // `locals.__this__`.
5610 if (parent._yield) {
5620 return parent._yield(data, prop);
563 }
564 // If `parent` is an object passed by the developer to the context (i.e.,
565 // `expression` was a `VariableExpression`), simply return the member of
566 // the object corresponding to `prop`. We don't really care about
567 // `locals` here.
5680 if (typeof parent !== 'function') {
5690 return [locals, parent[prop]];
570 }
5710 return parent(locals, env, data, prop);
572 }
573 }
5741 function AttributeExpression(node) {
575 // XXX looks similar to PropertyExpression, but it's actually closer to
576 // Identifier
5770 var expression = Expression(node.expression);
5780 var attribute = node.computed ?
579 Expression(node.attribute) :
580 node.attribute.name;
5810 return function attributeExpression(locals, env, data) {
5820 var attr = _resolve(attribute, locals, env, data);
5830 var entity = expression(locals, env, data);
5840 locals = entity[0], entity = entity[1];
585 // XXX what if it's not an entity?
5860 return [locals, entity.attributes[attr]];
587 }
588 }
5891 function ParenthesisExpression(node) {
5900 return Expression(node.expression);
591 }
592
593}).call(this);
Coverage

Coverage

94%
230
218
12

compiler.js

94%
230
218
12
LineHitsSource
1// This is L20n's on-the-fly compiler. It takes the AST produced by the parser
2// and uses it to create a set of JavaScript objects and functions representing
3// entities and macros and other expressions.
4//
5// The module defines a `Compiler` singleton with a single method: `compile`.
6// The result of the compilation is stored on the `entries` object passed as
7// the second argument to the `compile` function. The third argument is
8// `globals`, an object whose properties provide information about the runtime
9// environment, e.g., the current hour, operating system etc.
10//
11// Main concepts
12// -------------
13//
14// **Entities** and **attributes** are objects which are publicly available.
15// Their `toString` method is designed to be used by the L20n context to get
16// a string value of the entity, given the context data passed to the method.
17//
18// All other symbols defined by the grammar are implemented as expression
19// functions. The naming convention is:
20//
21// - capitalized first letters denote **expressions constructors**, e.g.
22// `PropertyExpression`.
23// - camel-case denotes **expression functions** returned by the
24// constructors, e.g. `propertyExpression`.
25//
26// ### Constructors
27//
28// The constructor is called for every node in the AST. It stores the
29// components of the expression which are constant and do not depend on the
30// calling context (an example of the latter would be the data passed by the
31// developer to the `toString` method).
32//
33// ### Expression functions
34//
35// The constructor, when called, returns an expression function, which, in
36// turn, is called every time the expression needs to be evaluated. The
37// evaluation call is context-dependend. Every expression function takes three
38// mandatory arguments and one optional one:
39//
40// - `locals`, which stores the information about the currently evaluated
41// entity (`locals.__this__`). It also stores the arguments passed to macros.
42// - `env`, which combines `entries` (all other entities and macros) and
43// `globals` passed to `Compiler.compile`.
44// - `data`, which is an object with data passed to the context by the
45// developer. The developer can define data on the context, or pass it on
46// a per-call basis.
47// - `key` (optional), which is a number or a string passed to an
48// `ArrayLiteral` or a `HashLiteral` expression denoting the member of the
49// array or the hash to return. The member will be another expression function
50// which can then be evaluated further.
51//
52//
53// Bubbling up the new _current_ entity
54// ------------------------------------
55//
56// Every expression function returns an array [`newLocals`, `evaluatedValue`].
57// The reason for this, and in particular for returning `newLocals`, is
58// important for understanding how the compiler works.
59//
60// In most of the cases. `newLocals` will be the same as the original `locals`
61// passed to the expression function during the evaluation call. In some
62// cases, however, `newLocals.__this__` will reference a different entity than
63// `locals.__this__` did. On runtime, as the compiler traverses the AST and
64// goes deeper into individual branches, when it hits an `identifier` and
65// evaluates it to an entity, it needs to **bubble up** this find back to the
66// top expressions in the chain. This is so that the evaluation of the
67// top-most expressions in the branch (root being at the very top of the tree)
68// takes into account the new value of `__this__`.
69//
70// To illustrate this point, consider the following example.
71//
72// Two entities, `brandName` and `about` are defined as such:
73//
74// <brandName {
75// short: "Firefox",
76// long: "Mozilla {{ ~ }}"
77// }>
78// <about "About {{ brandName.long }}">
79//
80// Notice two `complexString`s: `about` references `brandName.long`, and
81// `brandName.long` references its own entity via `~`. This `~` (meaning, the
82// current entity) must always reference `brandName`, even when called from
83// `about`.
84//
85// The AST for the `about` entity looks like this:
86//
87// [Entity]
88// .id[Identifier]
89// .name[unicode "about"]
90// .index
91// .value[ComplexString] <1>
92// .content
93// [String] <2>
94// .content[unicode "About "]
95// [PropertyExpression] <3>
96// .expression[Identifier] <4>
97// .name[unicode "brandName"]
98// .property[Identifier]
99// .name[unicode "long"]
100// .computed[bool=False]
101// .attrs
102// .local[bool=False]
103//
104// During the compilation the compiler will walk the AST top-down to the
105// deepest terminal leaves and will use expression constructors to create
106// expression functions for the components. For instance, for `about`'s value,
107// the compiler will call `ComplexString()` to create an expression function
108// `complexString` <1> which will be assigned to the entity's value. The
109// `ComplexString` construtor, before it returns the `complexString` <1>, will
110// in turn call other expression constructors to create `content`:
111// a `stringLiteral` and a `propertyExpression`. The `PropertyExpression`
112// contructor will do the same, etc...
113//
114// When `entity.toString(ctxdata)` is called by a third-party code, we need to
115// resolve the whole `complexString` <1> to return a single string value. This
116// is what **resolving** means and it involves some recursion. On the other
117// hand, **evaluating** means _to call the expression once and use what it
118// returns_.
119//
120// `toString` sets `locals.__this__` to the current entity, `about` and tells
121// the `complexString` <1> to _resolve_ itself.
122//
123// In order to resolve the `complexString` <1>, we start by resolving its first
124// member <2> to a string. As we resolve deeper down, we bubble down `locals`
125// set by `toString`. The first member of `content` turns out to simply be
126// a string that reads `About `.
127//
128// On to the second member, the propertyExpression <3>. We bubble down
129// `locals` again and proceed to evaluate the `expression` field, which is an
130// `identifier`. Note that we don't _resolve_ it to a string; we _evaluate_ it
131// to something that can be further used in other expressions, in this case, an
132// **entity** called `brandName`.
133//
134// Had we _resolved_ the `propertyExpression`, it would have resolve to
135// a string, and it would have been impossible to access the `long` member.
136// This leads us to an important concept: the compiler _resolves_ expressions
137// when it expects a primitive value (a string, a number, a bool). On the
138// other hand, it _evaluates_ expressions (calls them only once) when it needs
139// to work with them further, e.g. in order to access a member of the hash.
140//
141// This also explains why in the above example, once the compiler hits the
142// `brandName` identifier and changes the value of `locals.__this__` to the
143// `brandName` entity, this value doesn't bubble up all the way up to the
144// `about` entity. All components of any `complexString` are _resolved_ by
145// the compiler until a primitive value is returned. This logic lives in the
146// `_resolve` function.
147
148//
149// Inline comments
150// ---------------
151//
152// Isolate the code by using an immediately-invoked function expression.
153// Invoke it via `(function(){ ... }).call(this)` so that inside of the IIFE,
154// `this` references the global object.
1551(function() {
1561 'use strict';
157
1581 var Compiler;
159
160 // Depending on the environment the script is run in, define `Compiler` as
161 // the exports object which can be `required` as a module, or as a member of
162 // the L20n object defined on the global object in the browser, i.e.
163 // `window`.
1641 if (typeof exports !== 'undefined') {
1651 Compiler = exports;
166 } else {
1670 Compiler = this.L20n.Compiler = {};
168 }
169
170 // `Compiler.compile` is the only publicly visible method. It takes three
171 // arguments: `ast`, the AST produced by the parser; `entries`, an object
172 // which will be populted with compiled entities and macros (their `id`s will
173 // be used asthe keys of the `entries` object; and `globals`, an object
174 // whose properties can be accessed to get information about the runtime
175 // environment.
1761 Compiler.compile = function compile(ast, entries, globals) {
177 // `entries` and `globals` are grouped into an `env` object throught the
178 // file
179248 var env = {
180 entries: entries,
181 globals: globals,
182 };
183248 for (var i = 0, entry; entry = ast[i]; i++) {
1844152 if (entry.type == 'Entity') {
1853306 env.entries[entry.id.name] = new Entity(entry, env);
186846 } else if (entry.type == 'Macro') {
187783 env.entries[entry.id.name] = new Macro(entry);
188 }
189 }
190 }
191
192 // The Entity object.
1931 function Entity(node, env) {
1943306 this.id = node.id;
1953306 this.value = Expression(node.value);
1963306 this.index = [];
1973306 node.index.forEach(function(ind) {
198562 this.index.push(Expression(ind));
199 }, this);
2003306 this.attributes = {};
2013306 for (var key in node.attrs) {
202108 this.attributes[key] = new Attribute(node.attrs[key], this);
203 }
2043306 this.local = node.local || false;
2053306 this.env = env;
206 }
207 // Entities are wrappers around their value expression. _Yielding_ from the
208 // entity is identical to _evaluating_ its value with the appropriate value
209 // of `locals.__this__`. See `PropertyExpression` for an example usage.
2101 Entity.prototype._yield = function E_yield(data, key) {
21143 var locals = {
212 __this__: this,
213 };
21443 return this.value(locals, this.env, data, key);
215 };
216 // Calling `entity._resolve` will _resolve_ its value to a primitive value.
217 // See `ComplexString` for an example usage.
2181 Entity.prototype._resolve = function E_resolve(data, index) {
219268 index = index || this.index;
220268 var locals = {
221 __this__: this,
222 };
223268 return _resolve(this.value, locals, this.env, data, index);
224 };
225 // `toString` is the only method that is supposed to be used by the L20n's
226 // context.
2271 Entity.prototype.toString = function toString(data) {
228107 return this._resolve(data);
229 };
230
2311 function Attribute(node, entity) {
232108 this.key = node.key.name;
233108 this.local = node.local || false;
234108 this.value = Expression(node.value);
235108 this.entity = entity;
236 }
2371 Attribute.prototype._yield = function A_yield(data, key) {
2384 var locals = {
239 __this__: this.entity,
240 };
2414 return this.value(locals, this.entity.env, data, key);
242 };
2431 Attribute.prototype._resolve = function A_resolve(data, index) {
2446 index = index || this.entity.index;
2456 var locals = {
246 __this__: this.entity,
247 };
2486 return _resolve(this.value, locals, this.entity.env, data, index);
249 };
2501 Attribute.prototype.toString = function toString(data) {
2510 return this._resolve(data);
252 };
253
2541 function Macro(node) {
255783 var expression = Expression(node.expression);
256783 return function(locals, env, data, args) {
25721970 node.args.forEach(function(arg, i) {
25821974 locals[arg.id.name] = args[i];
259 });
26021970 return expression(locals, env, data);
261 };
262 }
263
264 // The 'dispatcher' expression constructor. Other expression constructors
265 // call this to create expression functions for their components. For
266 // instance, `ConditionalExpression` calls `Expression` to create expression
267 // functions for its `test`, `consequent` and `alternate` symbols.
2681 function Expression(node) {
26923108 var EXPRESSION_TYPES = {
270 // Primary expressions.
271 'Identifier': Identifier,
272 'ThisExpression': ThisExpression,
273 'VariableExpression': VariableExpression,
274 'GlobalsExpression': GlobalsExpression,
275
276 // Value expressions.
277 'Literal': NumberLiteral,
278 'String': StringLiteral,
279 'Array': ArrayLiteral,
280 'Hash': HashLiteral,
281 'HashItem': HashItem,
282 'ComplexString': ComplexString,
283
284 // Logical expressions.
285 'UnaryExpression': UnaryExpression,
286 'BinaryExpression': BinaryExpression,
287 'LogicalExpression': LogicalExpression,
288 'ConditionalExpression': ConditionalExpression,
289
290 // Member expressions.
291 'CallExpression': CallExpression,
292 'PropertyExpression': PropertyExpression,
293 'AttributeExpression': AttributeExpression,
294 'ParenthesisExpression': ParenthesisExpression,
295 };
296 // An entity can have no value. It will be resolved to `null`.
29723108 if (!node) {
2980 return null;
299 }
30023108 try {
30123108 var expr = EXPRESSION_TYPES[node.type](node);
302 } catch(e) {
3030 throw new Error('Unknown expression type');
304 }
30523108 return expr;
306 }
307
3081 function _resolve(expr, locals, env, data, index) {
309 // Bail out early if it's a primitive value or `null`. This is exactly
310 // what we want.
311372178 if (!expr ||
312 typeof expr === 'string' ||
313 typeof expr === 'boolean' ||
314 typeof expr === 'number') {
315186084 return expr;
316 }
317 // Check if `expr` knows how to resolve itself (if it's an Entity or an
318 // Attribute).
319186094 if (expr._resolve) {
32026 return expr._resolve(data, index);
321 }
322186068 index = index || [];
323186068 var key = index.shift();
324 // `var [locals, current] = expr(...)` is not ES5 (V8 doesn't support it)
325186068 var current = expr(locals, env, data, key);
326186055 locals = current[0], current = current[1];
327186055 return _resolve(current, locals, env, data, index);
328 }
329
3301 function Identifier(node) {
3313254 var name = node.name;
3323254 return function identifier(locals, env, data) {
33322034 var entity = env.entries[name]
33422034 return [{ __this__: entity }, entity]
335 };
336 }
3371 function ThisExpression(node) {
338201 return function thisExpression(locals, env, data) {
3399 return [locals, locals.__this__];
340 };
341 }
3421 function VariableExpression(node) {
3431436 return function variableExpression(locals, env, data) {
34461717 var value = locals[node.id.name];
34561717 if (value !== undefined)
34661690 return value;
34727 return [locals, data[node.id.name]];
348 };
349 }
3501 function GlobalsExpression(node) {
3518 return function globalsExpression(locals, env, data) {
3522 return [locals, env.globals[node.id.name]];
353 };
354 }
3551 function NumberLiteral(node) {
3562227 return function numberLiteral(locals, env, data) {
35772700 return [locals, node.value];
358 };
359 }
3601 function StringLiteral(node) {
3615614 return function stringLiteral(locals, env, data) {
362343 return [locals, node.content];
363 };
364 }
3651 function ArrayLiteral(node) {
366854 var content = [];
367854 node.content.forEach(function(elem, i) {
3681582 content.push(Expression(elem));
369 });
370854 return function arrayLiteral(locals, env, data, key) {
371132 key = _resolve(key, locals, env, data);
372132 if (key && content[key]) {
37347 return [locals, content[key]];
374 } else {
375 // For Arrays, the default key is always 0. The syntax does not allow
376 // specifying a different default with an asterisk, like in hashes.
37785 return [locals, content[0]];
378 }
379 };
380 }
3811 function HashLiteral(node) {
3821696 var content = [];
3831696 var defaultKey = null;
3841696 node.content.forEach(function(elem, i) {
3853486 content[elem.key.name] = HashItem(elem);
3863486 if (i == 0 || elem['default'])
3871810 defaultKey = elem.key.name;
388 });
3891696 return function hashLiteral(locals, env, data, key) {
390287 key = _resolve(key, locals, env, data);
391287 if (key && content[key]) {
392200 return [locals, content[key]];
393 } else {
39487 return [locals, content[defaultKey]];
395 }
396 };
397 }
3981 function HashItem(node) {
399 // return the value expression right away
400 // the `key` and the `default` flag logic is done in `HashLiteral`
4013486 return Expression(node.value)
402 }
4031 function ComplexString(node) {
4042602 var content = [];
4052602 node.content.forEach(function(elem) {
4063364 content.push(Expression(elem));
407 });
408 // Every complexString needs to have its own `dirty` flag whose state
409 // persists across multiple calls to the given complexString. On the other
410 // hand, `dirty` must not be shared by all complexStrings. Hence the need
411 // to define `dirty` as a variable available in the closure. Note that the
412 // anonymous function is a self-invoked one and it returns the closure
413 // immediately.
4142602 return function() {
4152602 var dirty = false;
4162602 return function complexString(locals, env, data) {
417106 if (dirty) {
4184 throw new Error("Cyclic reference detected");
419 }
420102 dirty = true;
421102 var parts = [];
422102 content.forEach(function resolveElemOfComplexString(elem) {
423167 var part = _resolve(elem, locals, env, data);
424159 parts.push(part);
425 });
42694 dirty = false;
42794 return [locals, parts.join('')];
428 }
429 }();
430 }
431
4321 function UnaryOperator(token) {
43342 if (token == '-') return function negativeOperator(argument) {
4340 return -argument;
435 };
43642 if (token == '+') return function positiveOperator(argument) {
4370 return +argument;
438 };
43984 if (token == '!') return function notOperator(argument) {
4408 return !argument;
441 };
4420 throw new Error("Unknown token: " + token);
443 }
4441 function BinaryOperator(token) {
4451148 if (token == '==') return function equalOperator(left, right) {
44639655 return left == right;
447 };
448744 if (token == '!=') return function notEqualOperator(left, right) {
4490 return left != right;
450 };
451810 if (token == '<') return function lessThanOperator(left, right) {
45220 return left < right;
453 };
454738 if (token == '<=') return function lessThanEqualOperator(left, right) {
45520 return left <= right;
456 };
457618 if (token == '>') return function greaterThanOperator(left, right) {
4580 return left > right;
459 };
460744 if (token == '>=') return function greaterThanEqualOperator(left, right) {
46140 return left >= right;
462 };
463576 if (token == '+') return function addOperator(left, right) {
46410948 return left + right;
465 };
466534 if (token == '-') return function substractOperator(left, right) {
46721905 return left - right;
468 };
469324 if (token == '*') return function multiplyOperator(left, right) {
47015 return left * right;
471 };
472240 if (token == '/') return function devideOperator(left, right) {
4730 return left / right;
474 };
475480 if (token == '%') return function moduloOperator(left, right) {
47680 return left % right;
477 };
4780 throw new Error("Unknown token: " + token);
479 }
4801 function LogicalOperator(token) {
481354 if (token == '&&') return function andOperator(left, right) {
48240 return left && right;
483 };
484204 if (token == '||') return function orOperator(left, right) {
48521 return left || right;
486 };
4870 throw new Error("Unknown token: " + token);
488 }
4891 function UnaryExpression(node) {
49042 var operator = UnaryOperator(node.operator.token);
49142 var argument = Expression(node.argument);
49242 return function unaryExpression(locals, env, data) {
4938 return [locals, operator(_resolve(argument, locals, env, data))];
494 };
495 }
4961 function BinaryExpression(node) {
497946 var left = Expression(node.left);
498946 var operator = BinaryOperator(node.operator.token);
499946 var right = Expression(node.right);
500946 return function binaryExpression(locals, env, data) {
50172683 return [locals, operator(
502 _resolve(left, locals, env, data),
503 _resolve(right, locals, env, data)
504 )];
505 };
506 }
5071 function LogicalExpression(node) {
508228 var left = Expression(node.left);
509228 var operator = LogicalOperator(node.operator.token);
510228 var right = Expression(node.right);
511228 return function logicalExpression(locals, env, data) {
51261 return [locals, operator(
513 _resolve(left, locals, env, data),
514 _resolve(right, locals, env, data)
515 )];
516 }
517 }
5181 function ConditionalExpression(node) {
519352 var test = Expression(node.test);
520352 var consequent = Expression(node.consequent);
521352 var alternate = Expression(node.alternate);
522352 return function conditionalExpression(locals, env, data) {
52339684 if (_resolve(test, locals, env, data)) {
52410970 return consequent(locals, env, data);
525 }
52628714 return alternate(locals, env, data);
527 };
528 }
529
5301 function CallExpression(node) {
5312148 var callee = Expression(node.callee);
5322148 var args = [];
5332148 node.arguments.forEach(function(elem, i) {
5342274 args.push(Expression(elem));
535 });
5362148 return function callExpression(locals, env, data) {
53721971 var evaluated_args = [];
53821971 args.forEach(function(arg, i) {
53921974 evaluated_args.push(arg(locals, env, data));
540 });
541 // callee is an expression pointing to a macro, e.g. an identifier
542 // XXX what if it doesn't point to a macro?
54321971 var macro = callee(locals, env, data);
54421971 locals = macro[0], macro = macro[1];
545 // rely entirely on the platform implementation to detect recursion
54621971 return macro(locals, env, data, evaluated_args);
547 };
548 }
5491 function PropertyExpression(node) {
5501314 var expression = Expression(node.expression);
5511314 var property = node.computed ?
552 Expression(node.property) :
553 node.property.name;
5541314 return function propertyExpression(locals, env, data) {
55573 var prop = _resolve(property, locals, env, data);
55673 var parent = expression(locals, env, data);
55773 locals = parent[0], parent = parent[1];
558 // If `parent` is an Entity or an Attribute, evaluate its value via the
559 // `_yield` method. This will ensure the correct value of
560 // `locals.__this__`.
56173 if (parent._yield) {
56247 return parent._yield(data, prop);
563 }
564 // If `parent` is an object passed by the developer to the context (i.e.,
565 // `expression` was a `VariableExpression`), simply return the member of
566 // the object corresponding to `prop`. We don't really care about
567 // `locals` here.
56826 if (typeof parent !== 'function') {
5693 return [locals, parent[prop]];
570 }
57123 return parent(locals, env, data, prop);
572 }
573 }
5741 function AttributeExpression(node) {
575 // XXX looks similar to PropertyExpression, but it's actually closer to
576 // Identifier
577126 var expression = Expression(node.expression);
578126 var attribute = node.computed ?
579 Expression(node.attribute) :
580 node.attribute.name;
581126 return function attributeExpression(locals, env, data) {
58210 var attr = _resolve(attribute, locals, env, data);
58310 var entity = expression(locals, env, data);
58410 locals = entity[0], entity = entity[1];
585 // XXX what if it's not an entity?
58610 return [locals, entity.attributes[attr]];
587 }
588 }
5891 function ParenthesisExpression(node) {
59060 return Expression(node.expression);
591 }
592
593}).call(this);
make[2]: Leaving directory `/home/stas/moz/code/l20n/js'