jscc-parser
Version:
A LALR(1) Parser Generator for JavaScript written in JavaScript.
1,754 lines (1,445 loc) • 123 kB
JavaScript
/* -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 + " -> " );
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.