UNPKG

jscc-parser

Version:

A LALR(1) Parser Generator for JavaScript written in JavaScript.

1,754 lines (1,445 loc) 123 kB
/* -HEADER---------------------------------------------------------------------- JS/CC LALR(1) Parser Generator Copyright (C) 2007-2012 by Phorward Software Technologies, Jan Max Meyer http://jscc.phorward-software.com ++ contact<<AT>>phorward-software<<DOT>>com File: global.js Author: Jan Max Meyer Usage: General variables, constants and defines You may use, modify and distribute this software under the terms and conditions of the BSD license. Please see LICENSE for more information. ----------------------------------------------------------------------------- */ /// constructors generator: function createConstructor(a){ var arr={}; for(var i = 0;i<a.length;i++) arr[a[i]]=true; return function(o){ o=o||{}; for(var f in o) if(f in arr) this[f]=o[f]; } } /* Constants */ //Program version info var JSCC_VERSION = "0.37"; //Symbol types var SYM_NONTERM = {}; var SYM_TERM = {}; //Symbol special cases var SPECIAL_NO_SPECIAL = {}; var SPECIAL_EOF = {}; var SPECIAL_WHITESPACE = {}; var SPECIAL_ERROR = {}; //Symbol associativity var ASSOC_NONE = {}; var ASSOC_LEFT = {}; var ASSOC_RIGHT = {}; var ASSOC_NOASSOC = {}; //Token-Definitions var TOK_ERROR = {}; var TOK_EOF = {}; var TOK_ID = {}; var TOK_TERM = {}; var TOK_TERM_S = {}; var TOK_COLON = {}; var TOK_SEMICOLON = {}; var TOK_DELIMITER = {}; var TOK_SEMANTIC = {}; var TOK_IGNORE = {}; var TOK_PREFIX = {}; //Miscelleanous constants var DEF_PROD_CODE = "%% = %1;"; //Code generation/output modes var MODE_GEN_TEXT = {}; var MODE_GEN_JS = {}; var MODE_GEN_HTML = {}; //Executable environment var EXEC_CONSOLE = {}; var EXEC_WEB = {}; //Lexer state construction var MIN_CHAR = 0; var MAX_CHAR = 255; var EDGE_FREE = {}; var EDGE_EPSILON = {}; var EDGE_CHAR = {}; /* Structs */ var SYMBOL=createConstructor(['id','kind','label','prods','first','associativity','level','code','special','nullable','defined','defined_at','used_at']); var PROD=createConstructor(['id','lhs','rhs','level','code']); var ITEM=createConstructor(['prod','dot_offset','lookahead']); var STATE=createConstructor(['kernel','epsilon','def_act','done','closed','actionrow','gotorow']); var NFA=createConstructor(['edge','ccl','follow','follow2','accept','weight']); var DFA=createConstructor(['line','object','nfa_set','accept','done','group']); var PARAM=createConstructor(['start','end']); var TOKEN=createConstructor(['token','lexeme']); function NFAStates(){ this.value = []; } NFAStates.prototype.create = function(){ var nfa; var i; /* Use an empty item if available, else create a new one... */ for( i = 0; i < this.value.length; i++ ) if( this.value[i].edge === EDGE_FREE ) break; if( i == this.value.length ){ nfa = new NFA() this.value.push( nfa ); }else nfa = this.value[i]; nfa.edge = EDGE_EPSILON; nfa.ccl=new BitSet(MAX_CHAR); nfa.accept = -1; nfa.follow = -1; nfa.follow2 = -1; nfa.weight = -1; //created_nfas.push( i ); return i; } /* Globals (will be initialized via reset_all()!) */ var symbols; var productions; var states; var lex; var nfa_states, NFA_states; var dfa_states; var whitespace_token; var code_head; var code_foot; var file; var errors; var show_errors; var warnings; var show_warnings; var shifts; var reduces; var gotos; var exec_mode; var assoc_level; var regex_weight; /* -MODULE---------------------------------------------------------------------- JS/CC LALR(1) Parser Generator Copyright (C) 2007-2012 by Phorward Software Technologies, Jan Max Meyer Contibutions by Louis P. Santillan <lpsantil@gmail.com> http://jscc.phorward-software.com ++ contact<<AT>>phorward-software<<DOT>>com File: io_rhino.js Author: Louis P. Santillan Jan Max Meyer Usage: Console-based wrapper function set for JS/CC to be executed via Mozilla/Rhino. You may use, modify and distribute this software under the terms and conditions of the BSD license. Please see LICENSE for more information. ----------------------------------------------------------------------------- */ var DEFAULT_DRIVER = "driver_node.js_"; var sys=require("util"); var fs=require("fs"); function _error( msg ) { if( show_errors ) sys.print( file + ": error: " + msg ); errors++; } function _warning( msg ){ sys.print( file + ": warning: " + msg ); warnings++; } var _print=sys.puts; var _quit=process.exit; function read_file( file ) { return fs.readFileSync(file,"utf-8"); } function write_file( file, content ) { return fs.writeFileSync(file, content, "utf-8"); } function get_arguments() { return process.argv.slice(2); } /* -MODULE---------------------------------------------------------------------- JS/CC LALR(1) Parser Generator Copyright (C) 2007-2012 by Phorward Software Technologies, Jan Max Meyer http://jscc.phorward-software.com ++ contact<<AT>>phorward-software<<DOT>>com File: debug.js Author: Jan Max Meyer Usage: Debug-Functions / Detail progress output These functions had been designed to both output plain text as well as HTML-formatted output. You may use, modify and distribute this software under the terms and conditions of the BSD license. Please see LICENSE for more information. ----------------------------------------------------------------------------- */ function print_symbols( mode ) { if( mode == MODE_GEN_HTML ) { _print( "<table class=\"debug\" cellpadding=\"0\" cellspacing=\"0\">" ); _print( "<tr>" ); _print( "<td class=\"tabtitle\" colspan=\"3\">Symbols Overview</td>" ); _print( "</tr>" ); _print( "<tr>" ); _print( "<td class=\"coltitle\">Symbol</td>" ); _print( "<td class=\"coltitle\">Type</td>" ); _print( "</tr>" ); } else if( mode == MODE_GEN_TEXT ) _print( "--- Symbol Dump ---" ); for( var i = 0; i < symbols.length; i++ ) { if( mode == MODE_GEN_HTML ) { _print( "<tr>" ); _print( "<td>" ); _print( symbols[i].label ); _print( "</td>" ); _print( "<td>" ); _print( ( ( symbols[i].kind == SYM_NONTERM ) ? "Non-terminal" : "Terminal" ) ); _print( "</td>" ); } else if( mode == MODE_GEN_TEXT ) { var output = new String(); output = symbols[i].label; for( var j = output.length; j < 20; j++ ) output += " "; output += ( ( symbols[i].kind == SYM_NONTERM ) ? "Non-terminal" : "Terminal" ); if( symbols[i].kind == SYM_TERM ) { for( var j = output.length; j < 40; j++ ) output += " "; output += symbols[i].level + "/"; switch( symbols[i].assoc ) { case ASSOC_NONE: output += "^"; break; case ASSOC_LEFT: output += "<"; break; case ASSOC_RIGHT: output += ">"; break; } } _print( output ); } } if( mode == MODE_GEN_HTML ) _print( "</table>" ); else if( mode == MODE_GEN_TEXT ) _print( "" ); } function print_grammar( mode ) { if( mode == MODE_GEN_HTML ) { _print( "<table class=\"debug\" cellpadding=\"0\" cellspacing=\"0\">" ); _print( "<tr>" ); _print( "<td class=\"tabtitle\" colspan=\"3\">Grammar Overview</td>" ); _print( "</tr>" ); _print( "<tr>" ); _print( "<td class=\"coltitle\">Left-hand side</td>" ); _print( "<td class=\"coltitle\">FIRST-set</td>" ); _print( "<td class=\"coltitle\">Right-hand side</td>" ); _print( "</tr>" ); for( var i = 0; i < symbols.length; i++ ) { _print( "<tr>" ); //alert( "symbols " + i + " = " + symbols[i].label + "(" + symbols[i].kind + ")" ); if( symbols[i].kind == SYM_NONTERM ) { _print( "<td>" ); _print( symbols[i].label ); _print( "</td>" ); _print( "<td>" ); for( var j = 0; j < symbols[i].first.length; j++ ) { _print( " <b>" + symbols[symbols[i].first[j]].label + "</b> " ); } _print( "</td>" ); _print( "<td>" ); for( var j = 0; j < symbols[i].prods.length; j++ ) { for( var k = 0; k < productions[symbols[i].prods[j]].rhs.length; k++ ) { if( symbols[productions[symbols[i].prods[j]].rhs[k]].kind == SYM_TERM ) _print( " <b>" + symbols[productions[symbols[i].prods[j]].rhs[k]].label + "</b> " ); else _print( " " + symbols[productions[symbols[i].prods[j]].rhs[k]].label + " " ); } _print( "<br />" ); } _print( "</td>" ); } _print( "</tr>" ); } _print( "</table>" ); } else if( mode == MODE_GEN_TEXT ) { var output = new String(); for( var i = 0; i < symbols.length; i++ ) { if( symbols[i].kind == SYM_NONTERM ) { output += symbols[i].label + " {"; for( var j = 0; j < symbols[i].first.length; j++ ) output += " " + symbols[symbols[i].first[j]].label + " "; output += "}\n"; for( var j = 0; j < symbols[i].prods.length; j++ ) { output += "\t"; for( var k = 0; k < productions[symbols[i].prods[j]].rhs.length; k++ ) { if( symbols[productions[symbols[i].prods[j]].rhs[k]].kind == SYM_TERM ) output += "#" + symbols[productions[symbols[i].prods[j]].rhs[k]].label + " "; else output += symbols[productions[symbols[i].prods[j]].rhs[k]].label + " "; } output += "\n"; } } } _print( output ); } } function print_item_set( mode, label, item_set ) { var i, j; if( item_set.length == 0 ) return; if( mode == MODE_GEN_HTML ) { _print( "<table class=\"debug\" cellpadding=\"0\" cellspacing=\"0\">" ); _print( "<tr>" ); _print( "<td class=\"tabtitle\" colspan=\"2\">" + label + "</td>" ); _print( "</tr>" ); _print( "<tr>" ); _print( "<td class=\"coltitle\" width=\"35%\">Lookahead</td>" ); _print( "<td class=\"coltitle\" width=\"65%\">Production</td>" ); _print( "</tr>" ); } else if( mode == MODE_GEN_TEXT ) _print( "--- " + label + " ---" ); for( i = 0; i < item_set.length; i++ ) { if( mode == MODE_GEN_HTML ) { _print( "<tr>" ); //alert( "symbols " + i + " = " + symbols[i].label + "(" + symbols[i].kind + ")" ); _print( "<td>" ); for( j = 0; j < item_set[i].lookahead.length; j++ ) { _print( " <b>" + symbols[item_set[i].lookahead[j]].label + "</b> " ); } _print( "</td>" ); _print( "<td>" ); _print( symbols[productions[item_set[i].prod].lhs].label + " -&gt; " ); for( j = 0; j < productions[item_set[i].prod].rhs.length; j++ ) { if( j == item_set[i].dot_offset ) _print( "." ); if( symbols[productions[item_set[i].prod].rhs[j]].kind == SYM_TERM ) _print( " <b>" + symbols[productions[item_set[i].prod].rhs[j]].label + "</b> " ); else _print( " " + symbols[productions[item_set[i].prod].rhs[j]].label + " " ); } if( j == item_set[i].dot_offset ) _print( "." ); _print( "</td>" ); _print( "</tr>" ); } else if( mode == MODE_GEN_TEXT ) { var out = new String(); out += symbols[productions[item_set[i].prod].lhs].label; for( j = out.length; j < 20; j++ ) out += " "; out += " -> "; for( j = 0; j < productions[item_set[i].prod].rhs.length; j++ ) { if( j == item_set[i].dot_offset ) out += "."; if( symbols[productions[item_set[i].prod].rhs[j]].kind == SYM_TERM ) out += " #" + symbols[productions[item_set[i].prod].rhs[j]].label + " "; else out += " " + symbols[productions[item_set[i].prod].rhs[j]].label + " "; } if( j == item_set[i].dot_offset ) out += "."; for( j = out.length; j < 60; j++ ) out += " "; out += "{ "; for( j = 0; j < item_set[i].lookahead.length; j++ ) out += "#" + symbols[item_set[i].lookahead[j]].label + " "; out += "}"; _print( out ); } } if( mode == MODE_GEN_HTML ) _print( "</table>" ); } /* -MODULE---------------------------------------------------------------------- JS/CC LALR(1) Parser Generator Copyright (C) 2007-2012 by Phorward Software Technologies, Jan Max Meyer http://jscc.phorward-software.com ++ contact<<AT>>phorward-software<<DOT>>com File: first.js Author: Jan Max Meyer Usage: FIRST-set calculation You may use, modify and distribute this software under the terms and conditions of the BSD license. Please see LICENSE for more information. ----------------------------------------------------------------------------- */ /* -FUNCTION-------------------------------------------------------------------- Function: first() Author: Jan Max Meyer Usage: Computes the FIRST-sets for all non-terminals of the grammar. Must be called right after the parse and before the table generation methods are performed. Parameters: void Returns: void ~~~ CHANGES & NOTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date: Author: Note: 25.08.2008 Jan Max Meyer Here was a bad bug that sometimes came up when nonterminals are nullable. An example is the grammar "A" "B"; ## x: y "B"; y: y "A" | ; Now it works... embarrassing bug... ;( ----------------------------------------------------------------------------- */ function first() { var cnt = 0, old_cnt = 0; var nullable; do { old_cnt = cnt; cnt = 0; for( var i = 0; i < symbols.length; i++ ) { if( symbols[i].kind == SYM_NONTERM ) { for( var j = 0; j < symbols[i].prods.length; j++ ) { nullable = false; for( var k = 0; k < productions[symbols[i].prods[j]].rhs.length; k++ ) { symbols[i].first = union( symbols[i].first, symbols[productions[symbols[i].prods[j]].rhs[k]].first ); nullable = symbols[productions[symbols[i].prods[j]].rhs[k]].nullable; if( !nullable ) break; } cnt += symbols[i].first.length; if( k == productions[symbols[i].prods[j]].rhs.length ) nullable = true; symbols[i].nullable |= nullable; } } } //_print( "first: cnt = " + cnt + " old_cnt = " + old_cnt + "<br />" ); } while( cnt != old_cnt ); } /* -FUNCTION-------------------------------------------------------------------- Function: rhs_first() Author: Jan Max Meyer Usage: Returns all terminals that are possible from a given position of a production's right-hand side. Parameters: item Item to which the lookaheads are added to. p The production where the computation should be done on. begin The offset of the symbol where rhs_first() begins its calculation from. Returns: true If the whole rest of the right-hand side can be null (epsilon), false else. ~~~ CHANGES & NOTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date: Author: Note: ----------------------------------------------------------------------------- */ function rhs_first( item, p, begin ) { var f, i; for( i = begin; i < p.rhs.length; i++ ) { item.lookahead = union( item.lookahead, symbols[p.rhs[i]].first ); if( !symbols[p.rhs[i]].nullable ) return false; } return true; } /* -MODULE---------------------------------------------------------------------- JS/CC LALR(1) Parser Generator Copyright (C) 2007-2009 by J.M.K S.F. Software Technologies, Jan Max Meyer http://jscc.phorward-software.com ++ contact<<AT>>phorward-software<<DOT>>com File: printtab.js Author: Jan Max Meyer Usage: Functions for printing the parse tables and related functions. You may use, modify and distribute this software under the terms and conditions of the BSD license. Please see LICENSE for more information. ----------------------------------------------------------------------------- */ /* 15.04.2009 Jan Max Meyer Removed the HTML-Code generation flag and re- placed it with text output; In WebEnv, this will be placed in <pre>-tags, and we finally can view the parse-tables even on the console. */ /* -FUNCTION-------------------------------------------------------------------- Function: print_parse_tables() Author: Jan Max Meyer Usage: Prints the parse tables in a desired format. Parameters: mode The output mode. This can be either MODE_GEN_JS to create JavaScript/ JScript code as output or MODE_GEN_HTML to create HTML-tables as output (the HTML-tables are formatted to look nice with the JS/CC Web Environment). Returns: code The code to be printed to a file or web-site. ~~~ CHANGES & NOTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date: Author: Note: 16.04.2009 Jan Max Meyer New table generator section to build default reduction table on each state. ----------------------------------------------------------------------------- */ function print_parse_tables( mode ){ var code = ""; var i, j, deepest = 0, val; switch(mode){ case MODE_GEN_HTML:case "html":{ code += "<table class=\"print\" cellpadding=\"0\" cellspacing=\"0\">"; code += "<tr>"; code += "<td class=\"tabtitle\" colspan=\"2\">Pop-Table</td>"; code += "</tr>"; code += "<td class=\"coltitle\" width=\"1%\" style=\"border-right: 1px solid lightgray;\">Left-hand side</td>"; code += "<td class=\"coltitle\">Number of symbols to pop</td>"; code += "</tr>"; for( i = 0; i < productions.length; i++ ){ code += "<tr>"; code += "<td style=\"border-right: 1px solid lightgray;\">" + productions[i].lhs + "</td>"; code += "<td>" + productions[i].rhs.length + "</td>"; code += "</tr>"; } code += "</table>"; for( i = 0; i < symbols.length; i++ ) if( symbols[i].kind == SYM_TERM ) deepest++; code += "<table class=\"print\" cellpadding=\"0\" cellspacing=\"0\">"; code += "<tr>"; code += "<td class=\"tabtitle\" colspan=\"" + (deepest + 1) + "\">Action-Table</td>"; code += "</tr>"; code += "<td class=\"coltitle\" width=\"1%\" style=\"border-right: 1px solid lightgray;\">State</td>"; for( i = 0; i < symbols.length; i++ ) { if( symbols[i].kind == SYM_TERM ) code += "<td><b>" + symbols[i].label + "</b></td>"; } code += "</tr>"; for( i = 0; i < states.length; i++ ) { code += "<tr>" ; code += "<td class=\"coltitle\" style=\"border-right: 1px solid lightgray;\">" + i + "</td>"; for( j = 0; j < symbols.length; j++ ) { if( symbols[j].kind == SYM_TERM ) { code += "<td>"; if( ( val = get_table_entry( states[i].actionrow, j ) ) != void(0) ) { if( val <= 0 ) code += "r" + (val * -1); else code += "s" + val; } code += "</td>"; } } code += "</tr>" ; } code += "</table>"; for( i = 0; i < symbols.length; i++ ) if( symbols[i].kind == SYM_NONTERM ) deepest++; code += "<table class=\"print\" cellpadding=\"0\" cellspacing=\"0\">"; code += "<tr>"; code += "<td class=\"tabtitle\" colspan=\"" + (deepest + 1) + "\">Goto-Table</td>"; code += "</tr>"; code += "<td class=\"coltitle\" width=\"1%\" style=\"border-right: 1px solid lightgray;\">State</td>"; for( i = 0; i < symbols.length; i++ ) { if( symbols[i].kind == SYM_NONTERM ) code += "<td>" + symbols[i].label + "</td>"; } code += "</tr>"; for( i = 0; i < states.length; i++ ) { code += "<tr>" ; code += "<td class=\"coltitle\" style=\"border-right: 1px solid lightgray;\">" + i + "</td>"; for( j = 0; j < symbols.length; j++ ) { if( symbols[j].kind == SYM_NONTERM ) { code += "<td>"; if( ( val = get_table_entry( states[i].gotorow, j ) ) != void(0) ) { code += val; } code += "</td>"; } } code += "</tr>" ; } code += "</table>"; code += "<table class=\"print\" cellpadding=\"0\" cellspacing=\"0\">"; code += "<tr>"; code += "<td class=\"tabtitle\" colspan=\"2\">Default Actions Table</td>"; code += "</tr>"; code += "<td class=\"coltitle\" width=\"1%\" style=\"border-right: 1px solid lightgray;\">Left-hand side</td>"; code += "<td class=\"coltitle\">Number of symbols to pop</td>"; code += "</tr>"; for( i = 0; i < states.length; i++ ){ code += "<tr>"; code += "<td style=\"border-right: 1px solid lightgray;\">State " + i + "</td>"; code += "<td>" + ( ( states[ i ].def_act < 0 ) ? "(none)" : states[ i ].def_act ) + "</td>"; code += "</tr>"; } code += "</table>"; break;} case MODE_GEN_JS:case "js":{ var pop_tab_json =[]; for( i = 0; i < productions.length; i++ ) pop_tab_json.push([productions[i].lhs,productions[i].rhs.length]); code +="\nvar pop_tab ="+JSON.stringify(pop_tab_json)+";\n"; var act_tab_json =[]; for( i = 0; i < states.length; i++ ){ var act_tab_json_item=[]; for( j = 0; j < states[i].actionrow.length; j++ ) act_tab_json_item.push(states[i].actionrow[j][0],states[i].actionrow[j][1]); act_tab_json.push(act_tab_json_item);} code +="\n/** @type {!Array<!Array<number>>} */\nvar act_tab ="+JSON.stringify(act_tab_json)+";\n"; var goto_tab_json = []; for( i = 0; i < states.length; i++ ){ var goto_tab_json_item=[]; for( j = 0; j < states[i].gotorow.length; j++ ) goto_tab_json_item.push(states[i].gotorow[j][0],states[i].gotorow[j][1]); goto_tab_json.push(goto_tab_json_item);} code +="\nvar goto_tab ="+JSON.stringify(goto_tab_json)+";\n"; var defact_tab_json=[]; for( i = 0; i < states.length; i++ ) defact_tab_json.push(states[i].def_act); code +="\nvar defact_tab ="+JSON.stringify(defact_tab_json)+";\n"; break;} } return code; } /* -FUNCTION-------------------------------------------------------------------- Function: print_dfa_table() Author: Jan Max Meyer Usage: Generates a state-machine construction from the deterministic finite automata. Parameters: dfa_states The dfa state machine for the lexing function. Returns: code The code to be inserted into the static parser driver framework. ~~~ CHANGES & NOTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date: Author: Note: ----------------------------------------------------------------------------- */ function pack_dfa(dfa_states){ function PL(line){ var out=[]; while(line.length){ var first=line.shift(); var second=line.shift(); if(first==second) out.push(first); else out.push([first,second]);} return out;} var json=[]; for(var i=0;i<dfa_states.length;i++)(function(i){ var line=[]; for(var j=0;j<dfa_states[i].line.length;j++) if(dfa_states[i].line[j]!=-1) line[j]=dfa_states[i].line[j]; line=PL(PL(PL(PL(PL(PL(PL(PL((line))))))))); json.push({ line:line, accept:dfa_states[i].accept, }); })(i); return json; } function print_dfa_table(dfa_states){ var json=[], code; for(var i=0;i<dfa_states.length;i++)(function(i){ var line={}; for(var j=0;j<dfa_states[i].line.length;j++) if(dfa_states[i].line[j]!=-1) line[j]=dfa_states[i].line[j]; json.push({ line:line, accept:dfa_states[i].accept, }); })(i); code = JSON.stringify(pack_dfa(dfa_states)); // JSON quotes object keys, of course -- but doing so breaks code minimization, so replace keys when possible return code.replace(/"([A-Za-z_][A-Za-z0-9_]*)"\s*:/g, "$1:").replace(/,/g, ",\n\t"); } /* -FUNCTION-------------------------------------------------------------------- Function: print_symbol_labels() Author: Jan Max Meyer Usage: Prints all symbol labels into an array; This is used for error reporting purposes only in the resulting parser. Parameters: void Returns: code The code to be inserted into the static parser driver framework. ~~~ CHANGES & NOTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date: Author: Note: ----------------------------------------------------------------------------- */ function print_symbol_labels(){ for(var i=0,arr=[];i<symbols.length;i++) arr.push(symbols[i].label); return "var labels = "+JSON.stringify(symbols)+";\n\n"; } /* -FUNCTION-------------------------------------------------------------------- Function: print_term_actions() Author: Jan Max Meyer Usage: Prints the terminal symbol actions to be associated with a terminal definition into a switch-case-construct. Parameters: void Returns: code The code to be inserted into the static parser driver framework. ~~~ CHANGES & NOTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date: Author: Note: 22.08.2008 Jan Max Meyer Bugfix: %offset returned the offset BEHIND the terminal, now it's the correct value; %source, which was documented in the manual since v0.24 was not implemented. 10.12.2008 Jan Max Meyer Removed the switch...case structure and replaced it with if...else, because of new possibilities with the lexical analyzer (more lex-like beha- vior). continue can now be used in semantic actions, or break, which is automatically done in each parser template. ----------------------------------------------------------------------------- */ function print_term_actions(){ var code = "({\n"; var re = /%match|%offset|%source/; var i, j, k; var semcode; var strmatch; for( i = 0; i < symbols.length; i++ ){ if( symbols[i].kind == SYM_TERM && symbols[i].code != "" ){ code += " \"" + i + "\":"; code += " /** @suppress {uselessCode} */ function(PCB){"; semcode = ""; for( j = 0, k = 0; j < symbols[i].code.length; j++, k++ ){ strmatch = re.exec( symbols[i].code.substr( j, symbols[i].code.length ) ); if( strmatch && strmatch.index == 0 ) { if( strmatch[0] == "%match" ) semcode += "PCB.att"; else if( strmatch[0] == "%offset" ) semcode += "( PCB.offset - PCB.att.length )"; else if( strmatch[0] == "%source" ) semcode += "PCB.src"; j += strmatch[0].length - 1; k = semcode.length; } else semcode += symbols[i].code.charAt( j ); } code += " " + semcode + "\n"; code += " return PCB.att;},\n"; } } code+="\n})"; return code; } /* -FUNCTION-------------------------------------------------------------------- Function: print_actions() Author: Jan Max Meyer Usage: Generates a switch-case-construction that contains all the semantic actions. This construction should then be generated into the static parser driver template. Parameters: void Returns: code The code to be inserted into the static parser driver framework. ~~~ CHANGES & NOTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date: Author: Note: ----------------------------------------------------------------------------- */ function print_actions(){ var code = ""; var re = /%[0-9]+|%%/; var semcode, strmatch; var i, j, k, idx, src; code += "["; for( i = 0; i < productions.length; i++ ){ src = productions[i].code; semcode = "function(){\n"; semcode+="var rval;" for(j = 0, k = 0; j < src.length; j++, k++){ strmatch = re.exec(src.substr(j, src.length)); if(strmatch && strmatch.index == 0){ if(strmatch[0] == "%%") semcode += "rval"; else{ idx = parseInt( strmatch[0].substr( 1, strmatch[0].length ) ); idx = productions[i].rhs.length - idx; semcode += " arguments[" + idx + "] "; } j += strmatch[0].length - 1; k = semcode.length; } else semcode += src.charAt(j); } code += " " + semcode + "\nreturn rval;},\n"; } code += "]"; return code; } /* -FUNCTION-------------------------------------------------------------------- Function: get_eof_symbol_id() Author: Jan Max Meyer Usage: Returns the value of the eof-symbol. Parameters: Returns: eof_id The id of the EOF-symbol. ~~~ CHANGES & NOTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date: Author: Note: ----------------------------------------------------------------------------- */ function get_eof_symbol_id(){ var eof_id = -1; //Find out which symbol is for EOF! for(var i = 0; i < symbols.length; i++){ if( symbols[i].special == SPECIAL_EOF ){ eof_id = i; break; } } if( eof_id == -1 ) _error( "No EOF-symbol defined - This might not be possible (bug!)" ); return eof_id; } /* -FUNCTION-------------------------------------------------------------------- Function: get_error_symbol_id() Author: Jan Max Meyer Usage: Returns the value of the error-symbol. Parameters: Returns: eof_id The id of the EOF-symbol. ~~~ CHANGES & NOTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date: Author: Note: ----------------------------------------------------------------------------- */ function get_error_symbol_id(){ var error_id = -1; //Find out which symbol is for EOF! for( var i = 0; i < symbols.length; i++ ){ if(symbols[i].special == SPECIAL_ERROR){ error_id = i; break; } } if( error_id == -1 ) _error( "No ERROR-symbol defined - This might not be possible (bug!)" ); return error_id; } /* -FUNCTION-------------------------------------------------------------------- Function: get_whitespace_symbol_id() Author: Jan Max Meyer Usage: Returns the ID of the whitespace-symbol. Parameters: Returns: whitespace The id of the whitespace-symbol. ~~~ CHANGES & NOTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date: Author: Note: ----------------------------------------------------------------------------- */ function get_whitespace_symbol_id(){ return whitespace_token; } /* -FUNCTION-------------------------------------------------------------------- Function: get_error_state() Author: Jan Max Meyer Usage: Returns the ID of a non-existing state. Parameters: Returns: length The length of the states array. ~~~ CHANGES & NOTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date: Author: Note: ----------------------------------------------------------------------------- */ function get_error_state(){ return states.length + 1; } /* -MODULE---------------------------------------------------------------------- JS/CC LALR(1) Parser Generator Copyright (C) 2007-2009 by J.M.K S.F. Software Technologies, Jan Max Meyer http://jscc.phorward-software.com ++ contact<<AT>>phorward-software<<DOT>>com File: tabgen.js Author: Jan Max Meyer Usage: LALR(1) closure and table construction You may use, modify and distribute this software under the terms and conditions of the BSD license. Please see LICENSE for more information. ----------------------------------------------------------------------------- */ // --- Utility functions: I think there is no documentation necessary ;) --- function create_state() { var state = new STATE({ kernel:[], epsilon:[], actionrow:[], gotorow:[], done:false, closed:false, def_act:0 }); states.push( state ); return state; } function create_item( p ) { return new ITEM({ prod:p, dot_offset:0, lookahead:[] }); } function add_table_entry( row, sym, act ) { for(var i = 0; i < row.length; i++ ) if( row[i][0] == sym ) return row; row.push( [ sym, act ] ); return row; } function update_table_entry( row, sym, act ) { var i; for( i = 0; i < row.length; i++ ) if( row[i][0] == sym ) { row[i][1] = act; return row; } return row; } function remove_table_entry( row, sym ) { for(var i = 0; i < row.length; i++ ) if( row[i][0] == sym ) { row.splice( i, 1 ); return row; } return row; } function get_table_entry( row, sym ) { for(var i = 0; i < row.length; i++ ) if( row[i][0] == sym ) return row[i][1]; return void(0); } function get_undone_state() { for( var i = 0; i < states.length; i++ ) if( states[i].done == false ) return i; return -1; } function sort_partition( a, b ) { return a.prod - b.prod; } /* -FUNCTION-------------------------------------------------------------------- Function: find_symbol() Author: Jan Max Meyer Usage: Searches for a symbol using its label and kind. Parameters: label The label of the symbol. kind Type of the symbol. This can be SYM_NONTERM or SYM_TERM special Specialized symbols Returns: The index of the desired object in the symbol table, -1 if the symbol was not found. ~~~ CHANGES & NOTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date: Author: Note: 16.11.2007 Jan Max Meyer Allow to find eof_character 19.11.2008 Jan Max Meyer Special character checking ----------------------------------------------------------------------------- */ function find_symbol( label, kind, special ) { if( !special ) special = SPECIAL_NO_SPECIAL; for( var i = 0; i < symbols.length; i++ ) if( symbols[i].label.toString() == label.toString() && symbols[i].kind == kind && symbols[i].special == special ) return i; return -1; } /* -FUNCTION-------------------------------------------------------------------- Function: create_symbol() Author: Jan Max Meyer Usage: Creates a new symbol (if necessary) and appends it to the global symbol array. If the symbol does already exist, the instance of that symbol is returned only. Parameters: label The label of the symbol. In case of kind == SYM_NONTERM, the label is the name of the right-hand side, else it is the regular expression for the terminal symbol. kind Type of the symbol. This can be SYM_NONTERM or SYM_TERM special Specialized symbols Returns: The particular object of type SYMBOL. ~~~ CHANGES & NOTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date: Author: Note: 16.11.2007 Jan Max Meyer Bugfix: EOF-character is a special case! 19.11.2008 Jan Max Meyer Special character checking ----------------------------------------------------------------------------- */ function create_symbol( label, kind, special ) { var exists; if( ( exists = find_symbol( label, kind, special ) ) > -1 ) return symbols[ exists ].id; var sym = new SYMBOL({ label:label, kind:kind, prods:[], nullable:false, id:symbols.length, code:"", assoc:ASSOC_NONE, //Could be changed by grammar parser level:0, //Could be changed by grammar parser special:special, defined:false, first:[] }); if( kind == SYM_TERM ) sym.first.push( sym.id ); symbols.push( sym ); //_print( "Creating new symbol " + sym.id + " kind = " + kind + " >" + label + "<" ); return sym.id; } /* -FUNCTION-------------------------------------------------------------------- Function: item_set_equal() Author: Jan Max Meyer Usage: Checks if two item sets contain the same items. The items may only differ in their lookahead. Parameters: set1 Set to be compared with set2. set2 Set to be compared with set1. Returns: true If equal, false else. ~~~ CHANGES & NOTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date: Author: Note: ----------------------------------------------------------------------------- */ function item_set_equal( set1, set2 ) { var i, j, cnt = 0; if( set1.length != set2.length ) return false; for( i = 0; i < set1.length; i++ ) { for( j = 0; j < set2.length; j++ ) { if( set1[i].prod == set2[j].prod && set1[i].dot_offset == set2[j].dot_offset ) { cnt++; break; } } } return cnt == set1.length; } /* -FUNCTION-------------------------------------------------------------------- Function: close_items() Author: Jan Max Meyer Usage: Parameters: Returns: void ~~~ CHANGES & NOTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date: Author: Note: ----------------------------------------------------------------------------- */ function close_items( seed, closure ) { var i, j, k; var cnt = 0, tmp_cnt = 0; var item; for( i = 0; i < seed.length; i++ ) { if( seed[i].dot_offset < productions[seed[i].prod].rhs.length ) { if( symbols[productions[seed[i].prod].rhs[seed[i].dot_offset]].kind == SYM_NONTERM ) { for( j = 0; j < symbols[productions[seed[i].prod].rhs[seed[i].dot_offset]].prods.length; j++ ) { for( k = 0; k < closure.length; k++ ) { if( closure[k].prod == symbols[productions[seed[i].prod].rhs[seed[i].dot_offset]].prods[j] ) break; } if( k == closure.length ) { item = create_item( symbols[productions[seed[i].prod].rhs[seed[i].dot_offset]].prods[j] ); closure.push( item ); cnt++; } tmp_cnt = closure[k].lookahead.length; if( rhs_first( closure[k], productions[seed[i].prod], seed[i].dot_offset+1 ) ) closure[k].lookahead = union( closure[k].lookahead, seed[i].lookahead ); cnt += closure[k].lookahead.length - tmp_cnt; } } } } return cnt; } /* -FUNCTION-------------------------------------------------------------------- Function: lalr1_closure() Author: Jan Max Meyer Usage: Implements the LALR(1) closure algorithm. A short overview: 1. Closing a closure_set of ITEM() objects from a given kernel seed (this includes the kernel seed itself!) 2. Moving all epsilon items to the current state's epsilon set. 3. Moving all symbols with the same symbol right to the dot to a partition set. 4. Check if there is already a state with the same items as there are in the partition. If so, union the look- aheads, else, create a new state and set the partition as kernel seed. 5. If the (probably new state) was not closed yet, perform some table creation: If there is a terminal to the right of the dot, do a shift on the action table, else do a goto on the goto table. Reductions are performed later, when all states are closed. Parameters: s Id of the state that should be closed. Returns: void ~~~ CHANGES & NOTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date: Author: Note: 29.02.2009 Jan Max Meyer There was a bug that rose up with some grammars and caused wrong lookahead computation. ----------------------------------------------------------------------------- */ function lalr1_closure( s ) { var closure = [], nclosure, partition; var item, partition_sym; var i, j, k, l, cnt = 0, old_cnt = 0, tmp_cnt, ns; /* for( i = 0; i < states[s].kernel.length; i++ ) { closure.push( new ITEM() ); closure[i].prod = states[s].kernel[i].prod; closure[i].dot_offset = states[s].kernel[i].dot_offset; closure[i].lookahead = new Array(); for( j = 0; j < states[s].kernel[i].lookahead.length; j++ ) closure[i].lookahead[j] = states[s].kernel[i].lookahead[j]; } */ do { old_cnt = cnt; cnt = close_items( ( ( old_cnt == 0 ) ? states[s].kernel : closure ), closure ); //_print( "closure: cnt = " + cnt + " old_cnt = " + old_cnt + "<br />" ); } while( cnt != old_cnt ); for( i = 0; i < states[s].kernel.length; i++ ) { if( states[s].kernel[i].dot_offset < productions[states[s].kernel[i].prod].rhs.length ) { closure.unshift( new ITEM({ prod: states[s].kernel[i].prod, dot_offset: states[s].kernel[i].dot_offset, lookahead: [] }) ); for( j = 0; j < states[s].kernel[i].lookahead.length; j++ ) closure[0].lookahead[j] = states[s].kernel[i].lookahead[j]; } } /* print_item_set( (exec_mode == EXEC_CONSOLE) ? MODE_GEN_TEXT : MODE_GEN_HTML, "closure in " + s, closure ); print_item_set( (exec_mode == EXEC_CONSOLE) ? MODE_GEN_TEXT : MODE_GEN_HTML, "states[" + s + "].epsilon", states[s].epsilon ); */ for( i = 0; i < closure.length; i++ ) { if( productions[closure[i].prod].rhs.length == 0 ) { for( j = 0; j < states[s].epsilon.length; j++ ) if( states[s].epsilon[j].prod == closure[i].prod && states[s].epsilon[j].dot_offset == closure[i].dot_offset ) break; if( j == states[s].epsilon.length ) states[s].epsilon.push( closure[i] ); closure.splice( i, 1 ); } } while( closure.length > 0 ) { partition = []; nclosure = []; partition_sym = -1; for( i = 0; i < closure.length; i++ ) { if( partition.length == 0 ) partition_sym = productions[closure[i].prod].rhs[closure[i].dot_offset]; if( closure[i].dot_offset < productions[closure[i].prod].rhs.length ) { if( productions[closure[i].prod].rhs[closure[i].dot_offset] == partition_sym ) { closure[i].dot_offset++; partition.push( closure[i] ); } else nclosure.push( closure[i] ); } } if( partition.length > 0 ) { /* beachcoder Feb 23, 2009: Uhh here was a very exciting bug that only came up on special grammar constellations: If we don't sort the partition set by production here, it may happen that states get wrong lookahead, and unexpected conflicts or failing grammars come up. */ partition.sort( sort_partition ); //Now one can check for equality! for( i = 0; i < states.length; i++ ) if( item_set_equal( states[i].kernel, partition ) ) break; if( i == states.length ) { ns = create_state(); //_print( "Generating state " + (states.length - 1) ); ns.kernel = partition; } tmp_cnt = 0; cnt = 0; for( j = 0; j < partition.length; j++ ) { tmp_cnt += states[i].kernel[j].lookahead.length; states[i].kernel[j].lookahead = union( states[i].kernel[j].lookahead, partition[j].lookahead ); cnt += states[i].kernel[j].lookahead.length; } if( tmp_cnt != cnt ) states[i].done = false; //_print( "<br />states[" + s + "].closed = " + states[s].closed ); if( !(states[s].closed) ) { for( j = 0; j < partition.length; j++ ) { //_print( "<br />partition[j].dot_offset-1 = " + // (partition[j].dot_offset-1) + " productions[partition[j].prod].rhs.length = " // + productions[partition[j].prod].rhs.length ); if( partition[j].dot_offset-1 < productions[partition[j].prod].rhs.length ) { //_print( "<br />symbols[productions[partition[j].prod].rhs[partition[j].dot_offset-1]].kind = " + // symbols[productions[partition[j].prod].rhs[partition[j].dot_offset-1]].kind ); if( symbols[productions[partition[j].prod].rhs[partition[j].dot_offset-1]].kind == SYM_TERM ) { states[s].actionrow = add_table_entry( states[s].actionrow, productions[partition[j].prod].rhs[partition[j].dot_offset-1], i ); shifts++; } else { states[s].gotorow = add_table_entry( states[s].gotorow, productions[partition[j].prod].rhs[partition[j].dot_offset-1], i ); gotos++; } } } } } closure = nclosure; } states[s].closed = true; } /* -FUNCTION-------------------------------------------------------------------- Function: do_reductions() Author: Jan Max Meyer Usage: Inserts reduce-cells into the action table. A reduction does always occur for items with the dot to the far right of the production and to items with no production (epsilon items). The reductions are done on the corresponding lookahead symbols. If a shift-reduce conflict appears, the function will always behave in favor of the shift. Reduce-reduce conflicts are reported immediatelly, and need to be solved. Parameters: item_set The item set to work on. s The index of the state where the reductions take effect. Returns: void ~~~ CHANGES & NOTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date: Author: Note: ----------------------------------------------------------------------------- */ function do_reductions( s ) { var n, i, j, ex, act, output_warning, item_set; var reds = []; var max = 0, count; for( n = 0; n < 2; n++ ) { if( !n ) item_set = states[ s ].kernel; else item_set = states[ s ].epsilon; // Do the reductions for( i = 0; i < item_set.length; i++ ) { if( item_set[i].dot_offset == productions[item_set[i].prod].rhs.length ) { for( j = 0; j < item_set[i].lookahead.length; j++ ) { output_warning = true; ex = get_table_entry( states[s].actionrow, item_set[i].lookahead[j] ); act = ex; if( ex == void(0) ) { act = -1 * item_set[i].prod; states[s].actionrow = add_table_entry( states[s].actionrow, item_set[i].lookahead[j], act ); reduces++; } else { var warning = ""; if( ex > 0 ) { //Shift-reduce conflict //Is there any level specified? if( symbols[item_set[i].lookahead[j]].level > 0 || productions[ item_set[i].prod ].level > 0 ) { //Is the level the same? if( symbols[item_set[i].lookahead[j]].level == productions[ item_set[i].prod ].level ) { //In case of left-associativity, reduce if( symbols[item_set[i].lookahead[j]].assoc == ASSOC_LEFT ) { //Reduce act = -1 * item_set[i].prod; } //else, if nonassociativity is set, //remove table entry. else if( symbols[item_set[i].lookahead[j]].assoc == ASSOC_NOASSOC ) { remove_table_entry( states[s].actionrow, item_set[i].lookahead[j] ); _warning( "Removing nonassociative symbol '" + symbols[item_set[i].lookahead[j]].label + "' in state " + s ); output_warning = false; } } else { //If symbol precedence is lower production's //precedence, reduce if( symbols[item_set[i].lookahead[j]].level < productions[ item_set[i].prod ].level ) //Reduce act = -1 * item_set[i].prod; } } warning = "Shift"; } else { //Reduce-reduce conflict act = ( ( act * -1 < item_set[i].prod ) ? act : -1 * item_set[i].prod ); warning = "Reduce"; } warning += "-reduce conflict on symbol '" + symbols[item_set[i].lookahead[j]].label + "' in state " + s; warning += "\n Conflict resolved by " + ( ( act <= 0 ) ? "reducing with production" : "shifting to state" ) + " " + ( ( act <= 0 ) ? act * -1 : act ); if( output_warning ) _warning( warning ); if( act != ex ) update_table_entry( states[s].actionrow, item_set[i].lookahead[j], act ); } //Remember this reduction, if there is any if( act <= 0 ) reds.push( act * -1 ); } } } } /* JMM 16.04.2009 Find most common reduction */ states[ s ].def_act = -1; //Define no default action //Are there any reductions? Then select the best of them! for( i = 0; i < reds.length; i++ ) { for( j = 0, count = 0; j < reds.length; j++ ) if( reds[j] == reds[i] ) count++; if( max < count ) { max = count; states[ s ].def_act = reds[ i ]; } } //Remove all default reduce action reductions, if they exist. if( states[s].def_act >= 0 ) { do { count = states[s].actionrow.length; for( i = 0; i < states[s].actionrow.length; i++ ) if( states[s].actionrow[i][1] == states[s].def_act * -1 ) states[s].actionrow.splice( i, 1 ); } while( count != states[s].actionrow.length ); } } /* -FUNCTION-------------------------------------------------------------------- Function: lalr1_parse_table() Author: Jan Max Meyer Usage: Entry function to perform table generation. If all states of the parsing state machine are constructed, all reduce operations are inserted in the particular positions of the action table. If there is a Shift-reduce conflict, the shift takes the higher precedence. Reduce-reduce conflics are resolved by choosing the first defined production. Parameters: debug Toggle debug trace output; This should only be switched on when JS/CC is executed in a web environ- ment, because HTML-code will be printed. Returns: void ~~~ CHANGES & NOTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date: Author: Note: 16.04.2009 Jan Max Meyer Added the feature of default productions; The most common production will be defined as the default, and all entries referencing this rule are removed.