lib/compiler.js
"use strict";
const CConsumer = require("./cconsumer");
const Context = require("./context");
const Parser = require("./parser");
const Scope = require("./scope");
const Structs = { contract: true, packet: true, struct: true, union: true };
class Compiler extends CConsumer {
constructor(source) {
super(source);
this.parser = new Parser(this);
}
addErr(cxt, res, msg) {
if(! (cxt instanceof Context)) {
msg = res;
res = cxt;
cxt = null;
}
this.addFormattedMsg(true, res, msg);
if(! cxt) return res;
cxt.addErr(res);
return null;
}
addWarn(res, msg) {
return this.addFormattedMsg(false, res, msg);
}
addFunction(func) {
if(func.global) return this.addErr(func.first, "functions declaration and definition in global scope is not supported yet");
if(func.export) func.export = true;
if(func.pure) {
func.export = true;
func.pure = true;
}
if(func.stackless) func.stackless = true;
if(func.view) {
func.export = true;
func.view = true;
}
this.signFunc(func);
const prev = this.scope.getFunction(func.signature);
if(prev) {
if(! func.defined) return this.addErr(func.first, `new declaration of '${func.extended}' previously declared at line ${prev.first.nr}`);
if(prev.defined) return this.addErr(func.first, `new definition of '${func.extended}' previously defined at line ${prev.first.nr}`);
let err = false;
if(prev.type.type !== func.type.type) err = true;
["export", "pure", "stackless", "view"].map(m => {
if(prev[m]) func[m] = true;
else if(func[m]) err = true;
});
if(err) return this.addErr(func.first, `new declaration of '${func.extended}' previously declared as '${prev.extended}' at line ${prev.first.nr}`);
func.extended = prev.extended;
}
func.scope.name = func.signature;
this.functions.push(func.scope);
this.scope.addFunction(func.name, func);
}
addVariable(variable, res, prev) {
let { type } = variable;
if(! variable.name) {
if(type.contract || type.packet || type.struct || type.union) return;
if(prev.definition && ! prev.unnamed) return;
return this.addWarn(res, "empty variable declaration");
}
if(variable.global) return this.addErr(variable.first, "variables can't be declared in global scope");
if(variable.type.undefined) return this.addErr(res, `not defined type: '${variable.type.type}'`);
if(variable.export) {
if(! variable.contract) return this.addErr(variable.export, "only contract variables can be exported");
if(! variable.storage) return this.addErr(variable.export, "only storage variables can be exported");
variable.export = true;
}
if(variable.memory) variable.memory = true;
if(variable.register) variable.register = true;
if(variable.storage) {
if(! variable.contract) return this.addErr(variable.storage, "only contract variables can be storage");
variable.storage = true;
}
if(variable.dim.length) {
if(type.type === "void") return this.addErr(variable, res, "can't handle arrays of void");
variable.dim.map(d => {
if(variable.storage) {
if(! d.isType) this.addErr(variable, d.first, "only type dimentions allowed for storage arrays");
if(d.pointer) this.addErr(variable, d.first, "pointers are not valid type dimentions for storage arrays");
if(d.struct) this.addErr(variable, d.first, "structs are not valid type dimentions for storage arrays");
} else if(d.isType) this.addErr(variable, d.first, "type dimentions allowed only for storage arrays");
if(! variable.err) type = this.scope.addTypeArray(type, d);
});
variable.type = type;
} else if(type.type === "void") return this.addErr(variable, res, "variables can't be of type void");
if(variable.err) return;
this.scope.addVar(variable.name, variable);
}
addType(type) {
this.scope.addType(type.name, type.type.type);
}
compile() {
let res;
this.res = [{ filename: "opcode", line: "" }];
this.pushScope();
this.functions = [this.scope];
if(process.env.CCC_TEST) global.scope = this.scope;
do res = this.pullGlobal();
while(! res || ! res.eof);
this.res.push({ line: "" });
if(this.errors.length) return;
}
getType(token) {
return this.scope.getType(token);
}
popScope() {
this.scope = this.scope.next;
}
pull() {
var res = super.pull();
while(res.space) res = super.pull();
return res;
}
pullBlock(scope) {
let res;
if(scope) this.scope = scope;
else this.pushScope();
do {
res = this.pull();
if(res.token !== "}") this.pullStatement(new Context(), res);
} while(res.token !== "}" && ! res.eof);
this.popScope();
}
pullContract(contract, res) {
const scope = this.pushScope(true);
contract.addEnd("}");
contract.definition = true;
contract.scope = scope;
do {
if(! res) res = this.pull();
if(contract.isEnd(res)) {
let empty = true;
for(let i in scope.functions) if(scope.functions[i].export) empty = false;
for(let i in scope.variables) if(scope.variables[i].export) empty = false;
if(empty) this.addWarn(res, "empty contract declaration");
this.popScope();
this.scope.addContract(contract);
this.getType("contract " + contract.name).scope = scope;
if(res.token === "}") return null;
if(res.eof) return this.addErr(contract, res, "unexpected end of file when declaration or '}' expected");
return this.addErr(contract, res, `unexpected token when declaration or '}' expected: '${res.token}'`);
}
res = this.pullStatement(new Context(contract).setContract(), res);
} while(true);
}
pullExpression(exp, res) {
let res2;
if(! res) res = this.pull();
if(! exp.first) exp.first = res;
if(exp.isEnd(res)) {
if(res.eof) this.addErr(exp, res, "unexpected end of file when ';' expected");
if(exp.err) return res;
const ret = this.parser.parse(exp, res);
this.scope.addStatement(exp);
return ret;
}
if(Structs[res.token]) {
const what = res.token;
const struct = exp.clone();
res2 = this.pullStruct(what, struct);
const { definition, unnamed } = struct;
exp.tokens.push({ ...res, definition, isType: true, unnamed, type: struct.err || ! this.getType(struct.type) ? {} : this.getType(struct.type) });
} else if(this.getType(res.token)) exp.tokens.push({ ...res, isType: true, type: this.getType(res.token) });
else if(res.number || res.symbol || res.string || res.operator) exp.tokens.push(res);
else this.addErr(exp, res, `unexpected token in expression: '${res.token}'`);
return this.pullExpression(exp, res2);
}
pullGlobal() {
const res = this.pull();
if(res.eof) return res;
return this.pullStatement(new Context().setGlobal(), res);
}
pullStatement(context, res) {
if(! res) res = this.pull();
if(res.token === "typedef") {
context.setTypedef().delEnd("{");
res = null;
}
res = this.pullExpression(context, res);
if(res.token === ";") return null;
if(context.err) return res;
if(res.token === "{") return this.pullBlock(res.scope);
if(res.eof) return this.addErr(context, res, "unexpected end of file when ';' expected");
throw Error("TODO");
}
pullStruct(what, struct, res) {
const addStruct = name => {
this.scope.addStruct(what, struct, true);
struct.definition = true;
struct.type = this.scope.getType(`${what} ${name}`).type;
};
if(! res) res = this.pull();
if(res.eof) return this.addErr(res, "unexpected end of file when identifier or '{' expected");
if(struct.name) {
if(struct.param || res.eof) return res;
if(res.token === "{") {
const type = struct.type ? this.scope.getType(struct.type) : {};
if(type.defined) this.addErr(struct, res, `${what} already defined: '${struct.name}'`);
struct.definition = true;
return what === "contract" ? this.pullContract(struct) : this.pullStructBody(what, struct);
}
return res;
} else if(res.symbol) {
if(this.reserved[res.token]) this.addErr(struct, res, `unexpected reserved token when identifier or '{' expected: '${res.token}'`);
else {
const prev = this.scope.getType(`${what} ${res.token}`);
struct.name = res.token;
if(prev) {
struct.type = prev.type;
return this.pullStruct(what, struct);
} else addStruct(res.token);
}
} else if(res.token === "{") {
struct.unnamed = true;
struct.definition = true;
if(what === "contract") {
if(this.scope.getType("contract default")) this.addErr(struct, res, "redefined type: 'contract default'");
addStruct((struct.name = "default"));
return this.pullContract(struct);
}
return this.pullStructBody(what, struct);
} else return this.addErr(struct, res, `unexpected token when identifier or '{' expected: '${res.token}'`);
return this.pullStruct(what, struct);
}
pullStructBody(what, struct, res) {
struct.addEnd("}");
struct.scope = this.pushScope();
struct.scope[what] = true;
do {
if(! res) res = this.pull();
if(res.eof) return this.addErr(struct, res, "unexpected end of file when type or '}' expected");
if(struct.isEnd(res)) {
this.popScope();
const { attrs, size, type } = this.scope.addStruct(what, struct);
struct.type = type;
if(attrs.length === 0) this.addWarn(res, `empty ${what} declaration`);
if(size > 32 && what === "packet") this.addErr(struct, res, "packet size exceeds 32 bytes length");
if(res.token === "}") return null;
return this.addErr(struct, res, `unexpected token when type or '}' expected: '${res.token}'`);
}
struct.tokens = [];
res = this.pullExpression(new Context(struct).setAttribute(), res);
if(res.token === ";") res = null;
else this.addErr(struct, res, `unexpected token when ';' expected: '${res.token}'`);
} while(true);
}
pushScope(contract) {
return (this.scope = new Scope(this.scope, contract));
}
signFunc(func) {
func.extended = `${func.type.type} ${func.name}`;
func.signature = "F" + func.name.length + func.name;
func.scope.params.map((param, n) => {
func.extended += (n ? ", " : "(") + (param.register ? "register " : "") + param.type.type + " " + param.name;
func.signature += (param.register ? "_" : "") + param.type.short;
});
func.extended += func.scope.params.length ? ")" : "()";
["export", "pure", "stackless", "view"].map(m => (func[m] ? (func.extended += ` ${m}`) : ""));
}
}
Compiler.prototype.reserved = {};
Compiler.prototype.modifiers = require("./modifiers");
"break case class continue contract do else for if map pack packet return sizeof struct switch typedef union unpack while".split(" ").map(r => (Compiler.prototype.reserved[r] = true));
Object.keys(Compiler.prototype.modifiers).map(r => (Compiler.prototype.reserved[r] = true));
module.exports = Compiler;