/* * 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 "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 Ref; struct Value; 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); bool operator==(double d) { abort(); return false; } // prevent Ref == number, which is potentially ambiguous; use ->getNumber() == number bool operator==(Ref other); bool operator!(); // check if null, in effect }; // Arena allocation, free it all on process exit typedef std::vector ArrayStorage; struct Arena { #define CHUNK_SIZE 1000 std::vector chunks; int index; // in last chunk std::vector arr_chunks; int arr_index; Arena() : index(0), arr_index(0) {} ~Arena(); Ref alloc(); ArrayStorage* allocArray(); }; extern Arena arena; // Main value type struct Value { enum Type { String = 0, Number = 1, Array = 2, Null = 3, Bool = 4, Object = 5 }; Type type; typedef std::unordered_map ObjectStorage; #ifdef _MSC_VER // MSVC does not allow unrestricted unions: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2544.pdf IString str; #endif union { // TODO: optimize #ifndef _MSC_VER IString str; #endif double num; ArrayStorage *arr; bool boo; ObjectStorage *obj; }; // constructors all copy their input Value() : type(Null), num(0) {} explicit Value(const char *s) : type(Null) { setString(s); } explicit Value(double n) : type(Null) { setNumber(n); } explicit Value(ArrayStorage &a) : type(Null) { 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(); arr->shrink_to_fit(); } 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.allocArray(); *arr = a; return *this; } Value& setArray(size_t size_hint=0) { free(); type = Array; arr = arena.allocArray(); arr->reserve(size_hint); return *this; } Value& setNull() { free(); type = Null; return *this; } Value& setBool(bool b) { // Bool in the name, as otherwise might overload over int free(); type = Bool; boo = b; return *this; } Value& setObject() { free(); type = Object; obj = new ObjectStorage(); return *this; } 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 isBool(bool b) { return type == Bool && b == boo; } // avoid overloading == as it might overload over int 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; } int getInteger() { // convenience function to get a known integer assert(fmod(getNumber(), 1) == 0); int ret = int(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; case Object: 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 } return true; } bool deepCompare(Ref ref) { Value& other = *ref; if (*this == other) return true; // either same pointer, or identical value type (string, number, null or bool) if (type != other.type) return false; if (type == Array) { if (arr->size() != other.arr->size()) return false; for (unsigned i = 0; i < arr->size(); i++) { if (!(*arr)[i]->deepCompare((*other.arr)[i])) return false; } return true; } else if (type == Object) { if (obj->size() != other.obj->size()) return false; for (auto i : *obj) { if (other.obj->count(i.first) == 0) return false; if (i.second->deepCompare((*other.obj)[i.first])) return false; } return true; } return false; } char* parse(char* curr) { #define is_json_space(x) (x == 32 || x == 9 || x == 10 || x == 13) /* space, tab, linefeed/newline, or return */ #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) { static int indent = 0; #define indentify() { for (int i_ = 0; i_ < indent; i_++) os << " "; } switch (type) { case String: if (str.str) { os << '"' << str.str << '"'; } else { os << "\"(null)\""; } break; case Number: os << std::setprecision(17) << num; // doubles can have 17 digits of precision break; case Array: if (arr->size() == 0) { os << "[]"; break; } os << '['; if (pretty) { os << std::endl; indent++; } for (size_t i = 0; i < arr->size(); i++) { if (i > 0) { if (pretty) os << "," << std::endl; else os << ", "; } indentify(); (*arr)[i]->stringify(os, pretty); } if (pretty) { os << std::endl; indent--; } indentify(); os << ']'; break; case Null: os << "null"; break; case Bool: os << (boo ? "true" : "false"); break; case Object: os << '{'; if (pretty) { os << std::endl; indent++; } bool first = true; for (auto i : *obj) { if (first) { first = false; } else { os << ", "; if (pretty) os << std::endl; } indentify(); os << '"' << i.first.c_str() << "\": "; i.second->stringify(os, pretty); } if (pretty) { os << std::endl; indent--; } indentify(); os << '}'; break; } } // String operations // Number operations // Array operations size_t size() { assert(isArray()); return arr->size(); } 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->at(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); } void insert(int x, int num) { arr->insert(arr->begin() + x, num, Ref()); } void insert(int x, Ref node) { arr->insert(arr->begin() + x, 1, node); } 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; } }; // AST traversals // Traverse, calling visit before the children void traversePre(Ref node, std::function visit); // Traverse, calling visitPre before the children and visitPost after void traversePrePost(Ref node, std::function visitPre, std::function visitPost); // Traverse, calling visitPre before the children and visitPost after. If pre returns false, do not traverse children void traversePrePostConditional(Ref node, std::function visitPre, std::function visitPost); // Traverses all the top-level functions in the document void traverseFunctions(Ref ast, std::function visit); // JS printer struct JSPrinter { bool pretty, finalize; char *buffer; size_t size, used; int indent; bool possibleSpace; // add a space to separate identifiers Ref ast; JSPrinter(bool pretty_, bool finalize_, Ref ast_) : pretty(pretty_), finalize(finalize_), buffer(0), size(0), used(0), indent(0), possibleSpace(false), ast(ast_) {} void printAst() { print(ast); buffer[used] = 0; } // Utils void ensure(int safety=100) { if (size < used + safety) { size = std::max((size_t)1024, size * 2) + safety; if (!buffer) { buffer = (char*)malloc(size); if (!buffer) { printf("Out of memory allocating %zd bytes for output buffer!", size); abort(); } } else { char *buf = (char*)realloc(buffer, size); if (!buf) { free(buffer); printf("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(' '); } } void print(Ref node) { ensure(); IString type = node[0]->getIString(); //fprintf(stderr, "printing %s\n", type.str); switch (type.str[0]) { case 'a': { if (type == ASSIGN) printAssign(node); else 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 == NAME) printName(node); else if (type == NUM) printNum(node); else 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 == STAT) printStat(node); else 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 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: { printf("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(stats[i]); } } } 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(); } bool isNothing(Ref node) { return (node[0] == TOPLEVEL && node[1]->size() == 0) || (node[0] == STAT && isNothing(node[1])); } void printStat(Ref node) { if (!isNothing(node[1])) { print(node[1]); if (buffer[used-1] != ';') emit(';'); } } void printAssign(Ref node) { printChild(node[2], node, -1); space(); emit('='); space(); printChild(node[3], node, 1); } void printName(Ref node) { emit(node[1]->getCString()); } static char* numToString(double d, bool finalize=true) { bool neg = d < 0; if (neg) d = -d; // try to emit the fewest necessary characters bool integer = fmod(d, 1) == 0; #define BUFFERSIZE 1000 static char full_storage_f[BUFFERSIZE], full_storage_e[BUFFERSIZE]; // f is normal, e is scientific for float, x for integer static char *storage_f = full_storage_f + 1, *storage_e = full_storage_e + 1; // full has one more char, for a possible '-' 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 snprintf(buffer, BUFFERSIZE-1, e ? "%e" : "%.0f", d); // even on integers, e with a dot is useful, e.g. 1.2e+200 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) { emit(numToString(node[1]->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) { 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) { return parent ? OperatorClass::getPrecedence(OperatorClass::Binary, COMMA) : -1; // call arguments are split by commas, but call itself is safe } else if (type == ASSIGN) { return OperatorClass::getPrecedence(OperatorClass::Binary, SET); } 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[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][0] == NUM || (node[2][0] == UNARY_PREFIX && node[2][1] == MINUS && node[2][2][0] == NUM))) { // 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 (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 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]); } } emit(';'); } static bool ifHasElse(Ref node) { assert(node[0] == IF); return node->size() >= 4 && !!node[3]; } void printIf(Ref node) { emit("if"); safeSpace(); emit('('); print(node[1]); emit(')'); space(); // special case: we need braces to save us from ambiguity, if () { if () } else. otherwise else binds to inner if // also need to recurse for if () { if () { } else { if () } else // (note that this is only a problem if the if body has a single element in it, not a block or such, as then // the block would be braced) // this analysis is a little conservative - it assumes any child if could be confused with us, which implies // all other braces vanished (the worst case for us, we are not saved by other braces). bool needBraces = false; bool hasElse = ifHasElse(node); if (hasElse) { Ref child = node[2]; while (child[0] == IF) { if (!ifHasElse(child)) { needBraces = true; break; } child = child[3]; // continue into the else } } if (needBraces) { emit('{'); indent++; newline(); print(node[2]); indent--; newline(); emit('}'); } else { print(node[2], "{}"); } if (hasElse) { space(); emit("else"); safeSpace(); print(node[3], "{}"); } } void printDo(Ref node) { emit("do"); safeSpace(); print(node[2], "{}"); space(); emit("while"); space(); emit('('); print(node[1]); emit(')'); 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]); } emit(';'); } void printBreak(Ref node) { emit("break"); if (!!node[1]) { emit(' '); emit(node[1]->getCString()); } emit(';'); } void printContinue(Ref node) { emit("continue"); if (!!node[1]) { emit(' '); emit(node[1]->getCString()); } emit(';'); } 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(); } const char *str = args[i][0]->getCString(); const char *check = str; bool needQuote = false; 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 IStringSet statable; 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 &makeRawArray(2)->push_back(makeRawString(NAME)) .push_back(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; } static Ref makeCall(IString target, Ref arg) { Ref ret = &makeRawArray(3)->push_back(makeRawString(CALL)) .push_back(makeName(target)) .push_back(makeRawArray(1)); ret[2]->push_back(arg); return ret; } static Ref makeCall(IString target, Ref arg1, Ref arg2) { Ref ret = &makeRawArray(3)->push_back(makeRawString(CALL)) .push_back(makeName(target)) .push_back(makeRawArray(2)); ret[2]->push_back(arg1); ret[2]->push_back(arg2); return ret; } static Ref makeCall(IString target, Ref arg1, Ref arg2, Ref arg3, Ref arg4) { Ref ret = &makeRawArray(3)->push_back(makeRawString(CALL)) .push_back(makeName(target)) .push_back(makeRawArray(4)); ret[2]->push_back(arg1); ret[2]->push_back(arg2); ret[2]->push_back(arg3); ret[2]->push_back(arg4); return ret; } static Ref makeCall(IString target, Ref arg1, Ref arg2, Ref arg3, Ref arg4, Ref arg5, Ref arg6, Ref arg7, Ref arg8) { Ref ret = &makeRawArray(3)->push_back(makeRawString(CALL)) .push_back(makeName(target)) .push_back(makeRawArray(8)); ret[2]->push_back(arg1); ret[2]->push_back(arg2); ret[2]->push_back(arg3); ret[2]->push_back(arg4); ret[2]->push_back(arg5); ret[2]->push_back(arg6); ret[2]->push_back(arg7); ret[2]->push_back(arg8); return ret; } static Ref makeCall(IString target, Ref arg1, Ref arg2, Ref arg3, Ref arg4, Ref arg5, Ref arg6, Ref arg7, Ref arg8, Ref arg9, Ref arg10, Ref arg11, Ref arg12, Ref arg13, Ref arg14, Ref arg15, Ref arg16) { Ref ret = &makeRawArray(3)->push_back(makeRawString(CALL)) .push_back(makeName(target)) .push_back(makeRawArray(16)); ret[2]->push_back(arg1); ret[2]->push_back(arg2); ret[2]->push_back(arg3); ret[2]->push_back(arg4); ret[2]->push_back(arg5); ret[2]->push_back(arg6); ret[2]->push_back(arg7); ret[2]->push_back(arg8); ret[2]->push_back(arg9); ret[2]->push_back(arg10); ret[2]->push_back(arg11); ret[2]->push_back(arg12); ret[2]->push_back(arg13); ret[2]->push_back(arg14); ret[2]->push_back(arg15); ret[2]->push_back(arg16); return ret; } static void appendToCall(Ref call, Ref element) { assert(call[0] == CALL); call[2]->push_back(element); } static Ref makeStatement(Ref contents) { if (statable.has(contents[0]->getIString())) { return &makeRawArray(2)->push_back(makeRawString(STAT)) .push_back(contents); } else { return contents; // only very specific things actually need to be stat'ed } } static Ref makeDouble(double num) { return &makeRawArray(2)->push_back(makeRawString(NUM)) .push_back(&arena.alloc()->setNumber(num)); } static Ref makeInt(uint32_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) { return &makeRawArray(4)->push_back(makeRawString(ASSIGN)) .push_back(&arena.alloc()->setBool(true)) .push_back(left) .push_back(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 makeDot(Ref obj, IString key) { return &makeRawArray(3)->push_back(makeRawString(DOT)) .push_back(obj) .push_back(makeRawString(key)); } static Ref makeDot(Ref obj, Ref key) { assert(key[0] == NAME); return makeDot(obj, key[1]->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 Ref makeAssign(Ref target, Ref value) { return &makeRawArray(3)->push_back(makeRawString(ASSIGN)) .push_back(&arena.alloc()->setBool(true)) .push_back(target) .push_back(value); } static Ref makeAssign(IString target, Ref value) { return &makeRawArray(3)->push_back(makeRawString(ASSIGN)) .push_back(&arena.alloc()->setBool(true)) .push_back(makeName(target)) .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