/* * Copyright 2015 WebAssembly Community Group participants * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef wasm_simple_ast_h #define wasm_simple_ast_h #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mixed_arena.h" #include "parser.h" #include "snprintf.h" #include "support/safe_integer.h" #define err(str) fprintf(stderr, str "\n"); #define errv(str, ...) fprintf(stderr, str "\n", __VA_ARGS__); #define printErr err namespace cashew { struct Value; struct Ref; void dump(const char* str, Ref node, bool pretty = false); // Reference to a value, plus some operators for convenience struct Ref { Value* inst; Ref(Value* v = nullptr) : inst(v) {} Value* get() { return inst; } Value& operator*() { return *inst; } Value* operator->() { return inst; } Ref& operator[](unsigned x); Ref& operator[](IString x); // special conveniences bool operator==(const char* str); // comparison to string, which is by value bool operator!=(const char* str); bool operator==(const IString& str); bool operator!=(const IString& str); // prevent Ref == number, which is potentially ambiguous; use ->getNumber() == // number bool operator==(double d) { abort(); return false; } bool operator==(Ref other); bool operator!(); // check if null, in effect }; // Arena allocation, free it all on process exit // A mixed arena for global allocation only, so members do not // receive an allocator, they all use the global one anyhow class GlobalMixedArena : public MixedArena { public: template T* alloc() { auto* ret = static_cast(allocSpace(sizeof(T), alignof(T))); new (ret) T(); return ret; } }; extern GlobalMixedArena arena; class ArrayStorage : public ArenaVectorBase { public: void allocate(size_t size) { allocatedElements = size; data = static_cast( arena.allocSpace(sizeof(Ref) * allocatedElements, alignof(Ref))); } }; struct Assign; struct AssignName; // Main value type struct Value { enum Type { String = 0, Number = 1, Array = 2, Null = 3, Bool = 4, Object = 5, Assign_ = 6, // ref = target AssignName_ = 7 }; Type type = Null; typedef std::unordered_map ObjectStorage; // MSVC does not allow unrestricted unions: // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2544.pdf #ifdef _MSC_VER IString str; #endif union { // TODO: optimize #ifndef _MSC_VER IString str; #endif double num; ArrayStorage* arr; bool boo; ObjectStorage* obj; Ref ref; }; // constructors all copy their input Value() {} explicit Value(const char* s) { setString(s); } explicit Value(double n) { setNumber(n); } explicit Value(ArrayStorage& a) { setArray(); *arr = a; } // no bool constructor - would endanger the double one (int might convert the // wrong way) ~Value() { free(); } void free() { if (type == Array) { arr->clear(); } else if (type == Object) { delete obj; } type = Null; num = 0; } Value& setString(const char* s) { free(); type = String; str.set(s); return *this; } Value& setString(const IString& s) { free(); type = String; str.set(s); return *this; } Value& setNumber(double n) { free(); type = Number; num = n; return *this; } Value& setArray(ArrayStorage& a) { free(); type = Array; arr = arena.alloc(); *arr = a; return *this; } Value& setArray(size_t size_hint = 0) { free(); type = Array; arr = arena.alloc(); arr->reserve(size_hint); return *this; } Value& setNull() { free(); type = Null; return *this; } // Bool in the name, as otherwise might overload over int Value& setBool(bool b) { free(); type = Bool; boo = b; return *this; } Value& setObject() { free(); type = Object; obj = new ObjectStorage(); return *this; } Value& setAssign(Ref target, Ref value); Value& setAssignName(IString target, Ref value); bool isString() { return type == String; } bool isNumber() { return type == Number; } bool isArray() { return type == Array; } bool isNull() { return type == Null; } bool isBool() { return type == Bool; } bool isObject() { return type == Object; } bool isAssign() { return type == Assign_; } bool isAssignName() { return type == AssignName_; } // avoid overloading == as it might overload over int bool isBool(bool b) { return type == Bool && b == boo; } // convenience function to check if something is an array and // also has a certain string as the first element. This is a // very common operation as the first element defines the node // type for most ast nodes bool isArray(IString name) { return isArray() && (*this)[0] == name; } const char* getCString() { assert(isString()); return str.str; } IString& getIString() { assert(isString()); return str; } double& getNumber() { assert(isNumber()); return num; } ArrayStorage& getArray() { assert(isArray()); return *arr; } bool& getBool() { assert(isBool()); return boo; } Assign* asAssign(); AssignName* asAssignName(); int32_t getInteger() { // convenience function to get a known integer assert(wasm::isInteger(getNumber())); int32_t ret = getNumber(); assert(double(ret) == getNumber()); // no loss in conversion return ret; } Value& operator=(const Value& other) { free(); switch (other.type) { case String: setString(other.str); break; case Number: setNumber(other.num); break; case Array: setArray(*other.arr); break; case Null: setNull(); break; case Bool: setBool(other.boo); break; default: abort(); // TODO } return *this; } bool operator==(const Value& other) { if (type != other.type) { return false; } switch (other.type) { case String: return str == other.str; case Number: return num == other.num; case Array: return this == &other; // if you want a deep compare, use deepCompare case Null: break; case Bool: return boo == other.boo; case Object: return this == &other; // if you want a deep compare, use deepCompare default: abort(); } return true; } char* parse(char* curr) { /* space, tab, linefeed/newline, or return */ #define is_json_space(x) (x == 32 || x == 9 || x == 10 || x == 13) #define skip() \ { \ while (*curr && is_json_space(*curr)) \ curr++; \ } skip(); if (*curr == '"') { // String curr++; char* close = strchr(curr, '"'); assert(close); *close = 0; // end this string, and reuse it straight from the input setString(curr); curr = close + 1; } else if (*curr == '[') { // Array curr++; skip(); setArray(); while (*curr != ']') { Ref temp = arena.alloc(); arr->push_back(temp); curr = temp->parse(curr); skip(); if (*curr == ']') { break; } assert(*curr == ','); curr++; skip(); } curr++; } else if (*curr == 'n') { // Null assert(strncmp(curr, "null", 4) == 0); setNull(); curr += 4; } else if (*curr == 't') { // Bool true assert(strncmp(curr, "true", 4) == 0); setBool(true); curr += 4; } else if (*curr == 'f') { // Bool false assert(strncmp(curr, "false", 5) == 0); setBool(false); curr += 5; } else if (*curr == '{') { // Object curr++; skip(); setObject(); while (*curr != '}') { assert(*curr == '"'); curr++; char* close = strchr(curr, '"'); assert(close); *close = 0; // end this string, and reuse it straight from the input IString key(curr); curr = close + 1; skip(); assert(*curr == ':'); curr++; skip(); Ref value = arena.alloc(); curr = value->parse(curr); (*obj)[key] = value; skip(); if (*curr == '}') { break; } assert(*curr == ','); curr++; skip(); } curr++; } else { // Number char* after; setNumber(strtod(curr, &after)); curr = after; } return curr; } void stringify(std::ostream& os, bool pretty = false); // String operations // Number operations // Array operations size_t size() { assert(isArray()); return arr->size(); } bool empty() { return size() == 0; } void setSize(size_t size) { assert(isArray()); auto old = arr->size(); if (old != size) { arr->resize(size); } if (old < size) { for (auto i = old; i < size; i++) { (*arr)[i] = arena.alloc(); } } } Ref& operator[](unsigned x) { assert(isArray()); return (*arr)[x]; } Value& push_back(Ref r) { assert(isArray()); arr->push_back(r); return *this; } Ref pop_back() { assert(isArray()); Ref ret = arr->back(); arr->pop_back(); return ret; } Ref back() { assert(isArray()); if (arr->size() == 0) { return nullptr; } return arr->back(); } void splice(int x, int num) { assert(isArray()); arr->erase(arr->begin() + x, arr->begin() + x + num); } int indexOf(Ref other) { assert(isArray()); for (size_t i = 0; i < arr->size(); i++) { if (other == (*arr)[i]) { return i; } } return -1; } Ref map(std::function func) { assert(isArray()); Ref ret = arena.alloc(); ret->setArray(); for (size_t i = 0; i < arr->size(); i++) { ret->push_back(func((*arr)[i])); } return ret; } Ref filter(std::function func) { assert(isArray()); Ref ret = arena.alloc(); ret->setArray(); for (size_t i = 0; i < arr->size(); i++) { Ref curr = (*arr)[i]; if (func(curr)) { ret->push_back(curr); } } return ret; } /* void forEach(std::function func) { for (size_t i = 0; i < arr->size(); i++) { func((*arr)[i]); } } */ // Null operations // Bool operations // Object operations Ref& operator[](IString x) { assert(isObject()); return (*obj)[x]; } bool has(IString x) { assert(isObject()); return obj->count(x) > 0; } }; struct Assign : public Value { Ref value_; Assign(Ref targetInit, Ref valueInit) { type = Assign_; target() = targetInit; value() = valueInit; } Assign() : Assign(nullptr, nullptr) {} Ref& target() { return ref; } Ref& value() { return value_; } }; struct AssignName : public Value { IString target_; AssignName(IString targetInit, Ref valueInit) { type = AssignName_; target() = targetInit; value() = valueInit; } AssignName() : AssignName(IString(), nullptr) {} IString& target() { return target_; } Ref& value() { return ref; } }; // JS printing support struct JSPrinter { bool pretty, finalize; char* buffer = nullptr; size_t size = 0; size_t used = 0; int indent = 0; bool possibleSpace = false; // add a space to separate identifiers Ref ast; JSPrinter(bool pretty_, bool finalize_, Ref ast_) : pretty(pretty_), finalize(finalize_), ast(ast_) {} ~JSPrinter() { free(buffer); } void printAst() { print(ast); ensure(1); buffer[used] = 0; } // Utils void ensure(int safety = 100) { if (size >= used + safety) { return; } size = std::max((size_t)1024, size * 2) + safety; if (!buffer) { buffer = (char*)malloc(size); if (!buffer) { errv("Out of memory allocating %zd bytes for output buffer!", size); abort(); } } else { char* buf = (char*)realloc(buffer, size); if (!buf) { free(buffer); errv("Out of memory allocating %zd bytes for output buffer!", size); abort(); } buffer = buf; } } void emit(char c) { maybeSpace(c); if (!pretty && c == '}' && buffer[used - 1] == ';') { used--; // optimize ;} into }, the ; is not separating anything } ensure(1); buffer[used++] = c; } void emit(const char* s) { maybeSpace(*s); int len = strlen(s); ensure(len + 1); strncpy(buffer + used, s, len + 1); used += len; } void newline() { if (!pretty) { return; } emit('\n'); for (int i = 0; i < indent; i++) { emit(' '); } } void space() { if (pretty) { emit(' '); } } void safeSpace() { if (pretty) { emit(' '); } else { possibleSpace = true; } } void maybeSpace(char s) { if (possibleSpace) { possibleSpace = false; if (isIdentPart(s)) { emit(' '); } } } bool isNothing(Ref node) { return node->isArray() && node[0] == TOPLEVEL && node[1]->size() == 0; } bool isDefun(Ref node) { return node->isArray() && node[0] == DEFUN; } bool endsInBlock(Ref node) { if (node->isArray() && node[0] == BLOCK) { return true; } // Check for a label on a block if (node->isArray() && node[0] == LABEL && endsInBlock(node[2])) { return true; } // Check for an if if (node->isArray() && node[0] == IF && endsInBlock(ifHasElse(node) ? node[3] : node[2])) { return true; } return false; } bool isIf(Ref node) { return node->isArray() && node[0] == IF; } void print(Ref node) { ensure(); if (node->isString()) { printName(node); return; } if (node->isNumber()) { printNum(node); return; } if (node->isAssignName()) { printAssignName(node); return; } if (node->isAssign()) { printAssign(node); return; } IString type = node[0]->getIString(); switch (type.str[0]) { case 'a': { if (type == ARRAY) { printArray(node); } else { abort(); } break; } case 'b': { if (type == BINARY) { printBinary(node); } else if (type == BLOCK) { printBlock(node); } else if (type == BREAK) { printBreak(node); } else { abort(); } break; } case 'c': { if (type == CALL) { printCall(node); } else if (type == CONDITIONAL) { printConditional(node); } else if (type == CONTINUE) { printContinue(node); } else { abort(); } break; } case 'd': { if (type == DEFUN) { printDefun(node); } else if (type == DO) { printDo(node); } else if (type == DOT) { printDot(node); } else { abort(); } break; } case 'i': { if (type == IF) { printIf(node); } else { abort(); } break; } case 'l': { if (type == LABEL) { printLabel(node); } else { abort(); } break; } case 'n': { if (type == NEW) { printNew(node); } else { abort(); } break; } case 'o': { if (type == OBJECT) { printObject(node); } break; } case 'r': { if (type == RETURN) { printReturn(node); } else { abort(); } break; } case 's': { if (type == SUB) { printSub(node); } else if (type == SEQ) { printSeq(node); } else if (type == SWITCH) { printSwitch(node); } else if (type == STRING) { printString(node); } else { abort(); } break; } case 't': { if (type == TOPLEVEL) { printToplevel(node); } else if (type == TRY) { printTry(node); } else { abort(); } break; } case 'u': { if (type == UNARY_PREFIX) { printUnaryPrefix(node); } else { abort(); } break; } case 'v': { if (type == VAR) { printVar(node); } else { abort(); } break; } case 'w': { if (type == WHILE) { printWhile(node); } else { abort(); } break; } default: { errv("cannot yet print %s\n", type.str); abort(); } } } // print a node, and if nothing is emitted, emit something instead void print(Ref node, const char* otherwise) { auto last = used; print(node); if (used == last) { emit(otherwise); } } void printStats(Ref stats) { bool first = true; for (size_t i = 0; i < stats->size(); i++) { Ref curr = stats[i]; if (!isNothing(curr)) { if (first) { first = false; } else { newline(); } print(curr); if (!isDefun(curr) && !endsInBlock(curr) && !isIf(curr)) { emit(';'); } } } } void printToplevel(Ref node) { if (node[1]->size() > 0) { printStats(node[1]); } } void printBlock(Ref node) { if (node->size() == 1 || node[1]->size() == 0) { emit("{}"); return; } emit('{'); indent++; newline(); printStats(node[1]); indent--; newline(); emit('}'); } void printDefun(Ref node) { emit("function "); emit(node[1]->getCString()); emit('('); Ref args = node[2]; for (size_t i = 0; i < args->size(); i++) { if (i > 0) { (pretty ? emit(", ") : emit(',')); } emit(args[i]->getCString()); } emit(')'); space(); if (node->size() == 3 || node[3]->size() == 0) { emit("{}"); return; } emit('{'); indent++; newline(); printStats(node[3]); indent--; newline(); emit('}'); newline(); } void printAssign(Ref node) { auto* assign = node->asAssign(); printChild(assign->target(), node, -1); space(); emit('='); space(); printChild(assign->value(), node, 1); } void printAssignName(Ref node) { auto* assign = node->asAssignName(); emit(assign->target().c_str()); space(); emit('='); space(); printChild(assign->value(), node, 1); } void printName(Ref node) { emit(node->getCString()); } static char* numToString(double d, bool finalize = true) { // If this number is NaN or infinite then things are a bit tricky. In JS we // want to eventually use `NaN` and/or `Infinity`, but neither of those // identifiers are valid in asm.js. Instead we have to explicitly import // `NaN` and `Infinity` from the global environment, and those names are // bound locally in an asm function as `nan` and `infinity`. // // TODO: the JS names of `NaN` and `Infinity` should be used once literal // asm.js code isn't generated any more if (std::isnan(d)) { if (std::signbit(d)) { return (char*)"-nan"; } else { return (char*)"nan"; } } else if (!std::isfinite(d)) { if (std::signbit(d)) { return (char*)"-infinity"; } else { return (char*)"infinity"; } } bool neg = d < 0; if (neg) { d = -d; } // try to emit the fewest necessary characters bool integer = wasm::isInteger(d); #define BUFFERSIZE 1000 // f is normal, e is scientific for float, x for integer static char full_storage_f[BUFFERSIZE], full_storage_e[BUFFERSIZE]; // full has one more char, for a possible '-' static char *storage_f = full_storage_f + 1, *storage_e = full_storage_e + 1; auto err_f = std::numeric_limits::quiet_NaN(); auto err_e = std::numeric_limits::quiet_NaN(); for (int e = 0; e <= 1; e++) { char* buffer = e ? storage_e : storage_f; double temp; if (!integer) { static char format[6]; for (int i = 0; i <= 18; i++) { format[0] = '%'; format[1] = '.'; if (i < 10) { format[2] = '0' + i; format[3] = e ? 'e' : 'f'; format[4] = 0; } else { format[2] = '1'; format[3] = '0' + (i - 10); format[4] = e ? 'e' : 'f'; format[5] = 0; } snprintf(buffer, BUFFERSIZE - 1, format, d); sscanf(buffer, "%lf", &temp); // errv("%.18f, %.18e => %s => %.18f, %.18e (%d), ", d, d, // buffer, temp, temp, temp == d); if (temp == d) { break; } } } else { // integer assert(d >= 0); if (wasm::isUInteger64(d)) { unsigned long long uu = wasm::toUInteger64(d); bool asHex = e && !finalize; snprintf(buffer, BUFFERSIZE - 1, asHex ? "0x%llx" : "%llu", uu); if (asHex) { unsigned long long tempULL; sscanf(buffer, "%llx", &tempULL); temp = (double)tempULL; } else { sscanf(buffer, "%lf", &temp); } } else { // too large for a machine integer, just use floats // even on integers, e with a dot is useful, e.g. 1.2e+200 snprintf(buffer, BUFFERSIZE - 1, e ? "%e" : "%.0f", d); sscanf(buffer, "%lf", &temp); } // errv("%.18f, %.18e => %s => %.18f, %.18e, %llu (%d)\n", d, // d, buffer, temp, temp, uu, temp == d); } (e ? err_e : err_f) = fabs(temp - d); // errv("current attempt: %.18f => %s", d, buffer); // assert(temp == d); char* dot = strchr(buffer, '.'); if (dot) { // remove trailing zeros char* end = dot + 1; while (*end >= '0' && *end <= '9') { end++; } end--; while (*end == '0') { char* copy = end; do { copy[0] = copy[1]; } while (*copy++ != 0); end--; } // errv("%.18f => %s", d, buffer); // remove preceding zeros while (*buffer == '0') { char* copy = buffer; do { copy[0] = copy[1]; } while (*copy++ != 0); } // errv("%.18f ===> %s", d, buffer); } else if (!integer || !e) { // no dot. try to change 12345000 => 12345e3 char* end = strchr(buffer, 0); end--; char* test = end; // remove zeros, and also doubles can use at most 24 digits, we can // truncate any extras even if not zero while ((*test == '0' || test - buffer > 24) && test > buffer) { test--; } int num = end - test; if (num >= 3) { test++; test[0] = 'e'; if (num < 10) { test[1] = '0' + num; test[2] = 0; } else if (num < 100) { test[1] = '0' + (num / 10); test[2] = '0' + (num % 10); test[3] = 0; } else { assert(num < 1000); test[1] = '0' + (num / 100); test[2] = '0' + (num % 100) / 10; test[3] = '0' + (num % 10); test[4] = 0; } } } // errv("..current attempt: %.18f => %s", d, buffer); } // fprintf(stderr, "options:\n%s\n%s\n (first? %d)\n", storage_e, storage_f, // strlen(storage_e) < strlen(storage_f)); char* ret; if (err_e == err_f) { ret = strlen(storage_e) < strlen(storage_f) ? storage_e : storage_f; } else { ret = err_e < err_f ? storage_e : storage_f; } if (neg) { ret--; // safe to go back one, there is one more char in full_* *ret = '-'; } return ret; } void printNum(Ref node) { if (node->getNumber() < 0 && buffer[used - 1] == '-') { emit(' '); // cannot join - and - to --, looks like the -- operator } emit(numToString(node->getNumber(), finalize)); } void printString(Ref node) { emit('"'); emit(node[1]->getCString()); emit('"'); } // Parens optimizing bool capturesOperators(Ref node) { Ref type = node[0]; return type == CALL || type == ARRAY || type == OBJECT || type == SEQ; } int getPrecedence(Ref node, bool parent) { if (node->isAssign() || node->isAssignName()) { return OperatorClass::getPrecedence(OperatorClass::Binary, SET); } if (!node->isArray()) { // node is a value return -1; } Ref type = node[0]; if (type == BINARY || type == UNARY_PREFIX) { return OperatorClass::getPrecedence( type == BINARY ? OperatorClass::Binary : OperatorClass::Prefix, node[1]->getIString()); } else if (type == SEQ) { return OperatorClass::getPrecedence(OperatorClass::Binary, COMMA); } else if (type == CALL) { // call arguments are split by commas, but call itself is safe return parent ? OperatorClass::getPrecedence(OperatorClass::Binary, COMMA) : -1; } else if (type == CONDITIONAL) { return OperatorClass::getPrecedence(OperatorClass::Tertiary, QUESTION); } // otherwise, this is something that fixes precedence explicitly, and we can // ignore return -1; // XXX } // check whether we need parens for the child, when rendered in the parent // @param childPosition -1 means it is printed to the left of parent, 0 means // "anywhere", 1 means right bool needParens(Ref parent, Ref child, int childPosition) { int parentPrecedence = getPrecedence(parent, true); int childPrecedence = getPrecedence(child, false); if (childPrecedence > parentPrecedence) { return true; // child is definitely a danger } if (childPrecedence < parentPrecedence) { return false; // definitely cool } // equal precedence, so associativity (rtl/ltr) is what matters // (except for some exceptions, where multiple operators can combine into // confusion) if (parent->isArray() && parent[0] == UNARY_PREFIX) { assert(child[0] == UNARY_PREFIX); if ((parent[1] == PLUS || parent[1] == MINUS) && child[1] == parent[1]) { // cannot emit ++x when we mean +(+x) return true; } } if (childPosition == 0) { return true; // child could be anywhere, so always paren } if (childPrecedence < 0) { return false; // both precedences are safe } // check if child is on the dangerous side if (OperatorClass::getRtl(parentPrecedence)) { return childPosition < 0; } else { return childPosition > 0; } } void printChild(Ref child, Ref parent, int childPosition = 0) { bool parens = needParens(parent, child, childPosition); if (parens) { emit('('); } print(child); if (parens) { emit(')'); } } void printBinary(Ref node) { printChild(node[2], node, -1); space(); emit(node[1]->getCString()); space(); printChild(node[3], node, 1); } void printUnaryPrefix(Ref node) { if (finalize && node[1] == PLUS && (node[2]->isNumber() || (node[2]->isArray() && node[2][0] == UNARY_PREFIX && node[2][1] == MINUS && node[2][2]->isNumber()))) { // emit a finalized number int last = used; print(node[2]); ensure(1); // we temporarily append a 0 char* curr = buffer + last; // ensure might invalidate buffer[used] = 0; if (strstr(curr, "infinity")) { return; } if (strstr(curr, "nan")) { return; } if (strchr(curr, '.')) { return; // already a decimal point, all good } char* e = strchr(curr, 'e'); if (!e) { emit(".0"); return; } ensure(3); curr = buffer + last; // ensure might invalidate char* end = strchr(curr, 0); while (end >= e) { end[2] = end[0]; end--; } e[0] = '.'; e[1] = '0'; used += 2; return; } if ((buffer[used - 1] == '-' && node[1] == MINUS) || (buffer[used - 1] == '+' && node[1] == PLUS)) { emit(' '); // cannot join - and - to --, looks like the -- operator } emit(node[1]->getCString()); printChild(node[2], node, 1); } void printConditional(Ref node) { printChild(node[1], node, -1); space(); emit('?'); space(); printChild(node[2], node, 0); space(); emit(':'); space(); printChild(node[3], node, 1); } void printCall(Ref node) { printChild(node[1], node, 0); emit('('); Ref args = node[2]; for (size_t i = 0; i < args->size(); i++) { if (i > 0) { (pretty ? emit(", ") : emit(',')); } printChild(args[i], node, 0); } emit(')'); } void printSeq(Ref node) { printChild(node[1], node, -1); emit(','); space(); printChild(node[2], node, 1); } void printDot(Ref node) { print(node[1]); emit('.'); emit(node[2]->getCString()); } void printSwitch(Ref node) { emit("switch"); space(); emit('('); print(node[1]); emit(')'); space(); emit('{'); newline(); Ref cases = node[2]; for (size_t i = 0; i < cases->size(); i++) { Ref c = cases[i]; if (!c[0]) { emit("default:"); } else { emit("case "); print(c[0]); emit(':'); } if (c[1]->size() > 0) { indent++; newline(); auto curr = used; printStats(c[1]); indent--; if (curr != used) { newline(); } else { used--; // avoid the extra indentation we added tentatively } } else { newline(); } } emit('}'); } void printTry(Ref node) { emit("try "); printBlock(node[1]); emit(" catch ("); printName(node[2]); emit(") "); printBlock(node[3]); } void printSub(Ref node) { printChild(node[1], node, -1); emit('['); print(node[2]); emit(']'); } void printVar(Ref node) { emit("var "); Ref args = node[1]; for (size_t i = 0; i < args->size(); i++) { if (i > 0) { (pretty ? emit(", ") : emit(',')); } emit(args[i][0]->getCString()); if (args[i]->size() > 1) { space(); emit('='); space(); print(args[i][1]); } } } static bool isBlock(Ref node) { return node->isArray() && !node->empty() && node[0] == BLOCK; } static bool ifHasElse(Ref node) { assert(node->isArray() && node[0] == IF); return node->size() >= 4 && !!node[3]; } void printIf(Ref node) { emit("if"); safeSpace(); emit('('); print(node[1]); emit(')'); space(); bool emitsBracesAnyhow = isBlock(node[2]); if (!emitsBracesAnyhow) { emit('{'); indent++; newline(); } print(node[2]); if (!emitsBracesAnyhow) { indent--; newline(); emit('}'); } if (ifHasElse(node)) { space(); emit("else"); safeSpace(); bool emitsBracesAnyhow = isBlock(node[3]); if (!emitsBracesAnyhow) { emit('{'); indent++; newline(); } print(node[3]); if (!emitsBracesAnyhow) { indent--; newline(); emit('}'); } } } void printDo(Ref node) { emit("do"); safeSpace(); print(node[2], "{}"); space(); emit("while"); space(); emit('('); print(node[1]); emit(')'); } void printWhile(Ref node) { emit("while"); space(); emit('('); print(node[1]); emit(')'); space(); print(node[2], "{}"); } void printLabel(Ref node) { emit(node[1]->getCString()); space(); emit(':'); space(); print(node[2]); } void printReturn(Ref node) { emit("return"); if (!!node[1]) { emit(' '); print(node[1]); } } void printBreak(Ref node) { emit("break"); if (!!node[1]) { emit(' '); emit(node[1]->getCString()); } } void printContinue(Ref node) { emit("continue"); if (!!node[1]) { emit(' '); emit(node[1]->getCString()); } } void printNew(Ref node) { emit("new "); print(node[1]); } void printArray(Ref node) { emit('['); Ref args = node[1]; for (size_t i = 0; i < args->size(); i++) { if (i > 0) { (pretty ? emit(", ") : emit(',')); } print(args[i]); } emit(']'); } void printObject(Ref node) { emit('{'); indent++; newline(); Ref args = node[1]; for (size_t i = 0; i < args->size(); i++) { if (i > 0) { pretty ? emit(", ") : emit(','); newline(); } bool needQuote = false; const char* str; if (args[i][0]->isArray()) { assert(args[i][0][0] == STRING); // A quoted string. needQuote = true; str = args[i][0][1]->getCString(); } else { // Just a raw string, no quotes. str = args[i][0]->getCString(); } const char* check = str; while (*check) { if (!isalnum(*check) && *check != '_' && *check != '$') { needQuote = true; break; } check++; } if (needQuote) { emit('"'); } emit(str); if (needQuote) { emit('"'); } emit(":"); space(); print(args[i][1]); } indent--; newline(); emit('}'); } }; // cashew builder class ValueBuilder { static Ref makeRawString(const IString& s) { return &arena.alloc()->setString(s); } static Ref makeNull() { return &arena.alloc()->setNull(); } public: static Ref makeRawArray(int size_hint = 0) { return &arena.alloc()->setArray(size_hint); } static Ref makeToplevel() { return &makeRawArray(2) ->push_back(makeRawString(TOPLEVEL)) .push_back(makeRawArray()); } static Ref makeString(IString str) { return &makeRawArray(2) ->push_back(makeRawString(STRING)) .push_back(makeRawString(str)); } static Ref makeBlock() { return &makeRawArray(2) ->push_back(makeRawString(BLOCK)) .push_back(makeRawArray()); } static Ref makeName(IString name) { return makeRawString(name); } static void setBlockContent(Ref target, Ref block) { if (target[0] == TOPLEVEL) { target[1]->setArray(block[1]->getArray()); } else if (target[0] == DEFUN) { target[3]->setArray(block[1]->getArray()); } else { abort(); } } static void appendToBlock(Ref block, Ref element) { assert(block[0] == BLOCK); block[1]->push_back(element); } static Ref makeCall(Ref target) { return &makeRawArray(3) ->push_back(makeRawString(CALL)) .push_back(target) .push_back(makeRawArray()); } static Ref makeCall(Ref target, Ref arg) { Ref ret = &makeRawArray(3) ->push_back(makeRawString(CALL)) .push_back(target) .push_back(makeRawArray()); ret[2]->push_back(arg); return ret; } static Ref makeCall(IString target) { Ref ret = &makeRawArray(3) ->push_back(makeRawString(CALL)) .push_back(makeName(target)) .push_back(makeRawArray()); return ret; } template static Ref makeCall(IString target, Ts... args) { size_t nArgs = sizeof...(Ts); Ref callArgs = makeRawArray(nArgs); Ref argArray[] = {args...}; for (size_t i = 0; i < nArgs; ++i) { callArgs->push_back(argArray[i]); } return &makeRawArray(3) ->push_back(makeRawString(CALL)) .push_back(makeName(target)) .push_back(callArgs); } static void appendToCall(Ref call, Ref element) { assert(call[0] == CALL); call[2]->push_back(element); } static Ref makeStatement(Ref contents) { return contents; } static Ref makeDouble(double num) { return &arena.alloc()->setNumber(num); } static Ref makeInt(uint32_t num) { return makeDouble(double(num)); } static Ref makeInt(int32_t num) { return makeDouble(double(num)); } static Ref makeNum(double num) { return makeDouble(num); } static Ref makeUnary(IString op, Ref value) { return &makeRawArray(3) ->push_back(makeRawString(UNARY_PREFIX)) .push_back(makeRawString(op)) .push_back(value); } static Ref makeBinary(Ref left, IString op, Ref right) { if (op == SET) { if (left->isString()) { return &arena.alloc()->setAssignName(left->getIString(), right); } else { return &arena.alloc()->setAssign(left, right); } } else if (op == COMMA) { return &makeRawArray(3) ->push_back(makeRawString(SEQ)) .push_back(left) .push_back(right); } else { return &makeRawArray(4) ->push_back(makeRawString(BINARY)) .push_back(makeRawString(op)) .push_back(left) .push_back(right); } } static Ref makePrefix(IString op, Ref right) { return &makeRawArray(3) ->push_back(makeRawString(UNARY_PREFIX)) .push_back(makeRawString(op)) .push_back(right); } static Ref makeFunction(IString name) { return &makeRawArray(4) ->push_back(makeRawString(DEFUN)) .push_back(makeRawString(name)) .push_back(makeRawArray()) .push_back(makeRawArray()); } static void appendArgumentToFunction(Ref func, IString arg) { assert(func[0] == DEFUN); func[2]->push_back(makeRawString(arg)); } static Ref makeVar(bool is_const = false) { return &makeRawArray(2) ->push_back(makeRawString(VAR)) .push_back(makeRawArray()); } static void appendToVar(Ref var, IString name, Ref value) { assert(var[0] == VAR); Ref array = &makeRawArray(1)->push_back(makeRawString(name)); if (!!value) { array->push_back(value); } var[1]->push_back(array); } static Ref makeReturn(Ref value) { return &makeRawArray(2) ->push_back(makeRawString(RETURN)) .push_back(!!value ? value : makeNull()); } static Ref makeIndexing(Ref target, Ref index) { return &makeRawArray(3) ->push_back(makeRawString(SUB)) .push_back(target) .push_back(index); } static Ref makeIf(Ref condition, Ref ifTrue, Ref ifFalse) { return &makeRawArray(4) ->push_back(makeRawString(IF)) .push_back(condition) .push_back(ifTrue) .push_back(!!ifFalse ? ifFalse : makeNull()); } static Ref makeConditional(Ref condition, Ref ifTrue, Ref ifFalse) { return &makeRawArray(4) ->push_back(makeRawString(CONDITIONAL)) .push_back(condition) .push_back(ifTrue) .push_back(ifFalse); } static Ref makeSeq(Ref left, Ref right) { return &makeRawArray(3) ->push_back(makeRawString(SEQ)) .push_back(left) .push_back(right); } static Ref makeDo(Ref body, Ref condition) { return &makeRawArray(3) ->push_back(makeRawString(DO)) .push_back(condition) .push_back(body); } static Ref makeWhile(Ref condition, Ref body) { return &makeRawArray(3) ->push_back(makeRawString(WHILE)) .push_back(condition) .push_back(body); } static Ref makeFor(Ref init, Ref condition, Ref inc, Ref body) { return &makeRawArray(5) ->push_back(makeRawString(FOR)) .push_back(init) .push_back(condition) .push_back(inc) .push_back(body); } static Ref makeBreak(IString label) { return &makeRawArray(2) ->push_back(makeRawString(BREAK)) .push_back(!!label ? makeRawString(label) : makeNull()); } static Ref makeContinue(IString label) { return &makeRawArray(2) ->push_back(makeRawString(CONTINUE)) .push_back(!!label ? makeRawString(label) : makeNull()); } static Ref makeLabel(IString name, Ref body) { return &makeRawArray(3) ->push_back(makeRawString(LABEL)) .push_back(makeRawString(name)) .push_back(body); } static Ref makeSwitch(Ref input) { return &makeRawArray(3) ->push_back(makeRawString(SWITCH)) .push_back(input) .push_back(makeRawArray()); } static void appendCaseToSwitch(Ref switch_, Ref arg) { assert(switch_[0] == SWITCH); switch_[2]->push_back( &makeRawArray(2)->push_back(arg).push_back(makeRawArray())); } static void appendDefaultToSwitch(Ref switch_) { assert(switch_[0] == SWITCH); switch_[2]->push_back( &makeRawArray(2)->push_back(makeNull()).push_back(makeRawArray())); } static void appendCodeToSwitch(Ref switch_, Ref code, bool explicitBlock) { assert(switch_[0] == SWITCH); assert(code[0] == BLOCK); if (!explicitBlock) { for (size_t i = 0; i < code[1]->size(); i++) { switch_[2]->back()->back()->push_back(code[1][i]); } } else { switch_[2]->back()->back()->push_back(code); } } static Ref makeTry(Ref try_, Ref arg, Ref catch_) { assert(try_[0] == BLOCK); assert(catch_[0] == BLOCK); return &makeRawArray(3) ->push_back(makeRawString(TRY)) .push_back(try_) .push_back(arg) .push_back(catch_); } static Ref makeDot(Ref obj, IString key) { return &makeRawArray(3) ->push_back(makeRawString(DOT)) .push_back(obj) .push_back(makeRawString(key)); } template static Ref makeDot(Ref obj, Ref key, Ts... args) { return makeDot(makeDot(obj, key), args...); } static Ref makeDot(Ref obj, Ref key) { assert(key->isString()); return makeDot(obj, key->getIString()); } static Ref makeNew(Ref call) { return &makeRawArray(2)->push_back(makeRawString(NEW)).push_back(call); } static Ref makeArray() { return &makeRawArray(2) ->push_back(makeRawString(ARRAY)) .push_back(makeRawArray()); } static void appendToArray(Ref array, Ref element) { assert(array[0] == ARRAY); array[1]->push_back(element); } static Ref makeObject() { return &makeRawArray(2) ->push_back(makeRawString(OBJECT)) .push_back(makeRawArray()); } static void appendToObject(Ref array, IString key, Ref value) { assert(array[0] == OBJECT); array[1]->push_back( &makeRawArray(2)->push_back(makeRawString(key)).push_back(value)); } static void appendToObjectWithQuotes(Ref array, IString key, Ref value) { assert(array[0] == OBJECT); array[1]->push_back( &makeRawArray(2)->push_back(makeString(key)).push_back(value)); } static Ref makeSub(Ref obj, Ref index) { return &makeRawArray(2) ->push_back(makeRawString(SUB)) .push_back(obj) .push_back(index); } static Ref makePtrShift(Ref ptr, int shifts) { return makeBinary(ptr, RSHIFT, makeInt(shifts)); } }; // Tolerates 0.0 in the input; does not trust a +() to be there. class DotZeroValueBuilder : public ValueBuilder { public: static Ref makeDouble(double num) { return makePrefix(PLUS, ValueBuilder::makeDouble(num)); } }; } // namespace cashew #endif // wasm_simple_ast_h