blob: b1e3d87f8afa8aa108281ca328b18399b546356e [file] [log] [blame] [edit]
// Pure parsing. Calls methods on a Builder (template argument) to actually construct the AST
//
// XXX All parsing methods assume they take ownership of the input string. This lets them reuse
// parts of it. You will segfault if the input string cannot be reused and written to.
#include <vector>
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include "istring.h"
namespace cashew {
// common strings
extern thread_local IString TOPLEVEL,
DEFUN,
BLOCK,
STAT,
ASSIGN,
NAME,
VAR,
CONST,
CONDITIONAL,
BINARY,
RETURN,
IF,
ELSE,
WHILE,
DO,
FOR,
SEQ,
SUB,
CALL,
NUM,
LABEL,
BREAK,
CONTINUE,
SWITCH,
STRING,
INF,
NaN,
TEMP_RET0,
UNARY_PREFIX,
UNARY_POSTFIX,
MATH_FROUND,
SIMD_FLOAT32X4,
SIMD_INT32X4,
PLUS,
MINUS,
OR,
AND,
XOR,
L_NOT,
B_NOT,
LT,
GE,
LE,
GT,
EQ,
NE,
DIV,
MOD,
MUL,
RSHIFT,
LSHIFT,
TRSHIFT,
TEMP_DOUBLE_PTR,
HEAP8,
HEAP16,
HEAP32,
HEAPF32,
HEAPU8,
HEAPU16,
HEAPU32,
HEAPF64,
F0,
EMPTY,
FUNCTION,
OPEN_PAREN,
OPEN_BRACE,
OPEN_CURLY,
CLOSE_CURLY,
COMMA,
QUESTION,
COLON,
CASE,
DEFAULT,
DOT,
PERIOD,
NEW,
ARRAY,
OBJECT,
THROW,
SET;
extern thread_local IStringSet keywords;
extern const char *OPERATOR_INITS, *SEPARATORS;
extern int MAX_OPERATOR_SIZE, LOWEST_PREC;
struct OperatorClass {
enum Type {
Binary = 0,
Prefix = 1,
Postfix = 2,
Tertiary = 3
};
IStringSet ops;
bool rtl;
Type type;
OperatorClass(const char* o, bool r, Type t) : ops(o), rtl(r), type(t) {}
static int getPrecedence(Type type, IString op);
static bool getRtl(int prec);
};
extern thread_local std::vector<OperatorClass> operatorClasses;
extern bool isIdentInit(char x);
extern bool isIdentPart(char x);
// parser
template<class NodeRef, class Builder>
class Parser {
static bool isSpace(char x) { return x == 32 || x == 9 || x == 10 || x == 13; } /* space, tab, linefeed/newline, or return */
static char* skipSpace(char* curr) {
while (*curr) {
if (isSpace(*curr)) {
curr++;
continue;
}
if (curr[0] == '/' && curr[1] == '/') {
curr += 2;
while (*curr && *curr != '\n') curr++;
if (*curr) curr++;
continue;
}
if (curr[0] == '/' && curr[1] == '*') {
curr += 2;
while (*curr && (curr[0] != '*' || curr[1] != '/')) curr++;
curr += 2;
continue;
}
break;
}
return curr;
}
static bool isDigit(char x) { return x >= '0' && x <= '9'; }
static bool hasChar(const char* list, char x) { while (*list) if (*list++ == x) return true; return false; }
static bool is32Bit(double x) {
return x == (int)x || x == (unsigned int)x;
}
// An atomic fragment of something. Stops at a natural boundary.
enum FragType {
KEYWORD = 0,
OPERATOR = 1,
IDENT = 2,
STRING = 3, // without quotes
INT = 4,
DOUBLE = 5,
SEPARATOR = 6
};
struct Frag {
#ifndef _MSC_VER // MSVC does not allow unrestricted unions: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2544.pdf
union {
#endif
IString str;
double num;
#ifndef _MSC_VER
};
#endif
int size;
FragType type;
bool isNumber() const {
return type == INT || type == DOUBLE;
}
explicit Frag(char* src) {
assert(!isSpace(*src));
char *start = src;
if (isIdentInit(*src)) {
// read an identifier or a keyword
src++;
while (isIdentPart(*src)) {
src++;
}
if (*src == 0) {
str.set(start);
} else {
char temp = *src;
*src = 0;
str.set(start, false);
*src = temp;
}
type = keywords.has(str) ? KEYWORD : IDENT;
} else if (*src == '"' || *src == '\'') {
char *end = strchr(src+1, *src);
*end = 0;
str.set(src+1);
src = end+1;
type = STRING;
} else if (isDigit(*src) || (src[0] == '.' && isDigit(src[1]))) {
if (src[0] == '0' && (src[1] == 'x' || src[1] == 'X')) {
// Explicitly parse hex numbers of form "0x...", because strtod
// supports hex number strings only in C++11, and Visual Studio 2013 does
// not yet support that functionality.
src += 2;
num = 0;
while (1) {
if (*src >= '0' && *src <= '9') { num *= 16; num += *src - '0'; }
else if (*src >= 'a' && *src <= 'f') { num *= 16; num += *src - 'a' + 10; }
else if (*src >= 'A' && *src <= 'F') { num *= 16; num += *src - 'F' + 10; }
else break;
src++;
}
} else {
num = strtod(start, &src);
}
// asm.js must have a '.' for double values. however, we also tolerate
// uglify's tendency to emit without a '.' (and fix it later with a +).
// for valid asm.js input, the '.' should be enough, and for uglify
// in the emscripten optimizer pipeline, we use simple_ast where INT/DOUBLE
// is quite the same at this point anyhow
type = (std::find(start, src, '.') == src && is32Bit(num)) ? INT : DOUBLE;
assert(src > start);
} else if (hasChar(OPERATOR_INITS, *src)) {
switch (*src) {
case '!': str = src[1] == '=' ? NE : L_NOT; break;
case '%': str = MOD; break;
case '&': str = AND; break;
case '*': str = MUL; break;
case '+': str = PLUS; break;
case ',': str = COMMA; break;
case '-': str = MINUS; break;
case '.': str = PERIOD; break;
case '/': str = DIV; break;
case ':': str = COLON; break;
case '<': str = src[1] == '<' ? LSHIFT : (src[1] == '=' ? LE : LT); break;
case '=': str = src[1] == '=' ? EQ : SET; break;
case '>': str = src[1] == '>' ? (src[2] == '>' ? TRSHIFT : RSHIFT) : (src[1] == '=' ? GE : GT); break;
case '?': str = QUESTION; break;
case '^': str = XOR; break;
case '|': str = OR; break;
case '~': str = B_NOT; break;
}
assert(!str.isNull());
size = strlen(str.str);
#ifndef NDEBUG
char temp = start[size];
start[size] = 0;
assert(strcmp(str.str, start) == 0);
start[size] = temp;
#endif
type = OPERATOR;
return;
} else if (hasChar(SEPARATORS, *src)) {
type = SEPARATOR;
char temp = src[1];
src[1] = 0;
str.set(src, false);
src[1] = temp;
src++;
} else {
dumps("frag parsing", src);
assert(0);
}
size = src - start;
}
};
// Parses an element in a list of such elements, e.g. list of statements in a block, or list of parameters in a call
NodeRef parseElement(char*& src, const char* seps=";") {
//dumps("parseElement", src);
src = skipSpace(src);
Frag frag(src);
src += frag.size;
switch (frag.type) {
case KEYWORD: {
return parseAfterKeyword(frag, src, seps);
}
case IDENT:
case STRING:
case INT:
case DOUBLE: {
src = skipSpace(src);
if (frag.type == IDENT) return parseAfterIdent(frag, src, seps);
else return parseExpression(parseFrag(frag), src, seps);
}
case SEPARATOR: {
if (frag.str == OPEN_PAREN) return parseExpression(parseAfterParen(src), src, seps);
if (frag.str == OPEN_BRACE) return parseExpression(parseAfterBrace(src), src, seps);
if (frag.str == OPEN_CURLY) return parseExpression(parseAfterCurly(src), src, seps);
assert(0);
}
case OPERATOR: {
return parseExpression(frag.str, src, seps);
}
default: /* dumps("parseElement", src); printf("bad frag type: %d\n", frag.type); */ assert(0);
}
return nullptr;
}
NodeRef parseFrag(Frag& frag) {
switch (frag.type) {
case IDENT: return Builder::makeName(frag.str);
case STRING: return Builder::makeString(frag.str);
case INT: return Builder::makeInt(uint32_t(frag.num));
case DOUBLE: return Builder::makeDouble(frag.num);
default: assert(0);
}
return nullptr;
}
NodeRef parseAfterKeyword(Frag& frag, char*& src, const char* seps) {
src = skipSpace(src);
if (frag.str == FUNCTION) return parseFunction(frag, src, seps);
else if (frag.str == VAR) return parseVar(frag, src, seps);
else if (frag.str == CONST) return parseVar(frag, src, seps);
else if (frag.str == RETURN) return parseReturn(frag, src, seps);
else if (frag.str == IF) return parseIf(frag, src, seps);
else if (frag.str == DO) return parseDo(frag, src, seps);
else if (frag.str == WHILE) return parseWhile(frag, src, seps);
else if (frag.str == BREAK) return parseBreak(frag, src, seps);
else if (frag.str == CONTINUE) return parseContinue(frag, src, seps);
else if (frag.str == SWITCH) return parseSwitch(frag, src, seps);
else if (frag.str == NEW) return parseNew(frag, src, seps);
dumps(frag.str.str, src);
assert(0);
return nullptr;
}
NodeRef parseFunction(Frag& frag, char*& src, const char* seps) {
Frag name(src);
if (name.type == IDENT) {
src += name.size;
} else {
assert(name.type == SEPARATOR && name.str[0] == '(');
name.str = IString();
}
NodeRef ret = Builder::makeFunction(name.str);
src = skipSpace(src);
assert(*src == '(');
src++;
while (1) {
src = skipSpace(src);
if (*src == ')') break;
Frag arg(src);
assert(arg.type == IDENT);
src += arg.size;
Builder::appendArgumentToFunction(ret, arg.str);
src = skipSpace(src);
if (*src && *src == ')') break;
if (*src && *src == ',') {
src++;
continue;
}
assert(0);
}
assert(*src == ')');
src++;
parseBracketedBlock(src, ret);
// TODO: parse expression?
return ret;
}
NodeRef parseVar(Frag& frag, char*& src, const char* seps) {
NodeRef ret = Builder::makeVar(frag.str == CONST);
while (1) {
src = skipSpace(src);
if (*src == ';') break;
Frag name(src);
assert(name.type == IDENT);
NodeRef value;
src += name.size;
src = skipSpace(src);
if (*src == '=') {
src++;
src = skipSpace(src);
value = parseElement(src, ";,");
}
Builder::appendToVar(ret, name.str, value);
src = skipSpace(src);
if (*src && *src == ';') break;
if (*src && *src == ',') {
src++;
continue;
}
assert(0);
}
assert(*src == ';');
src++;
return ret;
}
NodeRef parseReturn(Frag& frag, char*& src, const char* seps) {
src = skipSpace(src);
NodeRef value = !hasChar(seps, *src) ? parseElement(src, seps) : nullptr;
src = skipSpace(src);
assert(hasChar(seps, *src));
if (*src == ';') src++;
return Builder::makeReturn(value);
}
NodeRef parseIf(Frag& frag, char*& src, const char* seps) {
NodeRef condition = parseParenned(src);
NodeRef ifTrue = parseMaybeBracketed(src, seps);
src = skipSpace(src);
NodeRef ifFalse;
if (*src && !hasChar(seps, *src)) {
Frag next(src);
if (next.type == KEYWORD && next.str == ELSE) {
src += next.size;
ifFalse = parseMaybeBracketed(src, seps);
}
}
return Builder::makeIf(condition, ifTrue, ifFalse);
}
NodeRef parseDo(Frag& frag, char*& src, const char* seps) {
NodeRef body = parseMaybeBracketed(src, seps);
src = skipSpace(src);
Frag next(src);
assert(next.type == KEYWORD && next.str == WHILE);
src += next.size;
NodeRef condition = parseParenned(src);
return Builder::makeDo(body, condition);
}
NodeRef parseWhile(Frag& frag, char*& src, const char* seps) {
NodeRef condition = parseParenned(src);
NodeRef body = parseMaybeBracketed(src, seps);
return Builder::makeWhile(condition, body);
}
NodeRef parseBreak(Frag& frag, char*& src, const char* seps) {
src = skipSpace(src);
Frag next(src);
if (next.type == IDENT) src += next.size;
return Builder::makeBreak(next.type == IDENT ? next.str : IString());
}
NodeRef parseContinue(Frag& frag, char*& src, const char* seps) {
src = skipSpace(src);
Frag next(src);
if (next.type == IDENT) src += next.size;
return Builder::makeContinue(next.type == IDENT ? next.str : IString());
}
NodeRef parseSwitch(Frag& frag, char*& src, const char* seps) {
NodeRef ret = Builder::makeSwitch(parseParenned(src));
src = skipSpace(src);
assert(*src == '{');
src++;
while (1) {
// find all cases and possibly a default
src = skipSpace(src);
if (*src == '}') break;
Frag next(src);
if (next.type == KEYWORD) {
if (next.str == CASE) {
src += next.size;
src = skipSpace(src);
NodeRef arg;
Frag value(src);
if (value.isNumber()) {
arg = parseFrag(value);
src += value.size;
} else {
assert(value.type == OPERATOR);
assert(value.str == MINUS);
src += value.size;
src = skipSpace(src);
Frag value2(src);
assert(value2.isNumber());
arg = Builder::makePrefix(MINUS, parseFrag(value2));
src += value2.size;
}
Builder::appendCaseToSwitch(ret, arg);
src = skipSpace(src);
assert(*src == ':');
src++;
continue;
} else if (next.str == DEFAULT) {
src += next.size;
Builder::appendDefaultToSwitch(ret);
src = skipSpace(src);
assert(*src == ':');
src++;
continue;
}
// otherwise, may be some keyword that happens to start a block (e.g. case 1: _return_ 5)
}
// not case X: or default: or }, so must be some code
src = skipSpace(src);
bool explicitBlock = *src == '{';
Builder::appendCodeToSwitch(ret, parseMaybeBracketedBlock(src, ";}", CASE, DEFAULT), explicitBlock);
}
src = skipSpace(src);
assert(*src == '}');
src++;
return ret;
}
NodeRef parseNew(Frag& frag, char*& src, const char* seps) {
return Builder::makeNew(parseElement(src, seps));
}
NodeRef parseAfterIdent(Frag& frag, char*& src, const char* seps) {
assert(!isSpace(*src));
if (*src == '(') return parseExpression(parseCall(parseFrag(frag), src), src, seps);
if (*src == '[') return parseExpression(parseIndexing(parseFrag(frag), src), src, seps);
if (*src == ':' && expressionPartsStack.back().size() == 0) {
src++;
src = skipSpace(src);
NodeRef inner;
if (*src == '{') { // context lets us know this is not an object, but a block
inner = parseBracketedBlock(src);
} else {
inner = parseElement(src, seps);
}
return Builder::makeLabel(frag.str, inner);
}
if (*src == '.') return parseExpression(parseDotting(parseFrag(frag), src), src, seps);
return parseExpression(parseFrag(frag), src, seps);
}
NodeRef parseCall(NodeRef target, char*& src) {
expressionPartsStack.resize(expressionPartsStack.size()+1);
assert(*src == '(');
src++;
NodeRef ret = Builder::makeCall(target);
while (1) {
src = skipSpace(src);
if (*src == ')') break;
Builder::appendToCall(ret, parseElement(src, ",)"));
src = skipSpace(src);
if (*src && *src == ')') break;
if (*src && *src == ',') {
src++;
continue;
}
assert(0);
}
src++;
assert(expressionPartsStack.back().size() == 0);
expressionPartsStack.pop_back();
return ret;
}
NodeRef parseIndexing(NodeRef target, char*& src) {
expressionPartsStack.resize(expressionPartsStack.size()+1);
assert(*src == '[');
src++;
NodeRef ret = Builder::makeIndexing(target, parseElement(src, "]"));
src = skipSpace(src);
assert(*src == ']');
src++;
assert(expressionPartsStack.back().size() == 0);
expressionPartsStack.pop_back();
return ret;
}
NodeRef parseDotting(NodeRef target, char*& src) {
assert(*src == '.');
src++;
Frag key(src);
assert(key.type == IDENT);
src += key.size;
return Builder::makeDot(target, key.str);
}
NodeRef parseAfterParen(char*& src) {
expressionPartsStack.resize(expressionPartsStack.size()+1);
src = skipSpace(src);
NodeRef ret = parseElement(src, ")");
src = skipSpace(src);
assert(*src == ')');
src++;
assert(expressionPartsStack.back().size() == 0);
expressionPartsStack.pop_back();
return ret;
}
NodeRef parseAfterBrace(char*& src) {
expressionPartsStack.resize(expressionPartsStack.size()+1);
NodeRef ret = Builder::makeArray();
while (1) {
src = skipSpace(src);
assert(*src);
if (*src == ']') break;
NodeRef element = parseElement(src, ",]");
Builder::appendToArray(ret, element);
src = skipSpace(src);
if (*src == ',') {
src++;
continue;
} else assert(*src == ']');
}
assert(*src == ']');
src++;
return ret;
}
NodeRef parseAfterCurly(char*& src) {
expressionPartsStack.resize(expressionPartsStack.size()+1);
NodeRef ret = Builder::makeObject();
while (1) {
src = skipSpace(src);
assert(*src);
if (*src == '}') break;
Frag key(src);
assert(key.type == IDENT || key.type == STRING);
src += key.size;
src = skipSpace(src);
assert(*src == ':');
src++;
NodeRef value = parseElement(src, ",}");
Builder::appendToObject(ret, key.str, value);
src = skipSpace(src);
if (*src == ',') {
src++;
continue;
} else assert(*src == '}');
}
assert(*src == '}');
src++;
return ret;
}
struct ExpressionElement {
bool isNode;
#ifndef _MSC_VER // MSVC does not allow unrestricted unions: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2544.pdf
union {
#endif
NodeRef node;
IString op;
#ifndef _MSC_VER
};
#endif
ExpressionElement(NodeRef n) : isNode(true), node(n) {}
ExpressionElement(IString o) : isNode(false), op(o) {}
NodeRef getNode() {
assert(isNode);
return node;
}
IString getOp() {
assert(!isNode);
return op;
}
};
// This is a list of the current stack of node-operator-node-operator-etc.
// this works by each parseExpression call appending to the vector; then recursing out, and the toplevel sorts it all
typedef std::vector<ExpressionElement> ExpressionParts;
std::vector<ExpressionParts> expressionPartsStack;
void dumpParts(ExpressionParts& parts, int i) {
printf("expressionparts: %d (at %d)\n", parts.size(), i);
printf("| ");
for (int i = 0; i < parts.size(); i++) {
if (parts[i].isNode) {
parts[i].getNode()->stringify(std::cout);
printf(" ");
} else {
printf(" _%s_ ", parts[i].getOp().str);
}
}
printf("|\n");
}
NodeRef makeBinary(NodeRef left, IString op, NodeRef right) {
if (op == PERIOD) {
return Builder::makeDot(left, right);
} else {
return Builder::makeBinary(left, op ,right);
}
}
NodeRef parseExpression(ExpressionElement initial, char*&src, const char* seps) {
//dumps("parseExpression", src);
ExpressionParts& parts = expressionPartsStack.back();
src = skipSpace(src);
if (*src == 0 || hasChar(seps, *src)) {
if (parts.size() > 0) {
parts.push_back(initial); // cherry on top of the cake
}
return initial.getNode();
}
bool top = parts.size() == 0;
if (initial.isNode) {
Frag next(src);
if (next.type == OPERATOR) {
parts.push_back(initial);
src += next.size;
parts.push_back(next.str);
} else {
if (*src == '(') {
initial = parseCall(initial.getNode(), src);
} else if (*src == '[') {
initial = parseIndexing(initial.getNode(), src);
} else {
dumps("bad parseExpression state", src);
abort();
}
return parseExpression(initial, src, seps);
}
} else {
parts.push_back(initial);
}
NodeRef last = parseElement(src, seps);
if (!top) return last;
{
ExpressionParts& parts = expressionPartsStack.back(); // |parts| may have been invalidated by that call
// we are the toplevel. sort it all out
// collapse right to left, highest priority first
//dumpParts(parts, 0);
for (auto& ops : operatorClasses) {
if (ops.rtl) {
// right to left
for (int i = parts.size()-1; i >= 0; i--) {
if (parts[i].isNode) continue;
IString op = parts[i].getOp();
if (!ops.ops.has(op)) continue;
if (ops.type == OperatorClass::Binary && i > 0 && i < (int)parts.size()-1) {
parts[i] = makeBinary(parts[i-1].getNode(), op, parts[i+1].getNode());
parts.erase(parts.begin() + i + 1);
parts.erase(parts.begin() + i - 1);
} else if (ops.type == OperatorClass::Prefix && i < (int)parts.size()-1) {
if (i > 0 && parts[i-1].isNode) continue; // cannot apply prefix operator if it would join two nodes
parts[i] = Builder::makePrefix(op, parts[i+1].getNode());
parts.erase(parts.begin() + i + 1);
} else if (ops.type == OperatorClass::Tertiary) {
// we must be at X ? Y : Z
// ^
//dumpParts(parts, i);
if (op != COLON) continue;
assert(i < (int)parts.size()-1 && i >= 3);
if (parts[i-2].getOp() != QUESTION) continue; // e.g. x ? y ? 1 : 0 : 2
parts[i-3] = Builder::makeConditional(parts[i-3].getNode(), parts[i-1].getNode(), parts[i+1].getNode());
parts.erase(parts.begin() + i - 2, parts.begin() + i + 2);
i = parts.size(); // basically a reset, due to things like x ? y ? 1 : 0 : 2
} // TODO: postfix
}
} else {
// left to right
for (int i = 0; i < (int)parts.size(); i++) {
if (parts[i].isNode) continue;
IString op = parts[i].getOp();
if (!ops.ops.has(op)) continue;
if (ops.type == OperatorClass::Binary && i > 0 && i < (int)parts.size()-1) {
parts[i] = makeBinary(parts[i-1].getNode(), op, parts[i+1].getNode());
parts.erase(parts.begin() + i + 1);
parts.erase(parts.begin() + i - 1);
i--;
} else if (ops.type == OperatorClass::Prefix && i < (int)parts.size()-1) {
if (i > 0 && parts[i-1].isNode) continue; // cannot apply prefix operator if it would join two nodes
parts[i] = Builder::makePrefix(op, parts[i+1].getNode());
parts.erase(parts.begin() + i + 1);
i = std::max(i-2, 0); // allow a previous prefix operator to cascade
} // TODO: tertiary, postfix
}
}
}
assert(parts.size() == 1);
NodeRef ret = parts[0].getNode();
parts.clear();
return ret;
}
}
// Parses a block of code (e.g. a bunch of statements inside {,}, or the top level of o file)
NodeRef parseBlock(char*& src, NodeRef block=nullptr, const char* seps=";", IString keywordSep1=IString(), IString keywordSep2=IString()) {
//dumps("parseBlock", src);
if (!block) block = Builder::makeBlock();
while (*src) {
src = skipSpace(src);
if (*src == 0) break;
if (*src == ';') {
src++; // skip a statement in this block
continue;
}
if (hasChar(seps, *src)) break;
if (!!keywordSep1) {
Frag next(src);
if (next.type == KEYWORD && next.str == keywordSep1) break;
}
if (!!keywordSep2) {
Frag next(src);
if (next.type == KEYWORD && next.str == keywordSep2) break;
}
NodeRef element = parseElementOrStatement(src, seps);
Builder::appendToBlock(block, element);
}
return block;
}
NodeRef parseBracketedBlock(char*& src, NodeRef block=nullptr) {
if (!block) block = Builder::makeBlock();
src = skipSpace(src);
assert(*src == '{');
src++;
parseBlock(src, block, ";}"); // the two are not symmetrical, ; is just internally separating, } is the final one - parseBlock knows all this
assert(*src == '}');
src++;
return block;
}
NodeRef parseElementOrStatement(char*& src, const char *seps) {
src = skipSpace(src);
if (*src == ';') {
src++;
return Builder::makeBlock(); // we don't need the brackets here, but oh well
}
NodeRef ret = parseElement(src, seps);
src = skipSpace(src);
if (*src == ';') {
ret = Builder::makeStatement(ret);
src++;
}
return ret;
}
NodeRef parseMaybeBracketed(char*& src, const char *seps) {
src = skipSpace(src);
return *src == '{' ? parseBracketedBlock(src) : parseElementOrStatement(src, seps);
}
NodeRef parseMaybeBracketedBlock(char*& src, const char *seps, IString keywordSep1=IString(), IString keywordSep2=IString()) {
src = skipSpace(src);
return *src == '{' ? parseBracketedBlock(src) : parseBlock(src, nullptr, seps, keywordSep1, keywordSep2);
}
NodeRef parseParenned(char*& src) {
src = skipSpace(src);
assert(*src == '(');
src++;
NodeRef ret = parseElement(src, ")");
src = skipSpace(src);
assert(*src == ')');
src++;
return ret;
}
// Debugging
char *allSource;
int allSize;
static void dumps(const char *where, char* curr) {
/*
printf("%s:\n=============\n", where);
for (int i = 0; i < allSize; i++) printf("%c", allSource[i] ? allSource[i] : '?');
printf("\n");
for (int i = 0; i < (curr - allSource); i++) printf(" ");
printf("^\n=============\n");
*/
fprintf(stderr, "%s:\n==========\n", where);
int newlinesLeft = 2;
int charsLeft = 200;
while (*curr) {
if (*curr == '\n') {
newlinesLeft--;
if (newlinesLeft == 0) break;
}
charsLeft--;
if (charsLeft == 0) break;
fprintf(stderr, "%c", *curr++);
}
fprintf(stderr, "\n\n");
}
public:
Parser() : allSource(nullptr), allSize(0) {
expressionPartsStack.resize(1);
}
// Highest-level parsing, as of a JavaScript script file.
NodeRef parseToplevel(char* src) {
allSource = src;
allSize = strlen(src);
return parseBlock(src, Builder::makeToplevel());
}
};
} // namespace cashew