├── test
├── 3.pjs
└── 2.pjs
├── Makefile
├── package.json
├── lib
├── pubjs.js
├── lexer.l
├── runtime.js
├── parser.y
├── main.js
├── engine.js
├── ast.js
└── parser.js
└── README.md
/test/3.pjs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 |
3 | default: build
4 |
5 | lib/parser.js: lib/parser.y lib/lexer.l
6 | jison -o $@ $^
7 |
8 | build: lib/parser.js
9 |
10 | clean:
11 | rm -f lib/parser.js test/*.js
12 |
13 | all: build
14 |
--------------------------------------------------------------------------------
/test/2.pjs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {% if (foo) {{
}}
8 | else {{nothing}}
9 | %}
10 |
11 | {% if (x) {{
12 | x = %{x};
13 | {% if (y) {{ y = %{y} }} %}
14 | z = %{z};
15 | }}
16 | foreach (var jam in jams) {{
17 |
18 | | %{jam}
19 | |
20 |
21 | }}
22 | %}
23 |
24 |
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author" : "Max Krohn ",
3 | "name" : "pubjs",
4 | "description" : "A node.js templating language that handles arbitrary and composable nesting",
5 | "version" : "0.0.9",
6 | "keywords" : [
7 | "okws",
8 | "pub"
9 | ],
10 | "preferGlobal" : true,
11 | "repository" : {
12 | "type" : "git",
13 | "url" : "git://github.com/maxtaco/pubjs.git"
14 | },
15 | "bugs" : {
16 | "email" : "max@okcupid.com",
17 | "url" : "http://github.com/maxtaco/pubjs/issues"
18 | },
19 | "main" : "lib/pubjs.js",
20 | "bin" : {
21 | "pubjs" : "lib/main.js"
22 | },
23 | "engine" : "node >= 0.4",
24 | "scripts": {},
25 | "directories" : {
26 | "lib" : "lib",
27 | "bin" : "./bin"
28 | },
29 | "homepage": "http://github.com/maxtaco/pubjs"
30 | }
31 |
--------------------------------------------------------------------------------
/lib/pubjs.js:
--------------------------------------------------------------------------------
1 |
2 | // Runtime for command-line hooks
3 | var runtime = require ('./runtime').runtime;
4 | var fs = require ('fs');
5 | exports.runtime = runtime;
6 |
7 | var counter = 1;
8 |
9 | // Report the compiler error to standard output, so that
10 | // the log will show what went wrong...
11 | var report_compile_error = function (text, options) {
12 |
13 | if (options.debug) {
14 |
15 | // Crazy that node doesn't have a tempfile system :(
16 | var seqno = ((process.pid + counter++) * (new Date ()).getTime())
17 | % 0xfffffffff;
18 | var tmp = "/tmp/pubjs-template." + seqno + ".js";
19 |
20 | fs.writeFile (tmp, text, function (err) {
21 | if (!err) {
22 | try {
23 | require (tmp);
24 | } catch (e) {
25 | // outer = e;
26 | }
27 | fs.unlink (tmp, function (err) {
28 | if (err) {
29 | console.log ("In reporting compiler error; " +
30 | "in unlinking '" + tmp + "': " + err);
31 | }
32 | });
33 | } else {
34 | console.log ("In reporting compiler error, failed to write '"
35 | + tmp + "': " + err);
36 | }
37 | });
38 | }
39 | }
40 |
41 | // Compile function for Express
42 | var compile = function (str, options) {
43 | var Engine = require ("./engine").Engine;
44 | var fn = options.filename;
45 | var eng = new Engine (fn);
46 | var ast = eng.parse (str);
47 | var outdat = eng.compile ().formatOutput ();
48 | try {
49 | var func = new Function ('print', 'locals', 'diagnostics', outdat);
50 | } catch (e) {
51 | report_compile_error (outdat, options);
52 | e.message = "In compiling file '" + fn + "': " + e.message;
53 | throw e;
54 | }
55 |
56 | return function (locals) {
57 | var sink = new runtime.Sink ();
58 | var printFn = sink.printFn ();
59 | var diagnostics = {};
60 | try {
61 | func.call (this, printFn, locals, diagnostics);
62 | } catch (e) {
63 | e.message = "In file '" + fn + "' on or after line " +
64 | diagnostics.lineno + ": " + e.message;
65 | throw e;
66 | }
67 | return sink.output ();
68 | };
69 | };
70 |
71 | exports.compile = compile;
72 |
--------------------------------------------------------------------------------
/lib/lexer.l:
--------------------------------------------------------------------------------
1 |
2 | %s JS_MODE Q1_MODE Q2_MODE IJS_MODE IJS2_MODE CC_MODE FOREACH_MODE TEXT_MODE
3 | %s COMMENT_MODE
4 | %%
5 |
6 |
7 | "{%%" { this.begin ('COMMENT_MODE'); }
8 | "{%" { this.begin ('JS_MODE'); return 'JS_OPEN'; }
9 | "}}" { this.popState (); return 'TEXT_CLOSE'; }
10 | "%{" { this.begin ('IJS_MODE'); return 'IJS_OPEN'; }
11 | "\\"[%{}] { yytext = yytext.slice(1); return 'TEXT'; }
12 | [^{}%\\]+|[\\{}%] { return 'TEXT'; }
13 |
14 | "%}" { this.popState (); return 'JS_CLOSE'; }
15 | "{{" { this.begin ('TEXT_MODE'); return 'TEXT_OPEN'; }
16 | "\"" { this.begin ('Q2_MODE'); return 'QUOTE2'; }
17 | "'" { this.begin ('Q1_MODE'); return 'QUOTE1'; }
18 | "/*" { this.begin ('CC_MODE'); }
19 | "//".* /* skip over C++-style comments */
20 | <> return 'ENDOFFILE';
21 | "foreach" { this.begin ('FOREACH_MODE'); return 'FOREACH'; }
22 | [^{(%"'/f]+ { return 'JS'; }
23 | [{(%"'/f] { return 'JS'; }
24 |
25 | [^{]+ {
26 | return 'TEXT';
27 | }
28 | "{{" {
29 | this.popState ();
30 | this.begin ("TEXT_MODE");
31 | return 'TEXT_OPEN';
32 | }
33 | [{] { this.popState (); return 'LBRACE'; }
34 |
35 | "%%}" { this.popState(); }
36 | [^%]+|[%] /* skip over comments */
37 |
38 |
39 | "{" { this.begin ('IJS2_MODE'); return 'IJS'; }
40 | "}" { this.popState (); return 'IJS_CLOSE'; }
41 | "}" { this.popState (); return 'IJS'; }
42 | [^{}]+ { return 'IJS'; }
43 | <> return 'ENDOFFILE';
44 |
45 | "\\". return 'STRING';
46 | [^\\"]+ return 'STRING';
47 | "\"" { this.popState (); return "QUOTE2"; }
48 | <> return 'ENDOFFILE';
49 |
50 | "\\". return 'STRING';
51 | [^\\']+ return 'STRING';
52 | "'" { this.popState (); return "QUOTE1"; }
53 | <> return 'ENDOFFILE';
54 |
55 | "*/" { this.popState(); }
56 | "*" /* ignore */
57 | [^*]+ /* ignore */
58 | <> return 'ENDOFFILE';
59 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | pubjs
2 | =====
3 | Yet another *node.js* templating system.
4 |
5 | It differs from all others we've seen because it offer arbitrary nesting
6 | of code and HTML output.
7 |
8 | Tutorial and Code Examples
9 | -------------------------
10 |
11 | By default, input is in HTML mode, in which all input data is
12 | passed through as output data, with the exception of expressions
13 | of the form `%{foo}`, which are first evaluated by JavaScript, and
14 | then output:
15 |
16 | ```html
17 | Name: %{name}
18 | Passion: %{passion}
19 | ```
20 |
21 | However you can switch from HTML mode into JavaScript mode, with the
22 | `{% .. %}` environment. Inside a JavaScript environment, use normal
23 | JavaScript, and also the function `print` to output HTML:
24 |
25 | ```html
26 | Name: %{name}
27 | {% if (pet) { print ("Pet: ", pet); }
28 | else { print ("no pets"); } %}
29 | Passion: %{passion}
30 | ```
31 |
32 | You can also switch back to HTML mode from within JavaScript mode, with
33 | any block of the form `{{..}}`. An equivalent way to write the above is:
34 |
35 | ```html
36 | Name: %{name}
37 | {% if (pet) {{Pet: %{pet} }}
38 | else {{No pets!}} %}
39 | Passion: %{passion}
40 | ```
41 |
42 | And as advertised, you are free to go as deeply nested as you please:
43 |
44 | ```html
45 | Name: %{name}
46 | {% if (pet) {{
47 | Pet:
48 | {% if (pet.type == "dog") {{
49 | Goes woof! (and is
50 | {% if (pet.sex == "M") {{neutered}}
51 | else {{spayed}}
52 | %}
53 | so doesn't reproduce)
54 | }} else if (pet.type == "cat") {{
55 | Goes meow!
56 | }}
57 | %}
58 | }} else {{no pet!}} %}
59 | ```
60 |
61 | We've also taken the liberty of adding a bona fide `foreach` to JavaScript,
62 | for simplified iteration:
63 |
64 | ```html
65 |
66 | {%
67 | foreach (var row in rows) {{
68 |
69 | {%
70 | foreach (var col in row) {{
71 | | %{col} |
72 | }}
73 | %}
74 |
75 | }}
76 | %}
77 |
78 | ```
79 |
80 | Usage
81 | -----
82 |
83 | To install:
84 |
85 | npm install -g pubjs
86 |
87 | To use in express:
88 |
89 | ```javascript
90 |
91 | // Regigster the handler...
92 | app.register ('.pjs', require ('pubjs'));
93 |
94 | // Then invoke it as needs be...
95 | app.get('/', function(req, res){
96 | res.render('index.pjs', {
97 | title: 'Express'
98 | });
99 | });
100 | ```
101 |
102 |
103 | TODOS
104 | -----
105 | * regtest suite
106 | * documentation (flesh out this file)
107 | * hi
108 |
--------------------------------------------------------------------------------
/lib/runtime.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2011 (MIT License)
3 | // Maxwell Krohn
4 | // HumorRainbow, Inc.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a
7 | // copy of this software and associated documentation files (the
8 | // "Software"), to deal in the Software without restriction, including
9 | // without limitation the rights to use, copy, modify, merge, publish,
10 | // distribute, sublicense, and/or sell copies of the Software, and to permit
11 | // persons to whom the Software is furnished to do so, subject to the
12 | // following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included
15 | // in all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
20 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
21 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
22 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
23 | // USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 | //-----------------------------------------------------------------------
26 | //
27 | // Functions to support the tame runtime! Needs to required in every tame-
28 | // generated file. All are core, except for Rendezvous, which isn't
29 | // core, but is quite useful.
30 | //
31 | //-----------------------------------------------------------------------
32 |
33 | function Sink () {
34 |
35 | this._lines = [];
36 |
37 | this.printFn = function () {
38 | var self = this;
39 | return function () {
40 | for (var i in arguments) {
41 | var line = arguments[i];
42 | self._lines.push (line);
43 | }
44 | };
45 | };
46 |
47 | this.output = function () {
48 | return this._lines.join ('');
49 | };
50 | };
51 |
52 | //-----------------------------------------------------------------------
53 |
54 |
55 | function runCommandLine (args, fn) {
56 |
57 | function run2 (data) {
58 | var locals = JSON.parse (data);
59 | var sink = new Sink ();
60 | var diags = {};
61 | fn (sink.printFn (), locals, diags);
62 | sink.output ();
63 | };
64 |
65 | if (args.length < 2) { run2 ("{}"); }
66 | else if (args[2] == "-") {
67 | var fs = require ('fs');
68 | fs.readFile ("/dev/stdin", function (err, data) {
69 | if (err) throw err;
70 | run2 (String (data));
71 | });
72 | } else { run2 (args[2]); }
73 | };
74 |
75 | //-----------------------------------------------------------------------
76 |
77 | exports.runtime = {
78 | runCommandLine : runCommandLine,
79 | Sink : Sink
80 | };
81 |
82 | //-----------------------------------------------------------------------
83 |
--------------------------------------------------------------------------------
/lib/parser.y:
--------------------------------------------------------------------------------
1 |
2 | /* description: the parser definition for pubjs
3 | *
4 | * To build:
5 | * % jison parser.y lexer.l
6 | *
7 | * Author: Max Krohn
8 | *
9 | */
10 |
11 | %start File
12 |
13 | %%
14 |
15 | File
16 | : TextZoneInner
17 | {
18 | yy.output = new yy.File (@1.first_line, $1);
19 | }
20 | ;
21 |
22 | TextZoneInner
23 | : TextBlocks
24 | {
25 | $$ = new yy.TextZone (@1.first_line, $1);
26 | }
27 | ;
28 |
29 | TextZone
30 | : TEXT_OPEN TextZoneInner TEXT_CLOSE
31 | {
32 | $$ = $2;
33 | }
34 | ;
35 |
36 | Text
37 | : TEXT { $$ = yytext; }
38 | ;
39 |
40 | TextBlocks
41 | : { $$ = []; }
42 | | TextBlocks TextBlock
43 | {
44 | $1.push ($2);
45 | $$ = $1;
46 | }
47 | | TextBlocks Text
48 | {
49 | var v = $1;
50 | // Squish blocks of text together if possible
51 | if (!v.length || !v[v.length - 1].pushText ($2)) {
52 | v.push (new yy.Text (@2.first_line, $2));
53 | }
54 | $$ = v;
55 | }
56 | ;
57 |
58 | TextBlock
59 | : InlineJs
60 | | JsZone
61 | ;
62 |
63 | JsZone
64 | : JS_OPEN JsZoneInner JS_CLOSE
65 | {
66 | $$ = $2;
67 | }
68 | ;
69 |
70 | JsZoneInner
71 | : JsBlocks
72 | {
73 | $$ = yy.JsZone (@1.first_line, $1);
74 | }
75 | ;
76 |
77 | Js
78 | : JS { $$ = yytext; }
79 | ;
80 |
81 | JsBlocks
82 | : { $$ = []; }
83 | | JsBlocks JsBlock
84 | {
85 | $$ = $1.concat ($2);
86 | }
87 | ;
88 |
89 | Foreach
90 | : FOREACH ForeachText ForeachZone
91 | {
92 | $$ = yy.makeForeach (@1.first_line, @3.last_line, $2, $3);
93 | }
94 | ;
95 |
96 | ForeachZone
97 | : TextZone { $$ = $1; }
98 | | LBRACE { $$ = new yy.Js (@1.first_line, yytext); }
99 | ;
100 |
101 |
102 | ForeachText
103 | : TEXT { $$ = [ $1 ]; }
104 | | ForeachText TEXT { $1.push ($2); $$ = $1; }
105 | ;
106 |
107 | JsBlock
108 | : String { $$ = [ $1 ]; }
109 | | TextZone
110 | {
111 | $$ = [ new yy.Js (@1.first_line, " { "),
112 | $1,
113 | new yy.Js (@1.last_line, " } ")
114 | ];
115 | }
116 | | Foreach { $$ = $1; }
117 | | Js { $$ = [ new yy.Js (@1.last_line, $1) ]; }
118 | ;
119 |
120 | String
121 | : String1
122 | | String2
123 | ;
124 |
125 | String1
126 | : QUOTE1 StringAtoms QUOTE1
127 | {
128 | $$ = new yy.String (@1.first_line, $2, "'");
129 | }
130 | ;
131 |
132 | String2
133 | : QUOTE2 StringAtoms QUOTE2
134 | {
135 | $$ = new yy.String (@1.first_line, $2, "\"");
136 | }
137 | ;
138 |
139 |
140 | StringAtoms
141 | : { $$ = []; }
142 | | StringAtoms StringAtom
143 | {
144 | $1.push ($2);
145 | $$ = $1;
146 | }
147 | ;
148 |
149 | StringAtom
150 | : STRING { $$ = yytext; }
151 | | InlineJs { $$ = $1; }
152 | ;
153 |
154 | InlineJs
155 | : IJS_OPEN InlineJsAtoms IJS_CLOSE
156 | {
157 | $$ = new yy.InlineJs (@1.first_line, $2.join (""));
158 | }
159 | ;
160 |
161 | InlineJsAtoms
162 | : { $$ = []; }
163 | | InlineJsAtoms InlineJsAtom
164 | {
165 | $1.push ($2);
166 | $$ = $1;
167 | }
168 | ;
169 |
170 | InlineJsAtom
171 | : IJS { $$ = yytext; }
172 | ;
173 |
174 |
--------------------------------------------------------------------------------
/lib/main.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | //
4 | // Copyright (c) 2011 (MIT License)
5 | // Maxwell Krohn
6 | // HumorRainbow, Inc.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a
9 | // copy of this software and associated documentation files (the
10 | // "Software"), to deal in the Software without restriction, including
11 | // without limitation the rights to use, copy, modify, merge, publish,
12 | // distribute, sublicense, and/or sell copies of the Software, and to permit
13 | // persons to whom the Software is furnished to do so, subject to the
14 | // following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included
17 | // in all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
22 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
23 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
24 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
25 | // USE OR OTHER DEALINGS IN THE SOFTWARE.
26 | //-----------------------------------------------------------------------
27 | //
28 |
29 | //-----------------------------------------------------------------------
30 | // Dump in code from options.js, cloned from here:
31 | //
32 | // git://gist.github.com/982499.git
33 | //
34 | // Node.js should really standardize option parsing, so as not to
35 | // introduce dependencies.
36 |
37 | /** Command-line options parser (http://valeriu.palos.ro/1026/).
38 | Copyright 2011 Valeriu Paloş (valeriu@palos.ro). All rights reserved.
39 | Released as Public Domain.
40 |
41 | Expects the "schema" array with options definitions and produces the
42 | "options" object and the "arguments" array, which will contain all
43 | non-option arguments encountered (including the script name and such).
44 |
45 | Syntax:
46 | [«short», «long», «attributes», «brief», «callback»]
47 |
48 | Attributes:
49 | ! - option is mandatory;
50 | : - option expects a parameter;
51 | + - option may be specified multiple times (repeatable).
52 |
53 | Notes:
54 | - Parser is case-sensitive.
55 | - The '-h|--help' option is provided implicitly.
56 | - Parsed options are placed as fields in the "options" object.
57 | - Non-option arguments are placed in the "arguments" array.
58 | - Options and their parameters must be separated by space.
59 | - Either one of «short» or «long» must always be provided.
60 | - The «callback» function is optional.
61 | - Cumulated short options are supported (i.e. '-tv').
62 | - If an error occurs, the process is halted and the help is shown.
63 | - Repeatable options will be cumulated into arrays.
64 | - The parser does *not* test for duplicate option definitions.
65 | */
66 |
67 | // Thus for, only one option, with more to come...
68 | var schema = [
69 | ['o', 'outfile', ':', "the file to output to" ],
70 | ['v', 'verbose', '', 'dump internal states to console' ],
71 | ['c', 'command-line', '', 'make a command-line client' ],
72 | ['d', 'debug', '', 'for debugging, nice indentation' ]
73 | ];
74 |
75 | // Parse options.
76 | try {
77 | var tokens = [];
78 | var options = {};
79 | var arguments = [];
80 | for (var i = 0, item = process.argv[0]; i < process.argv.length;
81 | i++, item = process.argv[i]) {
82 | if (item.charAt(0) == '-') {
83 | if (item.charAt(1) == '-') {
84 | tokens.push('--', item.slice(2));
85 | } else {
86 | tokens = tokens.concat(item.split('').join('-').
87 | split('').slice(1));
88 | }
89 | } else {
90 | tokens.push(item);
91 | }
92 | }
93 | while (type = tokens.shift()) {
94 | if (type == '-' || type == '--') {
95 | var name = tokens.shift();
96 | if (name == 'help' || name == 'h') {
97 | throw 'help';
98 | continue;
99 | }
100 | var option = null;
101 | for (var i = 0, item = schema[0]; i < schema.length;
102 | i++, item = schema[i]) {
103 | if (item[type.length - 1] == name) {
104 | option = item;
105 | break;
106 | }
107 | }
108 | if (!option) {
109 | throw "Unknown option '" + type + name + "' encountered!";
110 | }
111 | var value = true;
112 | if ((option[2].indexOf(':') != -1) && !(value = tokens.shift())) {
113 | throw "Option '" + type + name + "' expects a parameter!";
114 | }
115 | var index = option[1] || option[0];
116 | if (option[2].indexOf('+') != -1) {
117 | options[index] = options[index] instanceof Array
118 | ? options[index] : [];
119 | options[index].push(value);
120 | } else {
121 | options[index] = value;
122 | }
123 | if (typeof(option[4]) == 'function') {
124 | option[4](value);
125 | }
126 | option[2] = option[2].replace('!', '');
127 | } else {
128 | arguments.push(type);
129 | continue;
130 | }
131 | }
132 | for (var i = 0, item = schema[0]; i < schema.length;
133 | i++, item = schema[i]) {
134 | if (item[2].indexOf('!') != -1) {
135 | throw "Option '" + (item[1] ? '--' + item[1] : '-' + item[0]) +
136 | "' is mandatory and was not given!";
137 | }
138 | }
139 |
140 | } catch(e) {
141 | if (e == 'help') {
142 | console.log("Usage: pubjs [-o ] []\n");
143 | console.log("Options:");
144 | for (var i = 0, item = schema[0]; i < schema.length;
145 | i++, item = schema[i]) {
146 | var names = (item[0] ? '-' + item[0] +
147 | (item[1] ? '|' : ''): ' ') +
148 | (item[1] ? '--' + item[1] : '');
149 | var syntax = names + (item[2].indexOf(':') != -1 ? ' «value»' : '');
150 | syntax += syntax.length < 20 ?
151 | new Array(20 - syntax.length).join(' ') : '';
152 | console.log("\t" + (item[2].indexOf('!') != -1 ? '*' : ' ')
153 | + (item[2].indexOf('+') != -1 ? '+' : ' ')
154 | + syntax + "\t" + item[3]);
155 | }
156 | console.log ("\n" +
157 | "\t- If no infile is specified, stdin is assumed\n" +
158 | "\n" +
159 | "\t- If the input file specified ends in .pjs, and no\n" +
160 | "\t explicit output file is given, then the output\n" +
161 | "\t will be written to stem.js, for the given stem\n" +
162 | "\n" +
163 | "\t- If no explicit output file is given, and the\n" +
164 | "\t input file is not of the form .pjs, then\n" +
165 | "\t output is written to stdout.");
166 |
167 | process.exit(0);
168 | }
169 | console.error(e);
170 | console.error("Use the '-h|--help' option for usage details.");
171 | process.exit(1);
172 | }
173 |
174 | //
175 | // End options.js
176 | //-----------------------------------------------------------------------
177 |
178 | function produce (infile, ast) {
179 | return engine.run (ast);
180 | };
181 |
182 | function main (infile, outfile) {
183 | var fs = require ('fs');
184 | var Engine = require ('./engine').Engine;
185 | var engine = new Engine (infile);
186 | if (options["command-line"]) { engine.setCommandLine (); }
187 | if (options.debug) { engine.setDebug (); }
188 |
189 | engine.readInput (function () {
190 | engine.parse ();
191 | if (options.verbose) {
192 | engine.dump ();
193 | }
194 | var outdat = engine.compile ().formatOutput ();
195 | if (outfile == "-") {
196 | process.stdout.write (outdat);
197 | } else {
198 | fs.writeFile (outfile, outdat, function (err) {
199 | if (err) throw err;
200 | });
201 | }
202 | });
203 | };
204 |
205 |
206 | var named_file = false;
207 | if (arguments.length <= 2) {
208 | infn = "/dev/stdin";
209 | } else {
210 | named_file = true;
211 | infn = arguments[2];
212 | }
213 |
214 | var rxx = new RegExp ("^(.*)\.pjs$");
215 | var m;
216 |
217 | if (options.outfile) {
218 | outfn = options.outfile;
219 | } else if (named_file && (m = infn.match (rxx))) {
220 | outfn = m[1] + ".js";
221 | } else {
222 | outfn = "-";
223 | }
224 |
225 | main (infn, outfn);
226 |
227 |
--------------------------------------------------------------------------------
/lib/engine.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2011 (MIT License)
3 | // Maxwell Krohn
4 | // HumorRainbow, Inc.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a
7 | // copy of this software and associated documentation files (the
8 | // "Software"), to deal in the Software without restriction, including
9 | // without limitation the rights to use, copy, modify, merge, publish,
10 | // distribute, sublicense, and/or sell copies of the Software, and to permit
11 | // persons to whom the Software is furnished to do so, subject to the
12 | // following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included
15 | // in all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
20 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
21 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
22 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
23 | // USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 | //-----------------------------------------------------------------------
26 |
27 | //=======================================================================
28 |
29 | function Break (line) {
30 | this.toString = function () {
31 | return ("diagnostics.lineno = " + line + ";")
32 | };
33 | this.isBreak = function () { return true; }
34 | };
35 |
36 | //=======================================================================
37 |
38 | function Text (txt) {
39 | this.toString = function () { return txt; }
40 | this.isBreak = function () { return false; }
41 | };
42 |
43 | //=======================================================================
44 |
45 | function Line (txt, lineno, indent) {
46 | this._atoms = [ txt ];
47 | if (!lineno) { lineno = 0; }
48 | if (!indent) { indent = 0; }
49 | this._lineno = lineno;
50 | this._indent = indent;
51 | this._annotated = false;
52 |
53 | //-----------------------------------------
54 |
55 | this.text = function () {
56 | var tmp = [];
57 | var found_break = false;
58 | for (var i in this._atoms) {
59 | var atom = this._atoms[i];
60 | var skip = false;
61 |
62 | if (!atom.isBreak()) {}
63 | else if (found_break) { skip = true; }
64 | else { found_break = true; }
65 |
66 | if (!skip) { tmp.push (atom.toString()); }
67 | }
68 | return tmp.join (' ');
69 | }
70 |
71 | //-----------------------------------------
72 |
73 | this.pushAtoms = function (l) {
74 | for (var i in l) {
75 | atom = l[i];
76 | this._atoms.push (atom);
77 | }
78 | };
79 |
80 | //-----------------------------------------
81 |
82 | this.atoms = function () { return this._atoms; }
83 |
84 | //-----------------------------------------
85 |
86 | this.lineno = function () { return this._lineno; }
87 | this.indent = function () { return this._indent; }
88 |
89 | //-----------------------------------------
90 |
91 | this.prepend = function (x) { this._atoms.unshift (new Text (x)); }
92 |
93 | //-----------------------------------------
94 |
95 | this.incIndent = function (i) { this._indent += i; }
96 |
97 | //-----------------------------------------
98 |
99 | this.push = function (obj, debug) {
100 | var ret = false;
101 | if ((!debug || obj.lineno ()) && this.lineno () == obj.lineno ()) {
102 | this.pushAtoms (obj.atoms ());
103 | ret = true;
104 | }
105 | return ret;
106 | };
107 |
108 | //-----------------------------------------
109 |
110 | this.annotated = function () { return this._annotated; }
111 | this.setAnnotated = function (a) { this._annotated = a; }
112 |
113 | //-----------------------------------------
114 |
115 | return this;
116 | };
117 |
118 | //=======================================================================
119 | //
120 | // A piece of output as output by the compilation engine
121 |
122 | function Output (eng) {
123 |
124 | this._lines = [];
125 | this._indent = 0;
126 | this._engine = eng;
127 |
128 | this.indent = function () { this._indent ++; };
129 | this.unindent = function () { this._indent--; };
130 |
131 | //-----------------------------------------
132 |
133 | this.addLine = function (txt, lineno) {
134 | this.addLineObj (new Line (new Text (txt), lineno, this._indent));
135 | };
136 |
137 | //-----------------------------------------
138 |
139 | this.addLineDiagnostics = function (lineno) {
140 | this.addLineObj (new Line (new Break (lineno), lineno, this._indent));
141 | };
142 |
143 | //-----------------------------------------
144 |
145 | this.addBlock = function (b, lineno) {
146 | this.addLines (b.split ("\n"), lineno);
147 | };
148 |
149 | //-----------------------------------------
150 |
151 | this.addLines = function (v, lineno) {
152 | for (var i in v) {
153 | var line = v[i];
154 | this.addLine (line, lineno);
155 | lineno++;
156 | }
157 | };
158 |
159 | //-----------------------------------------
160 |
161 | this.last = function () {
162 | var ret = null;
163 | var len = this._lines.length;
164 | if (len) { ret = this._lines[len-1]; }
165 | return ret;
166 | };
167 |
168 | //-----------------------------------------
169 |
170 | this.addLineObj = function (o) {
171 | var lst = this.last ();
172 | if (!lst || !lst.push (o, this._engine.debug ())) {
173 | this._lines.push (o);
174 | }
175 | };
176 |
177 | //-----------------------------------------
178 |
179 | this.addOutput = function (o) {
180 | for (var i in o._lines) {
181 | var line = o._lines[i];
182 | line.incIndent (this._indent);
183 | this.addLineObj (line);
184 | }
185 | };
186 |
187 | //----------------------------------------
188 |
189 | this._cachedIndents = {};
190 | this.outputIndent = function (n) {
191 | var ret;
192 | if (this._cachedIndents[n]) {
193 | ret = this._cachedIndents[n];
194 | } else {
195 | var spc = " ";
196 | var v = []
197 | for (var i = 0; i < n; i++) {
198 | v.push (spc);
199 | }
200 | ret = v.join ("");
201 | this._cachedIndents[n] = ret;
202 | }
203 | return ret;
204 | };
205 |
206 | //----------------------------------------
207 |
208 | this.indentAll = function () {
209 | for (var i in this._lines) {
210 | var l = this._lines[i];
211 | var ind = this.outputIndent (l.indent ());
212 | l.prepend (ind);
213 | }
214 | };
215 |
216 | //----------------------------------------
217 |
218 | this.formatOutput = function () {
219 | if (this._engine.debug ()) {
220 | this.indentAll ();
221 | }
222 | var buf = [];
223 | var lineno = 1;
224 | var fresh = false;
225 | for (var i in this._lines) {
226 | var line = this._lines[i];
227 | if (!this._engine.debug ()) {
228 | while (lineno < line.lineno ()) {
229 | lineno ++;
230 | buf.push ("\n");
231 | fresh = true;
232 | }
233 | if (!fresh) { buf.push (" "); }
234 | buf.push (line.text ());
235 | fresh = false;
236 | } else {
237 | buf.push (line.text () + "\n");
238 | }
239 | }
240 | return buf.join ('') + "\n";
241 | };
242 |
243 | //-----------------------------------------
244 |
245 | return this;
246 | };
247 |
248 | //=======================================================================
249 |
250 | function Engine (filename) {
251 |
252 | //-----------------------------------------
253 |
254 | this._filename = filename;
255 | this._txt = null;
256 | this._ast = null;
257 | this._commandLine = false;
258 | this._debug = false;
259 |
260 | //-----------------------------------------
261 |
262 | this.setCommandLine = function () { this._commandLine = true; } ;
263 | this.commandLine = function () { return this._commandLine; } ;
264 | this.setDebug = function () { this._debug = true; }
265 | this.debug = function () { return this._debug; }
266 |
267 |
268 | //-----------------------------------------
269 |
270 | // A block of output is given by this class.
271 | this.newOutput = function () { return new Output (this); }
272 |
273 | //-----------------------------------------
274 |
275 | this.compile = function () {
276 | return this._ast.compile (this);
277 | };
278 |
279 | //-----------------------------------------
280 |
281 | // Fix this up a bunch!
282 | this.error = function (node, msg) {
283 | console.log (this._filename + ":" + node.startLine () + ": " + msg);
284 | process.exit (1);
285 | };
286 |
287 | //-----------------------------------------
288 |
289 | this.readInput = function (cb) {
290 | var fs = require ('fs');
291 | var self = this;
292 | fs.readFile (this._filename, function (err, data) {
293 | if (err) throw err;
294 | self._txt = String (data);
295 | cb();
296 | });
297 | };
298 |
299 | //-----------------------------------------
300 |
301 | this.dump = function () {
302 | console.log (JSON.stringify (this._ast.dump ()));
303 | };
304 |
305 | //-----------------------------------------
306 |
307 | // Load up the parser and run it on the input text
308 | this.parse = function (txt) {
309 | if (!txt) { txt = this._txt; }
310 | var astmod = require ('./ast');
311 | var parser = require ('./parser').parser;
312 | // Set the ast bindings into the parser's free yy variable
313 | parser.yy = astmod;
314 |
315 | var res = parser.parse (txt);
316 | var ast = null;
317 | if (res) {
318 | ast = parser.yy.output;
319 | this._ast = ast;
320 | }
321 | return ast;
322 | };
323 |
324 | //-----------------------------------------
325 |
326 | return this;
327 | };
328 |
329 | //-----------------------------------------------------------------------
330 |
331 | exports.Engine = Engine;
332 |
--------------------------------------------------------------------------------
/lib/ast.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2011 (MIT License)
3 | // Maxwell Krohn
4 | // HumorRainbow, Inc.
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a
7 | // copy of this software and associated documentation files (the
8 | // "Software"), to deal in the Software without restriction, including
9 | // without limitation the rights to use, copy, modify, merge, publish,
10 | // distribute, sublicense, and/or sell copies of the Software, and to permit
11 | // persons to whom the Software is furnished to do so, subject to the
12 | // following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included
15 | // in all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
20 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
21 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
22 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
23 | // USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 | //-----------------------------------------------------------------------
26 | //
27 | // Code for making the elements of the abstract syntax tree (AST)
28 | // for the pubjs grammar. Everything should inherit from a Node
29 | // object. A File object is output by the parser
30 | //
31 | //-----------------------------------------------------------------------
32 |
33 | function Node (startLine) {
34 | this._startLine = startLine;
35 |
36 | this.pushText = function (txt) { return false; }
37 | this.pushJs = function (txt) { return false; }
38 | this.isJs = function (txt) { return false; }
39 | this.isTextZone = function (txt) { return false; }
40 | };
41 |
42 | //-----------------------------------------------------------------------
43 |
44 | function dump_v (v) {
45 | return v.map (function (x) {
46 | if (typeof (x) == 'string') { return x; }
47 | else { return x.dump (); ; }
48 | });
49 | };
50 |
51 | //-----------------------------------------------------------------------
52 |
53 | function InlineJs (startLine, code) {
54 | var that = new Node (startLine);
55 | that._code = code;
56 |
57 | that.dump = function () {
58 | return { type : 'InlineJs', code : this._code };
59 | };
60 |
61 | //-----------------------------------------
62 |
63 | that.compile = function (eng) {
64 | var out = eng.newOutput ();
65 | out.addBlock ("print (" + this._code + ");", this._startLine);
66 | return out;
67 | };
68 |
69 | return that;
70 | };
71 |
72 | //-----------------------------------------------------------------------
73 |
74 | function MyString (startLine, elements, quote) {
75 |
76 | //-----------------------------------------
77 | // Constructor --- smush together elements
78 | // so long as we have different string components.
79 | // when we get an InlineJs block, then we push it
80 | // as a separate object.
81 | var that = new Node (startLine);
82 | that._quote = quote;
83 |
84 | that.init = function (elements) {
85 | var v = [];
86 | var buf = [];
87 | for (var i in elements) {
88 | var element = elements[i];
89 | if (typeof (element) == 'string') {
90 | buf.push (element);
91 | } else {
92 | if (buf.length) {
93 | v.push (buf.join (""));
94 | }
95 | last = null;
96 | v.push (element);
97 | }
98 | }
99 | if (buf.length) { v.push (buf.join ("")); }
100 | this._elements = v;
101 | };
102 | that.init (elements);
103 |
104 | //-----------------------------------------
105 |
106 | that.dump = function () {
107 | return { type : 'String',
108 | elements : dump_v (this._elements),
109 | quoteChar : this._quote };
110 | };
111 |
112 | //-----------------------------------------
113 |
114 | that.text = function () {
115 | var s = this._quote + this._elements.join ("") + this._quote;
116 | return s;
117 | };
118 |
119 | //-----------------------------------------
120 |
121 | that.compile = function (eng) {
122 | var out = eng.newOutput ();
123 | out.addLine (this.text (), this._startLine);
124 | return out;
125 | };
126 |
127 | //-----------------------------------------
128 |
129 | return that;
130 | };
131 |
132 | //-----------------------------------------------------------------------
133 |
134 | function Js (startLine, code) {
135 | var that = new Node (startLine);
136 | that._code = [ code ];
137 |
138 | //-----------------------------------------
139 |
140 | that.isJs = function () { return true; }
141 |
142 | //-----------------------------------------
143 |
144 | that.pushJs = function (frag) {
145 | var ret = false;
146 | if (frag.isJs ()) {
147 | this._code = this._code.concat (frag._code);
148 | ret = true;
149 | }
150 | return ret;
151 | };
152 |
153 | //-----------------------------------------
154 |
155 | that.code = function () {
156 | return this._code.join ('');
157 | };
158 |
159 | //-----------------------------------------
160 |
161 | that.dump = function () {
162 | return { type : 'Js', code : this.code () };
163 | };
164 |
165 | //-----------------------------------------
166 |
167 | that.compile = function (eng) {
168 | var out = eng.newOutput ();
169 | out.addBlock (this.code (), this._startLine);
170 | return out;
171 | };
172 |
173 | //-----------------------------------------
174 |
175 | return that;
176 | };
177 |
178 | //-----------------------------------------------------------------------
179 |
180 | function JsZone (startLine, blocks) {
181 | var that = new Node (startLine);
182 |
183 | //-----------------------------------------
184 |
185 | that.compress = function (blocks) {
186 | var out = [];
187 | var last = null;
188 | for (var i in blocks) {
189 | var block = blocks[i];
190 | if (!last || !last.pushJs (block)) {
191 | out.push (block);
192 | last = block;
193 | }
194 | }
195 | return out;
196 | };
197 |
198 | that._blocks = that.compress (blocks);
199 |
200 | //-----------------------------------------
201 |
202 | that.dump = function () {
203 | return { type : 'JsZone', code : dump_v (this._blocks) };
204 | };
205 |
206 | //-----------------------------------------
207 |
208 | that.compile = function (eng) {
209 | var out = eng.newOutput ();
210 | for (var i in this._blocks) {
211 | var b = this._blocks[i];
212 | var c = b.compile (eng);
213 | out.addOutput (c);
214 | }
215 | return out;
216 | };
217 |
218 | //-----------------------------------------
219 |
220 | return that;
221 | };
222 |
223 | //-----------------------------------------------------------------------
224 |
225 | function Text (startLine, text) {
226 | var that = new Node (startLine);
227 | that._text = [ text ];
228 |
229 | //-----------------------------------------
230 |
231 | that.pushText = function (text) {
232 | this._text.push (text);
233 | return true;
234 | };
235 |
236 | //-----------------------------------------
237 |
238 | that.text = function () {
239 | return this._text.join ('');
240 | };
241 |
242 | //-----------------------------------------
243 |
244 | that.compile = function (eng) {
245 | var out = eng.newOutput ();
246 | var lines = this.text ().split ("\n");
247 | var n = this._startLine;
248 | out.addLineDiagnostics (n);
249 | out.addLine ("print (", n);
250 | out.indent ();
251 | for (var i in lines) {
252 | if (i > 0) {
253 | out.addLine (",", n);
254 | n++;
255 | }
256 | var line = lines[i];
257 | if (i != lines.length - 1) {
258 | line += "\n";
259 | }
260 | out.addLine (JSON.stringify (line), n);
261 | }
262 |
263 | out.unindent ();
264 | out.addLine (");", n);
265 | out.addLineDiagnostics (n);
266 | return out;
267 | };
268 |
269 | //-----------------------------------------
270 |
271 | that.dump = function () {
272 | return { type : 'Text', text : this.text (),
273 | lineno : this._startLine };
274 | };
275 |
276 | //-----------------------------------------
277 |
278 | return that;
279 | };
280 |
281 | //-----------------------------------------------------------------------
282 |
283 | function TextZone (startLine, blocks) {
284 | var that = new Node (startLine);
285 | that._blocks = blocks;
286 |
287 | //-----------------------------------------
288 |
289 | that.dump = function () {
290 | return { type : 'TextZone', code : dump_v (this._blocks) };
291 | };
292 |
293 | //-----------------------------------------
294 |
295 | that.isTextZone = function () { return true; }
296 |
297 | //-----------------------------------------
298 |
299 | that.compile = function (eng) {
300 | var out = eng.newOutput ();
301 | for (var i in this._blocks) {
302 | var b = this._blocks[i];
303 | var c = b.compile (eng);
304 | out.addOutput (c);
305 | }
306 | return out;
307 | };
308 |
309 | //-----------------------------------------
310 |
311 | return that;
312 | };
313 |
314 | //-----------------------------------------------------------------------
315 |
316 | function File (startLine, textZone) {
317 | var that = new Node (startLine);
318 | that._textZone = textZone;
319 |
320 | //-----------------------------------------------------------------------
321 |
322 | that.dump = function () {
323 | return { type : 'File',
324 | body : this._textZone.dump () };
325 | };
326 |
327 | //-----------------------------------------------------------------------
328 |
329 | that.compile = function (eng) {
330 | var lineno = 0;
331 | var out = eng.newOutput ();
332 |
333 | if (eng.commandLine ()) {
334 | out.addLine ("var pubjs = require ('pubjs').runtime;");
335 | out.addLine ("function runPubJs (print, locals, diagnostics) {",
336 | lineno);
337 | out.indent ();
338 | }
339 |
340 | out.addLine ("with (locals) {", lineno);
341 | out.indent ();
342 | var body = this._textZone.compile (eng);
343 | out.addOutput (body);
344 | out.unindent ();
345 | out.addLine ("}");
346 |
347 | if (eng.commandLine ()) {
348 | out.unindent ();
349 | out.addLine ("};");
350 | out.addLine ("pubjs.runCommandLine (process.argv, runPubJs);", 0);
351 | }
352 |
353 | return out;
354 | };
355 |
356 | //-----------------------------------------------------------------------
357 |
358 | return that;
359 | };
360 |
361 | //-----------------------------------------------------------------------
362 |
363 | var _iter_id = 0;
364 |
365 | function makeForeach (startLine, endLine, text, body) {
366 |
367 | var buf = text.join ("\n");
368 | var rxx = new RegExp ("^\\s*\\(\\s*var\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s+in\\s+(.*)\\s*\\)\\s*");
369 | var m = buf.match (rxx);
370 | var out = "";
371 | if (!m) {
372 | throw new Error ("bad foreach expression");
373 | }
374 | var id = m[1];
375 | var expr = m[2];
376 | var iter = "__pubjs_iter_" + _iter_id;
377 | _iter_id++;
378 | var txt = "for (var " + iter + " in " + expr + ") { ";
379 | txt += "var " + id + " = " + expr + "[" + iter + "]; ";
380 |
381 | ret = [ new Js (startLine, txt) ];
382 |
383 | if (body.isTextZone ()) {
384 | ret.push (body);
385 | ret.push (new Js (endLine, " } "));
386 | }
387 | return ret;
388 | };
389 |
390 |
391 | //-----------------------------------------------------------------------
392 |
393 | exports.InlineJs = InlineJs;
394 | exports.String = MyString;
395 | exports.Js = Js;
396 | exports.JsZone = JsZone;
397 | exports.Text = Text;
398 | exports.TextZone = TextZone;
399 | exports.makeForeach = makeForeach;
400 | exports.File = File;
401 |
402 |
--------------------------------------------------------------------------------
/lib/parser.js:
--------------------------------------------------------------------------------
1 | /* Jison generated parser */
2 | var parser = (function(){
3 | var parser = {trace: function trace() { },
4 | yy: {},
5 | symbols_: {"error":2,"File":3,"TextZoneInner":4,"TextBlocks":5,"TextZone":6,"TEXT_OPEN":7,"TEXT_CLOSE":8,"Text":9,"TEXT":10,"TextBlock":11,"InlineJs":12,"JsZone":13,"JS_OPEN":14,"JsZoneInner":15,"JS_CLOSE":16,"JsBlocks":17,"Js":18,"JS":19,"JsBlock":20,"Foreach":21,"FOREACH":22,"ForeachText":23,"ForeachZone":24,"LBRACE":25,"String":26,"String1":27,"String2":28,"QUOTE1":29,"StringAtoms":30,"QUOTE2":31,"StringAtom":32,"STRING":33,"IJS_OPEN":34,"InlineJsAtoms":35,"IJS_CLOSE":36,"InlineJsAtom":37,"IJS":38,"$accept":0,"$end":1},
6 | terminals_: {2:"error",7:"TEXT_OPEN",8:"TEXT_CLOSE",10:"TEXT",14:"JS_OPEN",16:"JS_CLOSE",19:"JS",22:"FOREACH",25:"LBRACE",29:"QUOTE1",31:"QUOTE2",33:"STRING",34:"IJS_OPEN",36:"IJS_CLOSE",38:"IJS"},
7 | productions_: [0,[3,1],[4,1],[6,3],[9,1],[5,0],[5,2],[5,2],[11,1],[11,1],[13,3],[15,1],[18,1],[17,0],[17,2],[21,3],[24,1],[24,1],[23,1],[23,2],[20,1],[20,1],[20,1],[20,1],[26,1],[26,1],[27,3],[28,3],[30,0],[30,2],[32,1],[32,1],[12,3],[35,0],[35,2],[37,1]],
8 | performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
9 |
10 | var $0 = $$.length - 1;
11 | switch (yystate) {
12 | case 1:
13 | yy.output = new yy.File (_$[$0].first_line, $$[$0]);
14 |
15 | break;
16 | case 2:
17 | this.$ = new yy.TextZone (_$[$0].first_line, $$[$0]);
18 |
19 | break;
20 | case 3:
21 | this.$ = $$[$0-1];
22 |
23 | break;
24 | case 4: this.$ = yytext;
25 | break;
26 | case 5: this.$ = [];
27 | break;
28 | case 6:
29 | $$[$0-1].push ($$[$0]);
30 | this.$ = $$[$0-1];
31 |
32 | break;
33 | case 7:
34 | var v = $$[$0-1];
35 | // Squish blocks of text together if possible
36 | if (!v.length || !v[v.length - 1].pushText ($$[$0])) {
37 | v.push (new yy.Text (_$[$0].first_line, $$[$0]));
38 | }
39 | this.$ = v;
40 |
41 | break;
42 | case 10:
43 | this.$ = $$[$0-1];
44 |
45 | break;
46 | case 11:
47 | this.$ = yy.JsZone (_$[$0].first_line, $$[$0]);
48 |
49 | break;
50 | case 12: this.$ = yytext;
51 | break;
52 | case 13: this.$ = [];
53 | break;
54 | case 14:
55 | this.$ = $$[$0-1].concat ($$[$0]);
56 |
57 | break;
58 | case 15:
59 | this.$ = yy.makeForeach (_$[$0-2].first_line, _$[$0].last_line, $$[$0-1], $$[$0]);
60 |
61 | break;
62 | case 16: this.$ = $$[$0];
63 | break;
64 | case 17: this.$ = new yy.Js (_$[$0].first_line, yytext);
65 | break;
66 | case 18: this.$ = [ $$[$0] ];
67 | break;
68 | case 19: $$[$0-1].push ($$[$0]); this.$ = $$[$0-1];
69 | break;
70 | case 20: this.$ = [ $$[$0] ];
71 | break;
72 | case 21:
73 | this.$ = [ new yy.Js (_$[$0].first_line, " { "),
74 | $$[$0],
75 | new yy.Js (_$[$0].last_line, " } ")
76 | ];
77 |
78 | break;
79 | case 22: this.$ = $$[$0];
80 | break;
81 | case 23: this.$ = [ new yy.Js (_$[$0].last_line, $$[$0]) ];
82 | break;
83 | case 26:
84 | this.$ = new yy.String (_$[$0-2].first_line, $$[$0-1], "'");
85 |
86 | break;
87 | case 27:
88 | this.$ = new yy.String (_$[$0-2].first_line, $$[$0-1], "\"");
89 |
90 | break;
91 | case 28: this.$ = [];
92 | break;
93 | case 29:
94 | $$[$0-1].push ($$[$0]);
95 | this.$ = $$[$0-1];
96 |
97 | break;
98 | case 30: this.$ = yytext;
99 | break;
100 | case 31: this.$ = $$[$0];
101 | break;
102 | case 32:
103 | this.$ = new yy.InlineJs (_$[$0-2].first_line, $$[$0-1].join (""));
104 |
105 | break;
106 | case 33: this.$ = [];
107 | break;
108 | case 34:
109 | $$[$0-1].push ($$[$0]);
110 | this.$ = $$[$0-1];
111 |
112 | break;
113 | case 35: this.$ = yytext;
114 | break;
115 | }
116 | },
117 | table: [{1:[2,5],3:1,4:2,5:3,10:[2,5],14:[2,5],34:[2,5]},{1:[3]},{1:[2,1]},{1:[2,2],8:[2,2],9:5,10:[1,8],11:4,12:6,13:7,14:[1,10],34:[1,9]},{1:[2,6],8:[2,6],10:[2,6],14:[2,6],34:[2,6]},{1:[2,7],8:[2,7],10:[2,7],14:[2,7],34:[2,7]},{1:[2,8],8:[2,8],10:[2,8],14:[2,8],34:[2,8]},{1:[2,9],8:[2,9],10:[2,9],14:[2,9],34:[2,9]},{1:[2,4],8:[2,4],10:[2,4],14:[2,4],34:[2,4]},{35:11,36:[2,33],38:[2,33]},{7:[2,13],15:12,16:[2,13],17:13,19:[2,13],22:[2,13],29:[2,13],31:[2,13]},{36:[1,14],37:15,38:[1,16]},{16:[1,17]},{6:20,7:[1,25],16:[2,11],18:22,19:[1,27],20:18,21:21,22:[1,26],26:19,27:23,28:24,29:[1,28],31:[1,29]},{1:[2,32],8:[2,32],10:[2,32],14:[2,32],29:[2,32],31:[2,32],33:[2,32],34:[2,32]},{36:[2,34],38:[2,34]},{36:[2,35],38:[2,35]},{1:[2,10],8:[2,10],10:[2,10],14:[2,10],34:[2,10]},{7:[2,14],16:[2,14],19:[2,14],22:[2,14],29:[2,14],31:[2,14]},{7:[2,20],16:[2,20],19:[2,20],22:[2,20],29:[2,20],31:[2,20]},{7:[2,21],16:[2,21],19:[2,21],22:[2,21],29:[2,21],31:[2,21]},{7:[2,22],16:[2,22],19:[2,22],22:[2,22],29:[2,22],31:[2,22]},{7:[2,23],16:[2,23],19:[2,23],22:[2,23],29:[2,23],31:[2,23]},{7:[2,24],16:[2,24],19:[2,24],22:[2,24],29:[2,24],31:[2,24]},{7:[2,25],16:[2,25],19:[2,25],22:[2,25],29:[2,25],31:[2,25]},{4:30,5:3,8:[2,5],10:[2,5],14:[2,5],34:[2,5]},{10:[1,32],23:31},{7:[2,12],16:[2,12],19:[2,12],22:[2,12],29:[2,12],31:[2,12]},{29:[2,28],30:33,33:[2,28],34:[2,28]},{30:34,31:[2,28],33:[2,28],34:[2,28]},{8:[1,35]},{6:38,7:[1,25],10:[1,37],24:36,25:[1,39]},{7:[2,18],10:[2,18],25:[2,18]},{12:43,29:[1,40],32:41,33:[1,42],34:[1,9]},{12:43,31:[1,44],32:41,33:[1,42],34:[1,9]},{7:[2,3],16:[2,3],19:[2,3],22:[2,3],29:[2,3],31:[2,3]},{7:[2,15],16:[2,15],19:[2,15],22:[2,15],29:[2,15],31:[2,15]},{7:[2,19],10:[2,19],25:[2,19]},{7:[2,16],16:[2,16],19:[2,16],22:[2,16],29:[2,16],31:[2,16]},{7:[2,17],16:[2,17],19:[2,17],22:[2,17],29:[2,17],31:[2,17]},{7:[2,26],16:[2,26],19:[2,26],22:[2,26],29:[2,26],31:[2,26]},{29:[2,29],31:[2,29],33:[2,29],34:[2,29]},{29:[2,30],31:[2,30],33:[2,30],34:[2,30]},{29:[2,31],31:[2,31],33:[2,31],34:[2,31]},{7:[2,27],16:[2,27],19:[2,27],22:[2,27],29:[2,27],31:[2,27]}],
118 | defaultActions: {2:[2,1]},
119 | parseError: function parseError(str, hash) {
120 | throw new Error(str);
121 | },
122 | parse: function parse(input) {
123 | var self = this,
124 | stack = [0],
125 | vstack = [null], // semantic value stack
126 | lstack = [], // location stack
127 | table = this.table,
128 | yytext = '',
129 | yylineno = 0,
130 | yyleng = 0,
131 | recovering = 0,
132 | TERROR = 2,
133 | EOF = 1;
134 |
135 | //this.reductionCount = this.shiftCount = 0;
136 |
137 | this.lexer.setInput(input);
138 | this.lexer.yy = this.yy;
139 | this.yy.lexer = this.lexer;
140 | if (typeof this.lexer.yylloc == 'undefined')
141 | this.lexer.yylloc = {};
142 | var yyloc = this.lexer.yylloc;
143 | lstack.push(yyloc);
144 |
145 | if (typeof this.yy.parseError === 'function')
146 | this.parseError = this.yy.parseError;
147 |
148 | function popStack (n) {
149 | stack.length = stack.length - 2*n;
150 | vstack.length = vstack.length - n;
151 | lstack.length = lstack.length - n;
152 | }
153 |
154 | function lex() {
155 | var token;
156 | token = self.lexer.lex() || 1; // $end = 1
157 | // if token isn't its numeric value, convert
158 | if (typeof token !== 'number') {
159 | token = self.symbols_[token] || token;
160 | }
161 | return token;
162 | }
163 |
164 | var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected;
165 | while (true) {
166 | // retreive state number from top of stack
167 | state = stack[stack.length-1];
168 |
169 | // use default actions if available
170 | if (this.defaultActions[state]) {
171 | action = this.defaultActions[state];
172 | } else {
173 | if (symbol == null)
174 | symbol = lex();
175 | // read action for current state and first input
176 | action = table[state] && table[state][symbol];
177 | }
178 |
179 | // handle parse error
180 | _handle_error:
181 | if (typeof action === 'undefined' || !action.length || !action[0]) {
182 |
183 | if (!recovering) {
184 | // Report error
185 | expected = [];
186 | for (p in table[state]) if (this.terminals_[p] && p > 2) {
187 | expected.push("'"+this.terminals_[p]+"'");
188 | }
189 | var errStr = '';
190 | if (this.lexer.showPosition) {
191 | errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'";
192 | } else {
193 | errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " +
194 | (symbol == 1 /*EOF*/ ? "end of input" :
195 | ("'"+(this.terminals_[symbol] || symbol)+"'"));
196 | }
197 | this.parseError(errStr,
198 | {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
199 | }
200 |
201 | // just recovered from another error
202 | if (recovering == 3) {
203 | if (symbol == EOF) {
204 | throw new Error(errStr || 'Parsing halted.');
205 | }
206 |
207 | // discard current lookahead and grab another
208 | yyleng = this.lexer.yyleng;
209 | yytext = this.lexer.yytext;
210 | yylineno = this.lexer.yylineno;
211 | yyloc = this.lexer.yylloc;
212 | symbol = lex();
213 | }
214 |
215 | // try to recover from error
216 | while (1) {
217 | // check for error recovery rule in this state
218 | if ((TERROR.toString()) in table[state]) {
219 | break;
220 | }
221 | if (state == 0) {
222 | throw new Error(errStr || 'Parsing halted.');
223 | }
224 | popStack(1);
225 | state = stack[stack.length-1];
226 | }
227 |
228 | preErrorSymbol = symbol; // save the lookahead token
229 | symbol = TERROR; // insert generic error symbol as new lookahead
230 | state = stack[stack.length-1];
231 | action = table[state] && table[state][TERROR];
232 | recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
233 | }
234 |
235 | // this shouldn't happen, unless resolve defaults are off
236 | if (action[0] instanceof Array && action.length > 1) {
237 | throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol);
238 | }
239 |
240 | switch (action[0]) {
241 |
242 | case 1: // shift
243 | //this.shiftCount++;
244 |
245 | stack.push(symbol);
246 | vstack.push(this.lexer.yytext);
247 | lstack.push(this.lexer.yylloc);
248 | stack.push(action[1]); // push state
249 | symbol = null;
250 | if (!preErrorSymbol) { // normal execution/no error
251 | yyleng = this.lexer.yyleng;
252 | yytext = this.lexer.yytext;
253 | yylineno = this.lexer.yylineno;
254 | yyloc = this.lexer.yylloc;
255 | if (recovering > 0)
256 | recovering--;
257 | } else { // error just occurred, resume old lookahead f/ before error
258 | symbol = preErrorSymbol;
259 | preErrorSymbol = null;
260 | }
261 | break;
262 |
263 | case 2: // reduce
264 | //this.reductionCount++;
265 |
266 | len = this.productions_[action[1]][1];
267 |
268 | // perform semantic action
269 | yyval.$ = vstack[vstack.length-len]; // default to $$ = $1
270 | // default location, uses first token for firsts, last for lasts
271 | yyval._$ = {
272 | first_line: lstack[lstack.length-(len||1)].first_line,
273 | last_line: lstack[lstack.length-1].last_line,
274 | first_column: lstack[lstack.length-(len||1)].first_column,
275 | last_column: lstack[lstack.length-1].last_column
276 | };
277 | r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
278 |
279 | if (typeof r !== 'undefined') {
280 | return r;
281 | }
282 |
283 | // pop off stack
284 | if (len) {
285 | stack = stack.slice(0,-1*len*2);
286 | vstack = vstack.slice(0, -1*len);
287 | lstack = lstack.slice(0, -1*len);
288 | }
289 |
290 | stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce)
291 | vstack.push(yyval.$);
292 | lstack.push(yyval._$);
293 | // goto new state = table[STATE][NONTERMINAL]
294 | newState = table[stack[stack.length-2]][stack[stack.length-1]];
295 | stack.push(newState);
296 | break;
297 |
298 | case 3: // accept
299 | return true;
300 | }
301 |
302 | }
303 |
304 | return true;
305 | }};
306 | /* Jison generated lexer */
307 | var lexer = (function(){
308 | var lexer = ({EOF:1,
309 | parseError:function parseError(str, hash) {
310 | if (this.yy.parseError) {
311 | this.yy.parseError(str, hash);
312 | } else {
313 | throw new Error(str);
314 | }
315 | },
316 | setInput:function (input) {
317 | this._input = input;
318 | this._more = this._less = this.done = false;
319 | this.yylineno = this.yyleng = 0;
320 | this.yytext = this.matched = this.match = '';
321 | this.conditionStack = ['INITIAL'];
322 | this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
323 | return this;
324 | },
325 | input:function () {
326 | var ch = this._input[0];
327 | this.yytext+=ch;
328 | this.yyleng++;
329 | this.match+=ch;
330 | this.matched+=ch;
331 | var lines = ch.match(/\n/);
332 | if (lines) this.yylineno++;
333 | this._input = this._input.slice(1);
334 | return ch;
335 | },
336 | unput:function (ch) {
337 | this._input = ch + this._input;
338 | return this;
339 | },
340 | more:function () {
341 | this._more = true;
342 | return this;
343 | },
344 | pastInput:function () {
345 | var past = this.matched.substr(0, this.matched.length - this.match.length);
346 | return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
347 | },
348 | upcomingInput:function () {
349 | var next = this.match;
350 | if (next.length < 20) {
351 | next += this._input.substr(0, 20-next.length);
352 | }
353 | return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
354 | },
355 | showPosition:function () {
356 | var pre = this.pastInput();
357 | var c = new Array(pre.length + 1).join("-");
358 | return pre + this.upcomingInput() + "\n" + c+"^";
359 | },
360 | next:function () {
361 | if (this.done) {
362 | return this.EOF;
363 | }
364 | if (!this._input) this.done = true;
365 |
366 | var token,
367 | match,
368 | tempMatch,
369 | index,
370 | col,
371 | lines;
372 | if (!this._more) {
373 | this.yytext = '';
374 | this.match = '';
375 | }
376 | var rules = this._currentRules();
377 | for (var i=0;i < rules.length; i++) {
378 | tempMatch = this._input.match(this.rules[rules[i]]);
379 | if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
380 | match = tempMatch;
381 | index = i;
382 | if (!this.options.flex) break;
383 | }
384 | }
385 | if (match) {
386 | lines = match[0].match(/\n.*/g);
387 | if (lines) this.yylineno += lines.length;
388 | this.yylloc = {first_line: this.yylloc.last_line,
389 | last_line: this.yylineno+1,
390 | first_column: this.yylloc.last_column,
391 | last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length}
392 | this.yytext += match[0];
393 | this.match += match[0];
394 | this.yyleng = this.yytext.length;
395 | this._more = false;
396 | this._input = this._input.slice(match[0].length);
397 | this.matched += match[0];
398 | token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]);
399 | if (token) return token;
400 | else return;
401 | }
402 | if (this._input === "") {
403 | return this.EOF;
404 | } else {
405 | this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
406 | {text: "", token: null, line: this.yylineno});
407 | }
408 | },
409 | lex:function lex() {
410 | var r = this.next();
411 | if (typeof r !== 'undefined') {
412 | return r;
413 | } else {
414 | return this.lex();
415 | }
416 | },
417 | begin:function begin(condition) {
418 | this.conditionStack.push(condition);
419 | },
420 | popState:function popState() {
421 | return this.conditionStack.pop();
422 | },
423 | _currentRules:function _currentRules() {
424 | return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
425 | },
426 | topState:function () {
427 | return this.conditionStack[this.conditionStack.length-2];
428 | },
429 | pushState:function begin(condition) {
430 | this.begin(condition);
431 | }});
432 | lexer.options = {};
433 | lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
434 |
435 | var YYSTATE=YY_START
436 | switch($avoiding_name_collisions) {
437 | case 0: this.begin ('COMMENT_MODE');
438 | break;
439 | case 1: this.begin ('JS_MODE'); return 14;
440 | break;
441 | case 2: this.popState (); return 8;
442 | break;
443 | case 3: this.begin ('IJS_MODE'); return 34;
444 | break;
445 | case 4: yy_.yytext = yy_.yytext.slice(1); return 10;
446 | break;
447 | case 5: return 10;
448 | break;
449 | case 6: this.popState (); return 16;
450 | break;
451 | case 7: this.begin ('TEXT_MODE'); return 7;
452 | break;
453 | case 8: this.begin ('Q2_MODE'); return 31;
454 | break;
455 | case 9: this.begin ('Q1_MODE'); return 29;
456 | break;
457 | case 10: this.begin ('CC_MODE');
458 | break;
459 | case 11:/* skip over C++-style comments */
460 | break;
461 | case 12:return 'ENDOFFILE';
462 | break;
463 | case 13: this.begin ('FOREACH_MODE'); return 22;
464 | break;
465 | case 14: return 19;
466 | break;
467 | case 15: return 19;
468 | break;
469 | case 16:
470 | return 10;
471 |
472 | break;
473 | case 17:
474 | this.popState ();
475 | this.begin ("TEXT_MODE");
476 | return 7;
477 |
478 | break;
479 | case 18: this.popState (); return 25;
480 | break;
481 | case 19: this.popState();
482 | break;
483 | case 20:/* skip over comments */
484 | break;
485 | case 21: this.begin ('IJS2_MODE'); return 38;
486 | break;
487 | case 22: this.popState (); return 36;
488 | break;
489 | case 23: this.popState (); return 38;
490 | break;
491 | case 24: return 38;
492 | break;
493 | case 25:return 'ENDOFFILE';
494 | break;
495 | case 26:return 33;
496 | break;
497 | case 27:return 33;
498 | break;
499 | case 28: this.popState (); return "QUOTE2";
500 | break;
501 | case 29:return 'ENDOFFILE';
502 | break;
503 | case 30:return 33;
504 | break;
505 | case 31:return 33;
506 | break;
507 | case 32: this.popState (); return "QUOTE1";
508 | break;
509 | case 33:return 'ENDOFFILE';
510 | break;
511 | case 34: this.popState();
512 | break;
513 | case 35:/* ignore */
514 | break;
515 | case 36:/* ignore */
516 | break;
517 | case 37:return 'ENDOFFILE';
518 | break;
519 | }
520 | };
521 | lexer.rules = [/^\{%%/,/^\{%/,/^\}\}/,/^%\{/,/^\\[%{}]/,/^[^{}%\\]+|[\\{}%]/,/^%\}/,/^\{\{/,/^"/,/^'/,/^\/\*/,/^\/\/.*/,/^$/,/^foreach\b/,/^[^{(%"'/f]+/,/^[{(%"'/f]/,/^[^{]+/,/^\{\{/,/^[{]/,/^%%\}/,/^[^%]+|[%]/,/^\{/,/^\}/,/^\}/,/^[^{}]+/,/^$/,/^\\./,/^[^\\"]+/,/^"/,/^$/,/^\\./,/^[^\\']+/,/^'/,/^$/,/^\*\//,/^\*/,/^[^*]+/,/^$/];
522 | lexer.conditions = {"COMMENT_MODE":{"rules":[19,20],"inclusive":true},"JS_MODE":{"rules":[6,7,8,9,10,11,12,13,14,15],"inclusive":true},"Q1_MODE":{"rules":[30,31,32,33],"inclusive":true},"Q2_MODE":{"rules":[26,27,28,29],"inclusive":true},"IJS_MODE":{"rules":[21,22,24,25],"inclusive":true},"IJS2_MODE":{"rules":[23,24,25],"inclusive":true},"CC_MODE":{"rules":[34,35,36,37],"inclusive":true},"FOREACH_MODE":{"rules":[16,17,18],"inclusive":true},"TEXT_MODE":{"rules":[0,1,2,3,4,5],"inclusive":true},"INITIAL":{"rules":[0,1,3,4,5],"inclusive":true}};
523 | return lexer;})()
524 | parser.lexer = lexer;
525 | return parser;
526 | })();
527 | if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
528 | exports.parser = parser;
529 | exports.parse = function () { return parser.parse.apply(parser, arguments); }
530 | exports.main = function commonjsMain(args) {
531 | if (!args[1])
532 | throw new Error('Usage: '+args[0]+' FILE');
533 | if (typeof process !== 'undefined') {
534 | var source = require('fs').readFileSync(require('path').join(process.cwd(), args[1]), "utf8");
535 | } else {
536 | var cwd = require("file").path(require("file").cwd());
537 | var source = cwd.join(args[1]).read({charset: "utf-8"});
538 | }
539 | return exports.parser.parse(source);
540 | }
541 | if (typeof module !== 'undefined' && require.main === module) {
542 | exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args);
543 | }
544 | }
--------------------------------------------------------------------------------