///
import {CheckContext, CheckMode, initialize, resolve} from "./analyzer/type-checker";
import {Node, NODE_FLAG_LIBRARY, NodeKind} from "./core/node";
import {ByteArray} from "../utils/bytearray";
import {Log, Source} from "../utils/log";
import {Preprocessor} from "./preprocessor/preprocessor";
import {Scope} from "./core/scope";
import {tokenize} from "./scanner/scanner";
import {parse} from "./parser/parser";
import {treeShaking} from "./optimizer/shaking";
import {wasmEmit} from "../backends/webassembly/webassembly";
import {Library} from "../library/library";
import {preparse} from "./parser/preparser";
import {CompileTarget} from "./compile-target";
import {assert} from "../utils/assert";
import {Terminal} from "../utils/terminal";
import {WasmBinary} from "../backends/webassembly/wasm/wasm-binary";
import {BinaryImporter} from "../importer/binary-importer";
/**
* Author: Nidin Vinayakan
*/
export class Compiler {
log: Log;
global: Node;
firstSource: Source;
lastSource: Source;
preprocessor: Preprocessor;
target: CompileTarget;
context: CheckContext;
librarySource: Source;
runtimeSource: string;
wrapperSource: string;
outputName: string;
outputWASM: ByteArray;
outputWAST: string;
outputJS: string;
outputCPP: string;
outputH: string;
static mallocRequired: boolean = false;
static debug:boolean = false;
initialize(target: CompileTarget, outputName: string): void {
assert(this.log == null);
this.log = new Log();
this.preprocessor = new Preprocessor();
BinaryImporter.reset();
this.target = target;
this.outputName = outputName;
this.librarySource = this.addInput("", Library.get(target));
this.librarySource.isLibrary = true;
this.runtimeSource = Library.getRuntime(target);
this.wrapperSource = Library.getWrapper(target);
this.createGlobals();
if (target == CompileTarget.CPP) {
this.preprocessor.define("CPP", true);
}
else if (target == CompileTarget.JAVASCRIPT) {
this.preprocessor.define("JS", true);
}
else if (target == CompileTarget.WEBASSEMBLY) {
this.preprocessor.define("WASM", true);
}
}
createGlobals(): void {
let context = new CheckContext();
context.log = this.log;
context.target = this.target;
context.pointerByteSize = 4; // Assume 32-bit code generation for now
let global = new Node();
global.kind = NodeKind.GLOBAL;
let scope = new Scope();
global.scope = scope;
// Hard-coded types
context.anyType = scope.defineNativeType(context.log, "any");
context.errorType = scope.defineNativeType(context.log, "");
context.nullType = scope.defineNativeType(context.log, "null");
context.undefinedType = scope.defineNativeType(context.log, "undefined");
context.voidType = scope.defineNativeType(context.log, "void");
this.context = context;
this.global = global;
}
addInput(name: string, contents: string): Source {
let source = new Source();
source.name = name;
source.contents = contents;
if (this.firstSource == null) this.firstSource = source;
else {
source.prev = this.lastSource;
this.lastSource.next = source;
}
this.lastSource = source;
return source;
}
addInputBefore(name: string, contents: string, nextSource: Source): Source {
let source = new Source();
source.name = name;
source.contents = contents;
nextSource.prev.next = source;
source.prev = nextSource.prev;
nextSource.prev = source;
source.next = nextSource;
return source;
}
finish(): boolean {
Terminal.time("pre-parsing");
let source = this.firstSource;
while (source != null) {
if (!preparse(source, this, this.log)) {
return false;
}
source = source.next;
}
Terminal.timeEnd("pre-parsing");
Terminal.time("scanning");
source = this.firstSource;
while (source != null) {
source.firstToken = tokenize(source, this.log);
source = source.next;
}
Terminal.timeEnd("scanning");
Terminal.time("pre-processing");
source = this.firstSource;
while (source != null) {
this.preprocessor.run(source, this.log);
source = source.next;
}
Terminal.timeEnd("pre-processing");
Terminal.time("parsing");
source = this.firstSource;
while (source != null) {
if (source.firstToken != null) {
source.file = parse(source.firstToken, this.log);
}
source = source.next;
}
Terminal.timeEnd("parsing");
Terminal.time("type-checking");
let global = this.global;
let context = this.context;
let fullResolve = true;
source = this.firstSource;
while (source != null) {
let file = source.file;
if (file != null) {
if (source.isLibrary) {
file.flags |= NODE_FLAG_LIBRARY;
initialize(context, file, global.scope, CheckMode.INITIALIZE);
resolve(context, file, global.scope);
} else {
initialize(context, file, global.scope, CheckMode.NORMAL);
}
while (file.firstChild != null) {
let child = file.firstChild;
child.remove();
global.appendChild(child);
}
}
// Stop if the library code has errors because it's highly likely that everything is broken
if (source.isLibrary && this.log.hasErrors()) {
fullResolve = false;
break;
}
source = source.next;
}
if (fullResolve) {
resolve(context, global, global.scope);
}
Terminal.timeEnd("type-checking");
if (this.log.hasErrors()) {
return false;
}
Terminal.time("optimizing");
treeShaking(global);
Terminal.timeEnd("optimizing");
Terminal.time("emitting");
// if (this.target == CompileTarget.C) {
// cEmit(this);
// }
// else if (this.target == CompileTarget.JAVASCRIPT) {
// jsEmit(this);
// } else
if (this.target == CompileTarget.WEBASSEMBLY) {
wasmEmit(this);
}
Terminal.timeEnd("emitting");
Terminal.write("Done!");
return true;
}
}
export function replaceFileExtension(path: string, extension: string): string {
let dot = path.lastIndexOf(".");
let forward = path.lastIndexOf("/");
let backward = path.lastIndexOf("\\");
// Make sure that there's a non-empty file name that the dot is a part of
if (dot > 0 && dot > forward && dot > backward) {
path = path.slice(0, dot);
}
return path + extension;
}