1 // Copyright 2014 Wouter van Oortmerssen. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 // lobster.cpp : Defines the entry point for the console application.
16 //
17 #include "lobster/stdafx.h"
18
19 #include "lobster/lex.h"
20 #include "lobster/idents.h"
21 #include "lobster/node.h"
22
23 #include "lobster/compiler.h"
24
25 #include "lobster/parser.h"
26 #include "lobster/typecheck.h"
27 #include "lobster/optimizer.h"
28 #include "lobster/codegen.h"
29
30 namespace lobster {
31
32 const Type g_type_int(V_INT);
33 const Type g_type_float(V_FLOAT);
34 const Type g_type_string(V_STRING);
35 const Type g_type_any(V_ANY);
36 const Type g_type_vector_int(V_VECTOR, &g_type_int);
37 const Type g_type_vector_float(V_VECTOR, &g_type_float);
38 const Type g_type_function_null(V_FUNCTION);
39 const Type g_type_function_cocl(V_YIELD);
40 const Type g_type_coroutine(V_COROUTINE);
41 const Type g_type_resource(V_RESOURCE);
42 const Type g_type_typeid(V_TYPEID);
43 const Type g_type_void(V_VOID);
44 const Type g_type_function_void(V_VOID, &g_type_function_null);
45 const Type g_type_undefined(V_UNDEFINED);
46
47 TypeRef type_int = &g_type_int;
48 TypeRef type_float = &g_type_float;
49 TypeRef type_string = &g_type_string;
50 TypeRef type_any = &g_type_any;
51 TypeRef type_vector_int = &g_type_vector_int;
52 TypeRef type_vector_float = &g_type_vector_float;
53 TypeRef type_function_null = &g_type_function_null;
54 TypeRef type_function_cocl = &g_type_function_cocl;
55 TypeRef type_coroutine = &g_type_coroutine;
56 TypeRef type_resource = &g_type_resource;
57 TypeRef type_typeid = &g_type_typeid;
58 TypeRef type_void = &g_type_void;
59 TypeRef type_function_void = &g_type_function_void;
60 TypeRef type_undefined = &g_type_undefined;
61
62 const Type g_type_vector_any(V_VECTOR, &g_type_any);
63 const Type g_type_vector_string(V_VECTOR, &g_type_string);
64 const Type g_type_vector_vector_int(V_VECTOR, &g_type_vector_int);
65 const Type g_type_vector_vector_float(V_VECTOR, &g_type_vector_float);
66 const Type g_type_vector_vector_vector_float(V_VECTOR, &g_type_vector_vector_float);
67
WrapKnown(TypeRef elem,ValueType with)68 TypeRef WrapKnown(TypeRef elem, ValueType with) {
69 if (with == V_VECTOR) {
70 switch (elem->t) {
71 case V_ANY: return &g_type_vector_any;
72 case V_INT: return elem->e ? nullptr : type_vector_int;
73 case V_FLOAT: return type_vector_float;
74 case V_STRING: return &g_type_vector_string;
75 case V_VECTOR: switch (elem->sub->t) {
76 case V_INT: return &g_type_vector_vector_int;
77 case V_FLOAT: return &g_type_vector_vector_float;
78 case V_VECTOR: switch (elem->sub->sub->t) {
79 case V_FLOAT: return &g_type_vector_vector_vector_float;
80 default: return nullptr;
81 }
82 default: return nullptr;
83 }
84 default: return nullptr;
85 }
86 } else if (with == V_NIL) {
87 switch (elem->t) {
88 case V_ANY: { static const Type t(V_NIL, &g_type_any); return &t; }
89 case V_INT: { static const Type t(V_NIL, &g_type_int); return elem->e ? nullptr : &t; }
90 case V_FLOAT: { static const Type t(V_NIL, &g_type_float); return &t; }
91 case V_STRING: { static const Type t(V_NIL, &g_type_string); return &t; }
92 case V_FUNCTION: { static const Type t(V_NIL, &g_type_function_null); return &t; }
93 case V_RESOURCE: { static const Type t(V_NIL, &g_type_resource); return &t; }
94 case V_COROUTINE: { static const Type t(V_NIL, &g_type_coroutine); return &t; }
95 case V_VECTOR: switch (elem->sub->t) {
96 case V_INT: { static const Type t(V_NIL, &g_type_vector_int); return &t; }
97 case V_FLOAT: { static const Type t(V_NIL, &g_type_vector_float); return &t; }
98 case V_STRING: { static const Type t(V_NIL, &g_type_vector_string); return &t; }
99 default: return nullptr;
100 }
101 default: return nullptr;
102 }
103 } else {
104 return nullptr;
105 }
106 }
107
IsCompressed(string_view filename)108 bool IsCompressed(string_view filename) {
109 auto dot = filename.find_last_of('.');
110 if (dot == string_view::npos) return false;
111 auto ext = filename.substr(dot);
112 return ext == ".lbc" || ext == ".lobster" || ext == ".materials";
113 }
114
115 static const uchar *magic = (uchar *)"LPAK";
116 static const size_t magic_size = 4;
117 static const size_t header_size = magic_size + sizeof(int64_t) * 3;
118 static const char *bcname = "bytecode.lbc";
119
LE(T x)120 template <typename T> int64_t LE(T x) { return flatbuffers::EndianScalar((int64_t)x); };
121
BuildPakFile(string & pakfile,string & bytecode,set<string> & files)122 void BuildPakFile(string &pakfile, string &bytecode, set<string> &files) {
123 // All offsets in 64bit, just in-case we ever want pakfiles > 4GB :)
124 // Since we're building this in memory, they can only be created by a 64bit build.
125 vector<int64_t> filestarts;
126 vector<int64_t> namestarts;
127 vector<int64_t> uncompressed;
128 vector<string> filenames;
129 auto add_file = [&](string_view buf, string_view filename) {
130 filestarts.push_back(LE(pakfile.size()));
131 filenames.push_back(string(filename));
132 LOG_INFO("adding to pakfile: ", filename);
133 if (IsCompressed(filename)) {
134 string out;
135 WEntropyCoder<true>((uchar *)buf.data(), buf.length(), buf.length(), out);
136 pakfile += out;
137 uncompressed.push_back(buf.length());
138 } else {
139 pakfile += buf;
140 uncompressed.push_back(-1);
141 }
142 };
143 // Start with a magic id, just for the hell of it.
144 pakfile.insert(pakfile.end(), magic, magic + magic_size);
145 // Bytecode always first entry.
146 add_file(bytecode, bcname);
147 // Followed by all files.
148 files.insert("data/shaders/default.materials"); // If it hadn't already been added.
149 string buf;
150 for (auto &filename : files) {
151 auto l = LoadFile(filename, &buf);
152 if (l >= 0) {
153 add_file(buf, filename);
154 } else {
155 vector<pair<string, int64_t>> dir;
156 if (!ScanDir(filename, dir))
157 THROW_OR_ABORT("cannot load file/dir for pakfile: " + filename);
158 for (auto &[name, size] : dir) {
159 auto fn = filename + name;
160 if (size >= 0 && LoadFile(fn, &buf) >= 0)
161 add_file(buf, fn);
162 }
163 }
164 }
165 // Now we can write the directory, first the names:
166 auto dirstart = LE(pakfile.size());
167 for (auto &filename : filenames) {
168 namestarts.push_back(LE(pakfile.size()));
169 pakfile.insert(pakfile.end(), filename.c_str(), filename.c_str() + filename.length() + 1);
170 }
171 // Then the starting offsets and other data:
172 pakfile.insert(pakfile.end(), (uchar *)uncompressed.data(),
173 (uchar *)(uncompressed.data() + uncompressed.size()));
174 pakfile.insert(pakfile.end(), (uchar *)filestarts.data(),
175 (uchar *)(filestarts.data() + filestarts.size()));
176 pakfile.insert(pakfile.end(), (uchar *)namestarts.data(),
177 (uchar *)(namestarts.data() + namestarts.size()));
178 auto num = LE(filestarts.size());
179 // Finally the "header" (or do we call this a "tailer" ? ;)
180 auto header_start = pakfile.size();
181 auto version = LE(1);
182 pakfile.insert(pakfile.end(), (uchar *)&num, (uchar *)(&num + 1));
183 pakfile.insert(pakfile.end(), (uchar *)&dirstart, (uchar *)(&dirstart + 1));
184 pakfile.insert(pakfile.end(), (uchar *)&version, (uchar *)(&version + 1));
185 pakfile.insert(pakfile.end(), magic, magic + magic_size);
186 assert(pakfile.size() - header_start == header_size);
187 (void)header_start;
188 }
189
190 // This just loads the directory part of a pakfile such that subsequent LoadFile calls know how
191 // to load from it.
LoadPakDir(const char * lpak)192 bool LoadPakDir(const char *lpak) {
193 // This supports reading from a pakfile > 4GB even on a 32bit system! (as long as individual
194 // files in it are <= 4GB).
195 auto plen = LoadFile(lpak, nullptr, 0, 0);
196 if (plen < 0) return false;
197 string header;
198 if (LoadFile(lpak, &header, plen - (int64_t)header_size, header_size) < 0 ||
199 memcmp(header.c_str() + header_size - magic_size, magic, magic_size)) return false;
200 auto read_unaligned64 = [](const void *p) {
201 int64_t r;
202 memcpy(&r, p, sizeof(int64_t));
203 return LE(r);
204 };
205 auto num = (size_t)read_unaligned64(header.c_str());
206 auto dirstart = read_unaligned64((int64_t *)header.c_str() + 1);
207 auto version = read_unaligned64((int64_t *)header.c_str() + 2);
208 if (version > 1) return false;
209 if (dirstart > plen) return false;
210 string dir;
211 if (LoadFile(lpak, &dir, dirstart, plen - dirstart - (int64_t)header_size) < 0)
212 return false;
213 auto namestarts = (int64_t *)(dir.c_str() + dir.length()) - num;
214 auto filestarts = namestarts - num;
215 auto uncompressed = filestarts - num;
216 for (size_t i = 0; i < num; i++) {
217 auto name = string_view(dir.c_str() + (read_unaligned64(namestarts + i) - dirstart));
218 auto off = read_unaligned64(filestarts + i);
219 auto end = i < num + 1 ? read_unaligned64(filestarts + i + 1) : dirstart;
220 auto len = end - off;
221 LOG_INFO("pakfile dir: ", name, " : ", len);
222 AddPakFileEntry(lpak, name, off, len, read_unaligned64(uncompressed + i));
223 }
224 return true;
225 }
226
LoadByteCode(string & bytecode)227 bool LoadByteCode(string &bytecode) {
228 if (LoadFile(bcname, &bytecode) < 0) return false;
229 flatbuffers::Verifier verifier((const uchar *)bytecode.c_str(), bytecode.length());
230 auto ok = bytecode::VerifyBytecodeFileBuffer(verifier);
231 assert(ok);
232 return ok;
233 }
234
RegisterBuiltin(NativeRegistry & nfr,const char * name,void (* regfun)(NativeRegistry &))235 void RegisterBuiltin(NativeRegistry &nfr, const char *name,
236 void (* regfun)(NativeRegistry &)) {
237 LOG_DEBUG("subsystem: ", name);
238 nfr.NativeSubSystemStart(name);
239 regfun(nfr);
240 }
241
DumpBuiltins(NativeRegistry & nfr,bool justnames,const SymbolTable & st)242 void DumpBuiltins(NativeRegistry &nfr, bool justnames, const SymbolTable &st) {
243 string s;
244 if (justnames) {
245 for (auto nf : nfr.nfuns) { s += nf->name; s += "|"; }
246 WriteFile("builtin_functions_names.txt", false, s);
247 return;
248 }
249 s = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n"
250 "<html>\n<head>\n<title>lobster builtin function reference</title>\n"
251 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
252 "<style type=\"text/css\">"
253 "table.a, tr.a, td.a {font-size: 10pt;border: 1pt solid #DDDDDD;"
254 " border-Collapse: collapse; max-width:1200px}</style>\n"
255 "</head>\n<body><center><table border=0><tr><td>\n<p>"
256 "lobster builtin functions:"
257 "(file auto generated by compiler, do not modify)</p>\n\n";
258 int cursubsystem = -1;
259 bool tablestarted = false;
260 for (auto nf : nfr.nfuns) {
261 if (nf->subsystemid != cursubsystem) {
262 if (tablestarted) s += "</table>\n";
263 tablestarted = false;
264 s += cat("<h3>", nfr.subsystems[nf->subsystemid], "</h3>\n");
265 cursubsystem = nf->subsystemid;
266 }
267 if (!tablestarted) {
268 s += "<table class=\"a\" border=1 cellspacing=0 cellpadding=4>\n";
269 tablestarted = true;
270 }
271 s += cat("<tr class=\"a\" valign=top><td class=\"a\"><tt><b>", nf->name, "</b>(");
272 int last_non_nil = -1;
273 for (auto [i, a] : enumerate(nf->args.v)) {
274 if (a.type->t != V_NIL) last_non_nil = (int)i;
275 }
276 for (auto [i, a] : enumerate(nf->args.v)) {
277 auto argname = nf->args.GetName(i);
278 if (i) s += ", ";
279 s += argname;
280 s += "<font color=\"#666666\">";
281 if (a.type->t != V_ANY) {
282 s += ":";
283 s += a.flags & NF_BOOL
284 ? "bool"
285 : TypeName(a.type->ElementIfNil(), a.fixed_len, &st);
286 }
287 s += "</font>";
288 if (a.type->t == V_NIL && (int)i > last_non_nil)
289 s += a.type->sub->Numeric() ? " = 0" : " = nil";
290 }
291 s += ")";
292 if (nf->retvals.v.size()) {
293 s += " -> ";
294 for (auto [i, a] : enumerate(nf->retvals.v)) {
295 s += "<font color=\"#666666\">";
296 s += TypeName(a.type, a.fixed_len, &st);
297 s += "</font>";
298 if (i < nf->retvals.v.size() - 1) s += ", ";
299 }
300 }
301 s += cat("</tt></td><td class=\"a\">", nf->help, "</td></tr>\n");
302 }
303 s += "</table>\n</td></tr></table></center></body>\n</html>\n";
304 WriteFile("builtin_functions_reference.html", false, s);
305 }
306
Compile(NativeRegistry & nfr,string_view fn,string_view stringsource,string & bytecode,string * parsedump,string * pakfile,bool dump_builtins,bool dump_names,bool return_value,int runtime_checks)307 void Compile(NativeRegistry &nfr, string_view fn, string_view stringsource, string &bytecode,
308 string *parsedump, string *pakfile, bool dump_builtins, bool dump_names, bool return_value,
309 int runtime_checks) {
310 SymbolTable st;
311 Parser parser(nfr, fn, st, stringsource);
312 parser.Parse();
313 TypeChecker tc(parser, st, return_value);
314 // Optimizer is not optional, must always run at least one pass, since TypeChecker and CodeGen
315 // rely on it culling const if-thens and other things.
316 Optimizer opt(parser, st, tc);
317 if (parsedump) *parsedump = parser.DumpAll(true);
318 CodeGen cg(parser, st, return_value, runtime_checks);
319 st.Serialize(cg.code, cg.code_attr, cg.type_table, cg.vint_typeoffsets, cg.vfloat_typeoffsets,
320 cg.lineinfo, cg.sids, cg.stringtable, cg.speclogvars, bytecode, cg.vtables);
321 if (pakfile) BuildPakFile(*pakfile, bytecode, parser.pakfiles);
322 if (dump_builtins) DumpBuiltins(nfr, false, st);
323 if (dump_names) DumpBuiltins(nfr, true, st);
324 }
325
CompileRun(VM & parent_vm,Value & source,bool stringiscode,const vector<string> & args)326 Value CompileRun(VM &parent_vm, Value &source, bool stringiscode, const vector<string> &args) {
327 string_view fn = stringiscode ? "string" : source.sval()->strv(); // fixme: datadir + sanitize?
328 #ifdef USE_EXCEPTION_HANDLING
329 try
330 #endif
331 {
332 auto vmargs = VMArgs {
333 parent_vm.nfr, fn, {}, nullptr, nullptr, 0, args
334 };
335 Compile(parent_vm.nfr, fn, stringiscode ? source.sval()->strv() : string_view(),
336 vmargs.bytecode_buffer, nullptr, nullptr, false, false, true, RUNTIME_ASSERT);
337 #ifdef VM_COMPILED_CODE_MODE
338 // FIXME: Sadly since we modify how the VM operates under compiled code, we can't run in
339 // interpreted mode anymore.
340 THROW_OR_ABORT(string("cannot execute bytecode in compiled mode"));
341 #endif
342 VM vm(std::move(vmargs));
343 vm.EvalProgram();
344 auto ret = vm.evalret;
345 parent_vm.Push(Value(parent_vm.NewString(ret)));
346 return Value();
347 }
348 #ifdef USE_EXCEPTION_HANDLING
349 catch (string &s) {
350 parent_vm.Push(Value(parent_vm.NewString("nil")));
351 return Value(parent_vm.NewString(s));
352 }
353 #endif
354 }
355
AddCompiler(NativeRegistry & nfr)356 void AddCompiler(NativeRegistry &nfr) { // it knows how to call itself!
357
358 nfr("compile_run_code", "code,args", "SS]", "SS?",
359 "compiles and runs lobster source, sandboxed from the current program (in its own VM)."
360 " the argument is a string of code. returns the return value of the program as a string,"
361 " with an error string as second return value, or nil if none. using parse_data(),"
362 " two program can communicate more complex data structures even if they don't have the same"
363 " version of struct definitions.",
364 [](VM &vm, Value &filename, Value &args) {
365 return CompileRun(vm, filename, true, ValueToVectorOfStrings(args));
366 });
367
368 nfr("compile_run_file", "filename,args", "SS]", "SS?",
369 "same as compile_run_code(), only now you pass a filename.",
370 [](VM &vm, Value &filename, Value &args) {
371 return CompileRun(vm, filename, false, ValueToVectorOfStrings( args));
372 });
373
374 }
375
RegisterCoreLanguageBuiltins(NativeRegistry & nfr)376 void RegisterCoreLanguageBuiltins(NativeRegistry &nfr) {
377 extern void AddBuiltins(NativeRegistry &nfr); RegisterBuiltin(nfr, "builtin", AddBuiltins);
378 extern void AddCompiler(NativeRegistry &nfr); RegisterBuiltin(nfr, "compiler", AddCompiler);
379 extern void AddFile(NativeRegistry &nfr); RegisterBuiltin(nfr, "file", AddFile);
380 extern void AddReader(NativeRegistry &nfr); RegisterBuiltin(nfr, "parsedata", AddReader);
381 }
382
CompiledInit(int argc,char * argv[],const void * entry_point,const void * bytecodefb,size_t static_size,const lobster::block_t * vtables,FileLoader loader,NativeRegistry & nfr)383 VMArgs CompiledInit(int argc, char *argv[], const void *entry_point, const void *bytecodefb,
384 size_t static_size, const lobster::block_t *vtables, FileLoader loader,
385 NativeRegistry &nfr) {
386 min_output_level = OUTPUT_INFO;
387 InitPlatform("../../", "", false, loader); // FIXME: path.
388 auto vmargs = VMArgs {
389 nfr, StripDirPart(argv[0]), {}, entry_point, bytecodefb, static_size, {},
390 vtables, TraceMode::OFF
391 };
392 for (int arg = 1; arg < argc; arg++) { vmargs.program_args.push_back(argv[arg]); }
393 return vmargs;
394 }
395
ConsoleRunCompiledCodeMain(int argc,char * argv[],const void * entry_point,const void * bytecodefb,size_t static_size,const lobster::block_t * vtables)396 extern "C" int ConsoleRunCompiledCodeMain(int argc, char *argv[], const void *entry_point,
397 const void *bytecodefb, size_t static_size,
398 const lobster::block_t *vtables) {
399 #ifdef USE_EXCEPTION_HANDLING
400 try
401 #endif
402 {
403 NativeRegistry nfr;
404 RegisterCoreLanguageBuiltins(nfr);
405 lobster::VM vm(CompiledInit(argc, argv, entry_point, bytecodefb, static_size, vtables,
406 DefaultLoadFile, nfr));
407 vm.EvalProgram();
408 }
409 #ifdef USE_EXCEPTION_HANDLING
410 catch (string &s) {
411 LOG_ERROR(s);
412 return 1;
413 }
414 #endif
415 return 0;
416 }
417
~SubFunction()418 SubFunction::~SubFunction() { delete body; }
419
~Field()420 Field::~Field() { delete defaultval; }
421
Field(const Field & o)422 Field::Field(const Field &o)
423 : type(o.type), id(o.id), genericref(o.genericref),
424 defaultval(o.defaultval ? o.defaultval->Clone() : nullptr) {}
425
426 }
427