Skip to content

Instantly share code, notes, and snippets.

@alucky0707
Created July 8, 2013 12:04
Show Gist options
  • Save alucky0707/5948205 to your computer and use it in GitHub Desktop.
Save alucky0707/5948205 to your computer and use it in GitHub Desktop.
PL/0 to JavaScript Compiler
/*
PL/0 Compiler for JavaScript v0.1.0
copyright 2013 alucky0707
licensed by http://opensource.org/licenses/MIT
*/
/*
BNF
program = block "." .
block = [ "const" IDENT "=" NUMBER {"," IDENT "=" NUMBER} ";"]
[ "var" IDENT {"," IDENT} ";"]
{ "procedure" IDENT ";" block ";" } statement .
statement = [ "call" IDENT |
"begin" statement {";" statement } "end" |
"if" condition "then" statement |
"while" condition "do" statement |
"!" expression {"," expression} |
IDENT ":=" expression] .
condition = "odd" expression |
expression ("="|"#"|"<"|"<="|">"|">=") expression .
expression = [ "+"|"-"] term { ("+"|"-") term}.
term = factor {("*"|"/") factor}.
factor = IDENT | NUMBER | "(" expression ")".
IDENT = [a-zA-Z]+
NUMBER = [0-9]+
*/
(function() {
'use strict';
function translate(src, config) {
config = config || {};
var
// Config
print = config.print || 'console.log',
indentChar = config.indentChar || ' ',
// Parse State
line = 1,
expect = '',
scope = [{}],
// Constant
rSpace = /^[ \t\r]+/,
rToken = /^(?:[a-zA-Z]+|[0-9]+|[:<>]=|[+*\/#=<>()-]|[,.;!]|\n)/,
fail = '',
reserved = {
CONST: 1,
VAR: 1,
PROCEDURE: 1,
CALL: 1,
IF: 1,
THEN: 1,
WHILE: 1,
DO: 1,
BEGIN: 1,
END: 1,
ODD: 1,
},
// Tokenized Source
tokens = tokenize(src),
// parse start
res = program();
if (tokens.length === 0)
return '(function(){\n\'use strict\';\n' + res + '\n})();';
else unexpected(['EOF']);
// Tokenizer
function tokenize(src) {
var
line = 1,
match,
tokens = [];
src = src.replace(rSpace, '');
do {
if (!(match = src.match(rToken)))
throw new Error('parse error : (line at ' + line + ') : ' +
'unexpected char "' + src.charAt(0) +'"');
if(match[0] === '\n') line++;
tokens.push(match[0]);
src = src.slice(match[0].length).replace(rSpace, '');
} while (src !== '');
return tokens;
}
// Rules
function program() {
var
res = block();
if (check('.')) {
skip();
return res;
} else unexpected();
}
function block() {
var
id, val, ids,
res = '';
if (check('CONST')) {
do {
if ((id = ident()) && check('=') && (val = number())) {
if (id in scope[0]) declared(id);
scope[0][id] = val;
} else unexpected();
} while (check(','))
if (!check(';')) unexpected();
}
if (check('VAR')) {
res += 'var ';
ids = [];
do {
if (id = ident()) {
if (id in scope[0]) declared(id);
scope[0][id] = 'var';
ids.push(id);
} else unexpected();
} while (check(','))
if (!check(';')) unexpected();
res += ids.join(', ') + ';\n';
}
while (check('PROCEDURE')) {
if ((id = ident()) && check(';')) {
if (id in scope[0]) declared(id);
scope[0][id] = 'procedure';
scope.unshift({});
val = block();
if (!check(';')) unexpected();
res += 'var ' + id + ' = function(){\n';
res += indent(val, scope.length - 1) + '\n};\n';
scope.shift();
} else unexpected();
}
val = statement(0);
return res + val;
}
function statement() {
var
vals,
id, cond, val, vals,
res = '';
switch (true) {
case check('CALL'):
if (!(id = ident())) unexpected();
if (has(id) !== 'procedure') typemiss(id, 'procedure', 'value');
res = id + '();';
break;
case check('IF'):
cond = condition();
if (!check('THEN')) unexpected();
val = statement();
res = 'if(' + cond + '){\n' + indent(val, 1) + '\n}';
break;
case check('WHILE'):
cond = condition();
if (!check('DO')) unexpected();
val = statement();
res = 'while(' + cond + '){\n' + indent(val, 1) + '\n}';
break;
case check('BEGIN'):
vals = [];
do {
val = statement();
if(val === '') continue;
vals.push(val);
} while (check(';'))
if (!check('END')) unexpected();
res = vals.join('\n');
break;
case check('!'):
vals = [];
do {
val = expression();
vals.push(val);
} while (check(','))
res = config.print + '(' + vals.join(', ') + ');';
break;
case !!(id = ident()):
if (!check(':=')) unexpected();
if (!has(id)) undeclared(id);
val = expression();
res = id + ' = ' + val + ';';
break;
default:
res = '';
break;
}
return res;
}
function condition() {
var
left, op, right;
if (check('ODD')) {
right = expression();
return '(' + right + ') % 2 === 1';
}
left = expression();
if (!(op = str('=') || str('#') ||
str('<') || str('>') ||
str('<=') || str('>='))) unexpected(['"="', '"#"', '"<"', '">"', '"<="', '">="']);
right = expression()
return left + ' ' + ({'=': '===', '#': '!=='}[op] || op) + ' ' + right;
}
function expression() {
var
op = str('+') || str('-'),
val,
res = '';
do {
val = term();
res += ' ' + op + ' ' + val;
} while (op = str('+') || str('-'))
return res.trim();
}
function term() {
var
f = false,
op = '',
val,
res = '';
do {
val = fact();
if (op === '/') f = true;
res += ' ' + op + ' ' + val;
} while (op = str('*') || str('/'))
res = res.trim();
return f ? '(function (x) { return Math[[\'floor\', \'ceil\'][+(x < 0)]](x);})(' + res + ')' : res;
}
function fact() {
var
id, val;
if (id = ident()) {
val = has(id);
if (val === 'procedure') typemiss(id, 'value', val);
return val === 'var' ? id : val.toString(10);
}
if (val = number()) {
return val;
}
if (check('(')) {
val = expression();
if (!check(')')) unexpected();
return '(' + val + ')';
}
unexpected(['IDENT', 'NUMBER', '"("']);
}
// Comibinators
function skip() {
while (tokens[0] === '\n') {
tokens.shift();
line++;
}
}
function ident() {
expect = 'IDENT';
skip();
return /^[a-zA-Z]+$/.test(tokens[0]) && !reserved[tokens[0]] ? tokens.shift() :
fail;
}
function number() {
expect = 'NUMBER';
skip();
return /^[0-9]+$/.test(tokens[0]) ? new Number(parseInt(tokens.shift(), 10)) :
0;
}
function str(s) {
expect = '"' + s + '"';
skip();
return tokens[0] === s ? tokens.shift() : fail;
}
function check(s) {
return !!str(s);
}
// Utils
function has(id) {
return scope.reduce(function (v, x) {
return v || x[id] || '';
}, '');
}
function indent(s, n) {
var
space = Array(n + 1).join(indentChar);
return s.split('\n').map(function (l) {
return space + l;
}).join('\n');
}
// Errors
function unexpected(expects) {
expects = expects || [expect];
throw new Error('parse error (line at ' + line + ') : ' +
'expected ' + expects.join(' or ') + ', but found "' + (tokens[0] || '') + '"');
}
function declared(id) {
throw new Error('complie error (line at ' + line + ') : "' +
id + '" is already declared');
}
function undeclared(id) {
throw new Error('complie error (line at ' + line + ') : "' +
id + '" is undeclared');
}
function typemiss(id, req, val) {
throw new Error('complie error (line at ' + line + ') : ' +
'require ' + req + ', but "' + id + '" is ' + val);
}
}
if (typeof module === 'object' && 'exports' in module) module.exports.translate = translate;
else window.PL0 = {translate: translate}
})();
// for Node.js
//*
if (process.argv.length < 3) {
console.log('please give me a input file!');
process.exit(0);
}
var
PL0 = module.exports,
src = require('fs').readFileSync(process.argv[2], 'UTF-8');
try {
console.log(PL0.translate(src, {print: 'console.log'}));
} catch(e) {
console.log(e.message);
}
//*/
// for Browser
/*
window.addEventListener('load', function () {
var
mimeType = 'text/pl0',
i, script, src,
scripts = document.getElementsByTagName('script');
for (i = 0; i < scripts.length; i++) {
script = scripts[i];
if (script.getAttribute('type') === mimeType) {
src = script.innerHTML;
try {
src = PL0.translate(src, {print: 'alert'});
setTimeout(new Function(src), 0);
} catch(e) {
alert(e.message);
}
}
}
}, false);
//*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment