196 строки
7.4 KiB
TypeScript
196 строки
7.4 KiB
TypeScript
///<reference path='refs.ts'/>
|
|
|
|
// The main driver for C++ compilation: for each program, loads the libraries it
|
|
// depends on, compiles said libraries, compiles the main program, and stitches
|
|
// the various part together.
|
|
|
|
module TDev {
|
|
import J = AST.Json
|
|
|
|
export module Embedded {
|
|
|
|
import H = Helpers
|
|
|
|
interface StringMap<T> {
|
|
[index: string]: T;
|
|
}
|
|
|
|
export function parseScript(text: string): Promise { // of AST.App
|
|
return AST.loadScriptAsync((id: string) => {
|
|
if (id == "")
|
|
return Promise.as(text);
|
|
else
|
|
return World.getAnyScriptAsync(id);
|
|
}, "").then((resp: AST.LoadScriptResult) => {
|
|
// Otherwise, eventually, this will result in our script being
|
|
// saved in the TouchDevelop format...
|
|
var s = Script;
|
|
Script = null;
|
|
// The function writes its result in a global
|
|
return Promise.as(s);
|
|
});
|
|
}
|
|
|
|
export function makeOutMbedErrorMsg(json: any) {
|
|
var errorMsg = "unknown error";
|
|
// This JSON format is *very* unstructured...
|
|
if (json.mbedresponse) {
|
|
if (json.messages) {
|
|
var messages = json.messages.filter(m =>
|
|
m.severity == "error" || m.type == "Error"
|
|
);
|
|
errorMsg = messages.map(m => m.message + "\n" + m.text).join("\n");
|
|
} else if (json.mbedresponse.result) {
|
|
errorMsg = json.mbedresponse.result.exception;
|
|
}
|
|
}
|
|
return errorMsg;
|
|
}
|
|
|
|
interface EmitterOutput {
|
|
prototypes: string;
|
|
code: string;
|
|
tPrototypes: string;
|
|
tCode: string;
|
|
prelude: string;
|
|
};
|
|
|
|
// Assuming all library references have been resolved, compile either the
|
|
// main app or one of said libraries.
|
|
function compile1(globalNameMap: H.GlobalNameMap, libs: J.JApp[], resolveMap: StringMap<string>, a: J.JApp): EmitterOutput
|
|
{
|
|
try {
|
|
var libRef: J.JCall = H.mkLibraryRef(a.name);
|
|
var libName = a.isLibrary ? a.name : null;
|
|
|
|
var env = H.emptyEnv(globalNameMap, libName);
|
|
lift(env, a);
|
|
var e = new Emitter(libRef, libs, resolveMap);
|
|
e.visit(env, a);
|
|
return e;
|
|
} catch (e) {
|
|
console.error("Compilation error", e);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
function buildResolveMap(libs: J.JLibrary[]): { [index: string]: string }[] {
|
|
var idToAbsoluteName = {};
|
|
libs.map((l: J.JLibrary) => {
|
|
idToAbsoluteName[l.id] = l.name;
|
|
});
|
|
return libs.map((l: J.JLibrary) => {
|
|
var map: { [i:string]: string } = {};
|
|
l.resolveClauses.map((r: J.JResolveClause) => {
|
|
map[r.name] = idToAbsoluteName[<any> r.defaultLibId];
|
|
});
|
|
return map;
|
|
});
|
|
}
|
|
|
|
// When assigning names to function definitions, we must make sure they
|
|
// don't clash with any of the existing symbols. This is not an issue for
|
|
// local variables.
|
|
var reservedNames = [
|
|
// Namespaces from microbit-touchdevelop/MicroBitTouchDevelop.h
|
|
"user_types", "globals", "create", "collection", "touch_develop", "ref",
|
|
"ds1307", "string", "action", "math", "number", "boolean", "bits", "internal_main",
|
|
|
|
// Types
|
|
"Number", "Boolean", "String", "ManagedString", "Collection",
|
|
"Collection_of", "Ref", "TdError", "DalAdapter", "MicroBitPin",
|
|
];
|
|
|
|
// Compile an entire program, including its libraries.
|
|
export function compile(a: J.JApp): Promise { // of string
|
|
// We need the library text for all the libraries referenced by this
|
|
// script.
|
|
var libraries = a.decls.filter((d: J.JDecl) => d.nodeType == "library");
|
|
var resolveMap = buildResolveMap(<J.JLibrary[]> libraries);
|
|
var mainResolveMap: StringMap<string> = {};
|
|
var textPromises = libraries.map((j: J.JDecl, i: number) => {
|
|
var pubId = (<J.JLibrary> j).libIdentifier;
|
|
return AST.loadScriptAsync(World.getAnyScriptAsync, pubId).then((resp: AST.LoadScriptResult) => {
|
|
var s = Script;
|
|
Script = resp.prevScript;
|
|
var jApp = J.dump(s);
|
|
mainResolveMap[libraries[i].name] = jApp.name;
|
|
return Promise.as(jApp);
|
|
});
|
|
});
|
|
textPromises.push(Promise.as(a));
|
|
return Promise.join(textPromises).then((everything: J.JApp[]) => {
|
|
// Consider the following situation. The main script binds [libA] as
|
|
// [libA']. The main script binds [libB] as [libB], but [libB] binds
|
|
// [libA] as [libA'']. Right now, the [resolveMap] for [libB] maps
|
|
// [libA] to [libA']. We need to use [mainResolveMap] (which maps
|
|
// [libA'] to [libA'']) to fix [resolveMap] so that it maps [libA''] to
|
|
// [libA].
|
|
Object.keys(resolveMap).forEach((libName: string) => {
|
|
var libMap = resolveMap[libName];
|
|
Object.keys(libMap).forEach((x: string) => {
|
|
libMap[x] = mainResolveMap[libMap[x]];
|
|
});
|
|
});
|
|
// And now the main map for the main script.
|
|
resolveMap.push(mainResolveMap);
|
|
// TouchDevelop allows any name; thus, both "Thing$" and "Thing@" sanitize
|
|
// to "Thing_". We need to disambuigate them. Because there may be
|
|
// references across library to these names, we need to agree on a final
|
|
// name before translating the various libraries.
|
|
var globalNameMap: H.GlobalNameMap = {
|
|
libraries: {},
|
|
program: null,
|
|
};
|
|
everything.forEach((a: J.JApp) => {
|
|
var tdToCpp: StringMap<string> = {};
|
|
var cppToTd: StringMap<boolean> = {};
|
|
// Avoid conflicts by pre-marking reserved names.
|
|
everything.map((a: J.JApp) => H.mangle(a.name)).concat(reservedNames)
|
|
.forEach((x: string) => cppToTd[x] = true);
|
|
a.decls.forEach((d: J.JDecl) => {
|
|
// This is over-conservative, since technically speaking, types and
|
|
// globals are each in their own namespace. Here, we
|
|
// over-approximate and treat things as if everyone were in the same
|
|
// namespace.
|
|
var n = H.freshName(cppToTd, d.name);
|
|
cppToTd[n] = true;
|
|
tdToCpp[d.name] = n;
|
|
});
|
|
// TODO we should be doing the same thing for libraries, in case the
|
|
// user has two libraries that desugar to the same name... not going
|
|
// to happen?
|
|
// This is the "real" name of the library (which may be different from
|
|
// the "known here as"... name).
|
|
if (a.isLibrary)
|
|
globalNameMap.libraries[a.name] = tdToCpp;
|
|
else
|
|
globalNameMap.program = tdToCpp;
|
|
});
|
|
|
|
var compiled = everything.map((a: J.JApp, i: number) => compile1(globalNameMap, everything, resolveMap[i], a));
|
|
return Promise.as(
|
|
compiled.map(x => x.prelude)
|
|
.concat(["namespace touch_develop {"])
|
|
.concat(compiled.map(x => x.tPrototypes))
|
|
.concat(compiled.map(x => x.tCode))
|
|
.concat(compiled.map(x => x.prototypes))
|
|
.concat(compiled.map(x => x.code))
|
|
.concat(["}"])
|
|
.filter(x => x != "")
|
|
.join("\n") + "\n" +
|
|
(a.isLibrary
|
|
? "\nvoid app_main() {\n"+
|
|
" uBit.display.scroll(\"Error: trying to run a library\");\n"+
|
|
"}\n"
|
|
: "\nvoid app_main() {\n"+
|
|
" touch_develop::main();\n"+
|
|
"}\n") + "\n\n// vim: sw=2 ts=2"
|
|
);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// vim: set ts=2 sw=2 sts=2:
|