├── 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 | 72 | }} 73 | %} 74 | 75 | }} 76 | %} 77 |
%{col}
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 | } --------------------------------------------------------------------------------