Skip to content

Instantly share code, notes, and snippets.

@tcortega
Last active December 1, 2023 22:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tcortega/b77b544ed5865affbe0c3ce7fb899804 to your computer and use it in GitHub Desktop.
Save tcortega/b77b544ed5865affbe0c3ce7fb899804 to your computer and use it in GitHub Desktop.
%{
const symbols = {};
let currentScope = 0;
let nextScope = 1;
let lastType = null;
function enterScope() {
currentScope = nextScope;
nextScope++;
}
function exitScope() {
for (const key in symbols) {
if (symbols[key].scope === currentScope) {
delete symbols[key];
}
}
currentScope--;
}
function addSymbol(id, type) {
const scopedId = id + "@" + currentScope;
if (symbols[scopedId]) {
throw new Error(`Symbol ${id} already declared in this scope`);
}
symbols[scopedId] = { type, scope: currentScope };
}
function checkSymbol(id) {
let scope = currentScope;
while (scope >= 0) {
const scopedId = id + "@" + scope;
if (symbols[scopedId]) {
return symbols[scopedId];
}
scope--;
}
throw new Error(`Symbol ${id} not declared`);
}
function checkTypeCompatibility(type1, type2) {
if (type1 === type2) {
return;
}
if ((type1 === 'double' && type2 === 'float') || (type1 === 'float' && type2 === 'double')) {
return;
}
throw new Error(`Type mismatch: ${type1} vs ${type2}`);
}
%}
%lex
%%
\s+ /* ignore */
\"([^"\\]|\\.)*\" return 'STRING_LIT'
"int" return 'INT'
"void" return 'VOID'
"#include" return 'INCLUDE'
"#define" return 'DEFINE'
"<".*">" return 'PREPROCESSOR'
"double" return 'DOUBLE'
"float" return 'FLOAT'
"'"[^']"'" return 'CHAR_LIT'
"char" return 'CHAR'
"if" return 'IF'
"switch" return 'SWITCH'
"case" return 'CASE'
"break" return 'BREAK'
"default" return 'DEFAULT'
"else" return 'ELSE'
"while" return 'WHILE'
"for" return 'FOR'
"Variável" return 'VAR'
"return" return 'RETURN'
"do while" return 'DO_WHILE'
"do" return 'DO'
"#" return '#'
"define" return 'DEFINE'
"//"[^\n]* /* Ignore single-line comments */
[a-zA-Z][a-zA-Z0-9_]* return 'IDF'
[0-9]*\.[0-9]+([eE][+-][0-9]+)? return 'F_LIT'
[0-9]+ return 'INT_LIT'
"++" return 'INCREMENT'
"--" return 'DECREMENT'
"+=" return 'PLUS_ASSIGN'
"-=" return 'MINUS_ASSIGN'
"*" return '*'
"+" return '+'
"-" return '-'
"/" return '/'
"%" return 'MOD'
"," return ','
";" return ';'
":" return ':'
"." return '.'
"'" return 'QUOTE'
'"' return 'DQUOTE'
"(" return '('
")" return ')'
"[" return '['
"]" return ']'
"{" return '{'
"}" return '}'
"<=" return 'LE'
">=" return 'GE'
"==" return 'EQ'
"!=" return 'NE'
"<" return '<'
">" return '>'
"=" return '='
"&&" return 'AND'
"||" return 'OR'
"!" return 'NOT'
<<EOF>> return 'EOF'
/lex
%left 'AND' 'OR'
%left '<' '>' 'EQ' 'NE' 'LE' 'GE'
%left '+' '-'
%left '*' '/'
%nonassoc 'UMINUS' // To handle unary minus
%start program
%%
program
: statement_list EOF { enterScope(); }
;
statement_list
: statement
| statement_list statement
;
statement
: var_declaration
| function_declaration
| assignment ';'
| function_call_statement
| if_statement
| switch_statement
| loop
| preprocessing_statement
| block
| return_statement
;
preprocessing_statement
: INCLUDE '<' IDF '.' IDF '>' ';'
| DEFINE IDF INT_LIT
| PREPROCESSOR
| INCLUDE PREPROCESSOR
;
block
: '{' '}' // Allow empty block
| '{' statement_list '}'
;
var_declaration
: type var_list ';'
;
var_list
: var_init
| var_list ',' var_init
;
var_init
: IDF { addSymbol($1, lastType); $$ = { id: $1, type: lastType }; }
| IDF '=' expression { addSymbol($1, lastType); checkSymbol($1); checkTypeCompatibility(symbols[$1 + "@" + currentScope].type, $3.type); $$ = { id: $1, type: symbols[$1 + "@" + currentScope].type }; }
| IDF '[' INT_LIT ']' { addSymbol($1, lastType + "[]"); $$ = { id: $1, type: lastType + "[]" }; }
| IDF '[' ']' '=' '{' array_init_list '}' { addSymbol($1, lastType + "[]"); $$ = { id: $1, type: lastType + "[]" }; } // Handle array initialization
| IDF '[' IDF ']' { checkSymbol($3); checkTypeCompatibility(symbols[$3 + "@" + currentScope].type, 'int'); addSymbol($1, lastType + "[]"); $$ = { id: $1, type: lastType + "[]" }; }
;
id_list
: IDF
| id_list ',' IDF
;
array_init_list
: expression
| array_init_list ',' expression
;
type
: INT { lastType = 'int'; }
| DOUBLE { lastType = 'double'; }
| FLOAT { lastType = 'float'; }
| CHAR { lastType = 'char'; }
| VOID { lastType = 'void'; }
;
assignment
: IDF '=' expression { checkSymbol($1); checkTypeCompatibility(symbols[$1 + "@" + currentScope].type, $3.type); }
| IDF 'INCREMENT' { checkSymbol($1); }
| IDF 'DECREMENT' { checkSymbol($1); }
| IDF 'PLUS_ASSIGN' expression { checkSymbol($1); }
| IDF 'MINUS_ASSIGN' expression { checkSymbol($1); }
;
expression
: IDF { const symbolIDF = checkSymbol($1); $$ = { type: symbolIDF.type }; }
| INT_LIT { $$ = { type: 'int' }; }
| F_LIT { $$ = { type: 'float' }; }
| CHAR_LIT { $$ = { type: 'char' }; }
| STRING_LIT { $$ = { type: 'string' }; }
| IDF '[' expression ']' { const symbolExpr = checkSymbol($1); if (!symbolExpr.type.endsWith('[]')) throw new Error(`Type of ${$1} is not an array`); $$ = { type: symbolExpr.type.slice(0, -2) }; }
| expression 'MOD' expression { checkTypeCompatibility($1.type, $3.type); $$ = { type: $1.type }; }
| expression '+' expression { checkTypeCompatibility($1.type, $3.type); $$ = { type: $1.type }; }
| expression '-' expression { checkTypeCompatibility($1.type, $3.type); $$ = { type: $1.type }; }
| expression '*' expression { checkTypeCompatibility($1.type, $3.type); $$ = { type: $1.type }; }
| expression '/' expression { checkTypeCompatibility($1.type, $3.type); $$ = { type: $1.type }; }
| expression 'EQ' expression { checkTypeCompatibility($1.type, $3.type); $$ = { type: 'int' }; }
| expression 'NE' expression { checkTypeCompatibility($1.type, $3.type); $$ = { type: 'int' }; }
| expression '<' expression { checkTypeCompatibility($1.type, $3.type); $$ = { type: 'int' }; }
| expression '>' expression { checkTypeCompatibility($1.type, $3.type); $$ = { type: 'int' }; }
| expression 'LE' expression { checkTypeCompatibility($1.type, $3.type); $$ = { type: 'int' }; }
| expression 'GE' expression { checkTypeCompatibility($1.type, $3.type); $$ = { type: 'int' }; }
| expression 'AND' expression { checkTypeCompatibility($1.type, $3.type); $$ = { type: 'int' }; }
| expression 'OR' expression { checkTypeCompatibility($1.type, $3.type); $$ = { type: 'int' }; }
| '(' expression ')' { $$ = $2; }
| '-' expression %prec UMINUS { checkTypeCompatibility($2.type, 'int'); $$ = { type: 'int' }; }
| '(' type ')' expression { checkTypeCompatibility($2, $4.type); $$ = { type: $2 }; }
| IDF 'INCREMENT' { checkSymbol($1); $$ = { type: 'int' }; }
| IDF 'DECREMENT' { checkSymbol($1); $$ = { type: 'int' }; }
| 'NOT' expression { checkTypeCompatibility($2.type, 'int'); $$ = { type: 'int' }; }
| function_call { $$ = $1; }
;
if_statement
: IF '(' expression ')' '{' statement_list '}' { checkTypeCompatibility($3.type, 'int'); }
| IF '(' expression ')' statement { checkTypeCompatibility($3.type, 'int'); }
| IF '(' expression ')' '{' statement_list '}' ELSE '{' statement_list '}' { checkTypeCompatibility($3.type, 'int'); }
| IF '(' expression ')' statement ELSE statement { checkTypeCompatibility($3.type, 'int'); }
| IF '(' expression ')' '{' statement_list '}' ELSE if_statement { checkTypeCompatibility($3.type, 'int'); }
;
switch_statement
: SWITCH '(' expression ')' '{' case_statements '}' { checkTypeCompatibility($3.type, 'int'); }
| SWITCH '(' expression ')' statement { checkTypeCompatibility($3.type, 'int'); }
;
case_statements
: CASE expression ':' { checkTypeCompatibility($2.type, 'int'); }
| CASE expression ':' statement_list { checkTypeCompatibility($2.type, 'int'); }
| CASE expression ':' BREAK ';' { checkTypeCompatibility($2.type, 'int'); }
| DEFAULT ':' { }
| DEFAULT ':' statement_list
| DEFAULT ':' BREAK ';' // Allow 'default: break;' without additional code
| case_statements CASE expression ':' { checkTypeCompatibility($3.type, 'int'); }
| case_statements CASE expression ':' statement_list { checkTypeCompatibility($3.type, 'int'); }
| case_statements CASE expression ':' BREAK ';' { checkTypeCompatibility($3.type, 'int'); }
| case_statements DEFAULT ':' { }
| case_statements DEFAULT ':' statement_list
| case_statements DEFAULT ':' BREAK ';' // Allow subsequent 'default: break;' without additional code
;
loop
: WHILE '(' expression ')' statement { checkTypeCompatibility($3.type, 'int'); }
| FOR '(' for_init ';' expression ';' assignment ')' statement { checkTypeCompatibility($5.type, 'int'); }
| DO '{' statement_list '}' WHILE '(' expression ')' ';' { checkTypeCompatibility($7.type, 'int'); }
;
for_init
: type IDF '=' expression { addSymbol($2, $1); $$ = { id: $2, type: $1 }; } // Handle variable declaration and initialization
| assignment // Handle assignment (for existing variables)
;
function_call_statement
: function_call ';'
;
function_call
: IDF '(' ')' { const symbolFuncCall = checkSymbol($1); if (!symbolFuncCall.type.startsWith('function')) throw new Error(`Function ${$1} not defined`); $$ = { type: symbolFuncCall.type.split(':')[1] }; }
| IDF '(' argument_list ')' { const symbolFuncCall2 = checkSymbol($1); if (!symbolFuncCall2.type.startsWith('function')) throw new Error(`Function ${$1} not defined`); $$ = { type: symbolFuncCall2.type.split(':')[1] }; }
;
argument_list
: expression { $$ = [$1]; }
| argument_list ',' expression { $1.push($3); $$ = $1; }
;
function_declaration
: function_definition
| function_prototype
;
function_definition
: type IDF '(' parameters ')' '{' statement_list '}' {
addSymbol($2, `function:${$1}`);
enterScope();
$4.forEach(param => addSymbol(param.id, param.type)); // $4 is the parameters, which is now always an array
$$ = { id: $2, type: `function:${$1}`, parameters: $4 };
exitScope();
}
;
function_prototype
: type IDF '(' parameters ')' ';' {
addSymbol($2, `function:${$1}`);
$$ = { id: $2, type: `function:${$1}` };
}
;
parameter_declaration
: type IDF { addSymbol($2, $1); $$ = { id: $2, type: $1 }; }
| type IDF '[' ']' { addSymbol($2, `${$1}[]`); $$ = { id: $2, type: `${$1}[]` }; }
;
parameters
: /* empty */ { $$ = []; }
| parameter_declaration { $$ = [$1]; }
| parameters ',' parameter_declaration { $1.push($3); $$ = $1; }
;
parameter_list
: parameter { $$ = [$1]; }
| parameter_list ',' parameter { $1.push($3); $$ = $1; }
;
parameter
: type IDF { $$ = { id: $2, type: $1 }; }
| type IDF '[' ']' { $$ = { id: $2, type: `${$1}[]` }; }
;
return_statement
: RETURN ';' { $$ = 'return'; }
| RETURN expression ';' { $$ = `return ${$2}`; }
;
%%
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment