Created
July 8, 2013 12:04
-
-
Save alucky0707/5948205 to your computer and use it in GitHub Desktop.
PL/0 to JavaScript Compiler
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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