var grammar = require("./parser.js");
var nearley = require("nearley");
var fs = require("fs");
var llvm = require("./llvm.js");
const { exec } = require('child_process');
function error(str) {
console.log("error: " + str);
process.exit(1);
}
var p = new nearley.Parser(grammar.ParserRules, grammar.ParserStart);
var src = fs.readFileSync("test.rcs", "utf8");
p.feed(fs.readFileSync("intrinsics.rcs", "utf8"))
p.feed(fs.readFileSync("win32.rcs", "utf8"));
p.feed(src);
if(p.results == 0) {
console.log("no parse found");
process.exit(1);
}
else if(p.results > 1) {
console.log("ambiguity found");
process.exit(1);
}
var tree = p.results[0];
var funs = [];
var dataTypes = {
void:{name:"void",size:0},bool:{name:"bool",size:1},
i8: {name:"i8", size:1},i16:{name:"i16",size:2},
i32:{name:"i32",size:4},i64:{name:"i64",size:8},
u8: {name:"u8", size:1},u16:{name:"u16",size:2},
u32:{name:"u32",size:4},u64:{name:"u64",size:8}
};
function getDataType(str) {
if(dataTypes[str] == null) {
console.log("error: Data type '" + str + "' doesn't exist");
process.exit(-1);
}
return dataTypes[str];
}
function getFun(name, paramTypes) {
for(var j = 0; j < funs.length; ++j) {
var fun = funs[j];
if(fun.name == name && fun.params.length == paramTypes.length) {
var pe = true;
for(var i = 0; i < fun.params.length; ++i)
pe = pe && (fun.params[i].dataType == paramTypes[i]);
if(pe)
return fun;
}
}
error("can't find function '"+ name + "'");
}
function preAnnotate() {
tree.funs.forEach(function(fun) {
fun.params = fun.params || [];
fun.params.forEach(function(param) {
param.dataType = getDataType(param.dataTypeStr);
delete param.dataTypeStr;
});
fun.dataType = getDataType(fun.dataTypeStr);
delete fun.dataTypeStr;
funs.push(fun);
});
}
function getVar(scope, name) {
while(scope) {
if(scope.vars[name])
return scope.vars[name];
scope = scope.parent;
}
error("can't find variable '"+name+"'");
}
function annotate(node) {
switch(node.type) {
case "assign":
annotate(node.expr);
node.var = getVar(node.scope, node.name);
node.dataType = node.expr.dataType;
if(node.dataType != getVar(node.scope, node.name).dataType)
error("trying to assign wrong data type");
break;
case "funCall":
node.params.forEach(function(param){
annotate(param);
});
node.funCall = getFun(node.name, node.params.map(function(param) {
return param.dataType;
}));
node.dataType = node.funCall.dataType;
break;
case "litInt":
node.dataType = dataTypes.i32;
break;
case "return":
if(node.expr == null) {
if(node.fun.dataType.name != "void")
error("can't void return in function with return type");
}
else {
annotate(node.expr);
node.dataType = node.expr.dataType;
if(node.dataType != node.fun.dataType)
error("wrong datatype in return");
}
break;
case "stmtExpr":
annotate(node.expr);
break;
case "varDef":
if(node.dataTypeStr == null && node.expr == null)
error("vardef of "+node.name+" has no datatype");
if(node.expr)
annotate(node.expr);
if(node.dataTypeStr == null)
node.dataType = node.expr.dataType;
else
node.dataType = getDataType(node.dataTypeStr);
if(node.dataType == null)
error("vardef has no datatype");
break;
case "varUse":
node.var = getVar(node.scope, node.name);
node.dataType = node.var.dataType;
break;
case "while":
annotate(node.expr);
annotateBlock(node.block);
break;
default:
error("not yet annotating '"+node.type+"'")
}
}
function annotateBlock(block) {
block.statements.forEach(function(stmt) {
annotate(stmt);
});
}
function annotateFunctions() {
tree.funs.forEach(function(fun) {
if(!fun.isStub)
annotateBlock(fun.block);
});
}
function initScope(node, scope, fun) {
node.scope = scope;
node.fun = fun;
if(node.type == "varDef") {
if(scope.vars[node.name])
error("variable '"+node.name+"'already exists");
scope.vars[node.name] = node;
}
else {
if(node.params)
node.params.forEach(function(param){initScope(param, scope)});
if(node.expr)
initScope(node.expr, scope, fun);
if(node.block)
initBlockScope(node.block, scope, fun);
else if(node.ifBlock) {
initBlockScope(node.ifBlock, scope, fun);
if(node.elseBlock)
initBlockScope(node.elseBlock, scope, fun);
}
}
}
function initBlockScope(block, parent, fun) {
block.scope = {vars: {}, parent: parent};
block.statements.forEach(function(statement) {
initScope(statement, block.scope, fun);
});
}
function initFunctionScopes() {
tree.funs.forEach(function(fun) {
if(fun.isStub)
return;
initBlockScope(fun.block, null, fun);
fun.params = fun.params || [];
fun.params.forEach(function(param){
if(fun.block.scope.vars[param.name])
error("variable '"+param.name+"'already exists");
fun.block.scope.vars[param.name] = param;
param.isParam = true;
});
});
}
////////////////////////////////////////
initFunctionScopes();
preAnnotate();
annotateFunctions();
llvm.initIntrinsics(dataTypes);
var out = fs.createWriteStream("tmp.ll");
llvm.outFunctions(out, funs);
llvm.outFunctions(process.stdout, funs);
out.end();
//process.exit(0);
exec('opt -O3 tmp.ll -S -o tmp.s && llc tmp.s -o tmp.o -filetype=obj && lld-link tmp.o "C:\\Program Files (x86)\\Windows Kits\\10\\Lib\\10.0.14393.0\\ucrt\\x64\\ucrt.lib" /entry:main__i32 /subsystem:console && test2.bat', (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
console.log(stdout);
});
/* todo:
- change temporaries to be stack allocated variables...
let llvm optimiser handle that, and just write a single generic way of
doing things; always copy before passing to function and delete after
- copy params into temporary stack allocated variables
- modulo operator
- unsigned intrinsics
- destructors... when to call them?
- whenever assigning to variable, first destruct what's in it
- temporaries... when?
- end of current statement, or...
- as soon as it's not needed anymore
- difference between variables, llvm-temporaries and radicalc-temporaries?
- copy before returning?
- strings?
*/