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