AbSyn compPhaseMacEx(EmitInfo finfo, AbSyn ab) { phStart(PH_MacEx); ab = macroExpand(ab); /* modifies the tree in-place */ phEnd((PhPrFun) abPrint, (PhPrFun) abPrettyPrint, (Pointer) ab); return ab; }
local AbSyn abqParseSrcLines(SrcLineList sll) { AbSyn ab; TokenList tl; tl = scan(sll); tl = linearize(tl); ab = parse(&tl); ab = abNormal(ab, false); ab = macroExpand(ab); ab = abNormal(ab, true); return ab; }
local AbSyn shexpParse(String txt) { AbSyn ab; TokenList tl; SrcLineList sll; SrcLine srcLine = slineNew(sposNone, 0, txt); sll = listList(SrcLine)(1, srcLine); tl = scan(sll); tl = linearize(tl); ab = parse(&tl); ab = abNormal(ab, false); ab = macroExpand(ab); ab = abNormal(ab, true); return ab; }
void Preprocessor::substituteUntilNewline(Symbols &substituted) { while (hasNext()) { Token token = next(); if (token == PP_IDENTIFIER) { substituted += macroExpand(this, symbols, index, symbol().lineNum, true); } else if (token == PP_DEFINED) { bool braces = test(PP_LPAREN); next(PP_IDENTIFIER); Symbol definedOrNotDefined = symbol(); definedOrNotDefined.token = macros.contains(definedOrNotDefined)? PP_MOC_TRUE : PP_MOC_FALSE; substituted += definedOrNotDefined; if (braces) test(PP_RPAREN); continue; } else if (token == PP_NEWLINE) { substituted += symbol(); break; } else { substituted += symbol(); } } }
malValuePtr EVAL(malValuePtr ast, malEnvPtr env) { if (!env) { env = replEnv; } while (1) { const malList* list = DYNAMIC_CAST(malList, ast); if (!list || (list->count() == 0)) { return ast->eval(env); } ast = macroExpand(ast, env); list = DYNAMIC_CAST(malList, ast); if (!list || (list->count() == 0)) { return ast->eval(env); } // From here on down we are evaluating a non-empty list. // First handle the special forms. if (const malSymbol* symbol = DYNAMIC_CAST(malSymbol, list->item(0))) { String special = symbol->value(); int argCount = list->count() - 1; if (special == "def!") { checkArgsIs("def!", 2, argCount); const malSymbol* id = VALUE_CAST(malSymbol, list->item(1)); return env->set(id->value(), EVAL(list->item(2), env)); } if (special == "defmacro!") { checkArgsIs("defmacro!", 2, argCount); const malSymbol* id = VALUE_CAST(malSymbol, list->item(1)); malValuePtr body = EVAL(list->item(2), env); const malLambda* lambda = VALUE_CAST(malLambda, body); return env->set(id->value(), mal::macro(*lambda)); } if (special == "do") { checkArgsAtLeast("do", 1, argCount); for (int i = 1; i < argCount; i++) { EVAL(list->item(i), env); } ast = list->item(argCount); continue; // TCO } if (special == "fn*") { checkArgsIs("fn*", 2, argCount); const malSequence* bindings = VALUE_CAST(malSequence, list->item(1)); StringVec params; for (int i = 0; i < bindings->count(); i++) { const malSymbol* sym = VALUE_CAST(malSymbol, bindings->item(i)); params.push_back(sym->value()); } return mal::lambda(params, list->item(2), env); } if (special == "if") { checkArgsBetween("if", 2, 3, argCount); bool isTrue = EVAL(list->item(1), env)->isTrue(); if (!isTrue && (argCount == 2)) { return mal::nilValue(); } ast = list->item(isTrue ? 2 : 3); continue; // TCO } if (special == "let*") { checkArgsIs("let*", 2, argCount); const malSequence* bindings = VALUE_CAST(malSequence, list->item(1)); int count = checkArgsEven("let*", bindings->count()); malEnvPtr inner(new malEnv(env)); for (int i = 0; i < count; i += 2) { const malSymbol* var = VALUE_CAST(malSymbol, bindings->item(i)); inner->set(var->value(), EVAL(bindings->item(i+1), inner)); } ast = list->item(2); env = inner; continue; // TCO } if (special == "macroexpand") { checkArgsIs("macroexpand", 1, argCount); return macroExpand(list->item(1), env); } if (special == "quasiquote") { checkArgsIs("quasiquote", 1, argCount); ast = quasiquote(list->item(1)); continue; // TCO } if (special == "quote") { checkArgsIs("quote", 1, argCount); return list->item(1); } if (special == "try*") { checkArgsIs("try*", 2, argCount); malValuePtr tryBody = list->item(1); const malList* catchBlock = VALUE_CAST(malList, list->item(2)); checkArgsIs("catch*", 2, catchBlock->count() - 1); MAL_CHECK(VALUE_CAST(malSymbol, catchBlock->item(0))->value() == "catch*", "catch block must begin with catch*"); // We don't need excSym at this scope, but we want to check // that the catch block is valid always, not just in case of // an exception. const malSymbol* excSym = VALUE_CAST(malSymbol, catchBlock->item(1)); malValuePtr excVal; try { ast = EVAL(tryBody, env); } catch(String& s) { excVal = mal::string(s); } catch (malEmptyInputException&) { // Not an error, continue as if we got nil ast = mal::nilValue(); } catch(malValuePtr& o) { excVal = o; }; if (excVal) { // we got some exception env = malEnvPtr(new malEnv(env)); env->set(excSym->value(), excVal); ast = catchBlock->item(2); } continue; // TCO } } // Now we're left with the case of a regular list to be evaluated. std::unique_ptr<malValueVec> items(list->evalItems(env)); malValuePtr op = items->at(0); if (const malLambda* lambda = DYNAMIC_CAST(malLambda, op)) { ast = lambda->getBody(); env = lambda->makeEnv(items->begin()+1, items->end()); continue; // TCO } else { return APPLY(op, items->begin()+1, items->end()); } } }
void Preprocessor::preprocess(const QByteArray &filename, Symbols &preprocessed) { currentFilenames.push(filename); preprocessed.reserve(preprocessed.size() + symbols.size()); while (hasNext()) { Token token = next(); switch (token) { case PP_INCLUDE: { int lineNum = symbol().lineNum; QByteArray include; bool local = false; if (test(PP_STRING_LITERAL)) { local = lexem().startsWith('\"'); include = unquotedLexem(); } else continue; until(PP_NEWLINE); // #### stringery QFileInfo fi; if (local) fi.setFile(QFileInfo(QString::fromLocal8Bit(filename.constData())).dir(), QString::fromLocal8Bit(include.constData())); for (int j = 0; j < Preprocessor::includes.size() && !fi.exists(); ++j) { const IncludePath &p = Preprocessor::includes.at(j); if (p.isFrameworkPath) { const int slashPos = include.indexOf('/'); if (slashPos == -1) continue; QByteArray frameworkCandidate = include.left(slashPos); frameworkCandidate.append(".framework/Headers/"); fi.setFile(QString::fromLocal8Bit(QByteArray(p.path + '/' + frameworkCandidate).constData()), QString::fromLocal8Bit(include.mid(slashPos + 1).constData())); } else { fi.setFile(QString::fromLocal8Bit(p.path.constData()), QString::fromLocal8Bit(include.constData())); } // try again, maybe there's a file later in the include paths with the same name // (186067) if (fi.isDir()) { fi = QFileInfo(); continue; } } if (!fi.exists() || fi.isDir()) continue; include = fi.canonicalFilePath().toLocal8Bit(); if (Preprocessor::preprocessedIncludes.contains(include)) continue; Preprocessor::preprocessedIncludes.insert(include); QFile file(QString::fromLocal8Bit(include.constData())); if (!file.open(QFile::ReadOnly)) continue; QByteArray input = file.readAll(); file.close(); if (input.isEmpty()) continue; Symbols saveSymbols = symbols; int saveIndex = index; // phase 1: get rid of backslash-newlines input = cleaned(input); // phase 2: tokenize for the preprocessor symbols = tokenize(input); input.clear(); index = 0; // phase 3: preprocess conditions and substitute macros preprocessed += Symbol(0, MOC_INCLUDE_BEGIN, include); preprocess(include, preprocessed); preprocessed += Symbol(lineNum, MOC_INCLUDE_END, include); symbols = saveSymbols; index = saveIndex; continue; } case PP_DEFINE: { next(IDENTIFIER); QByteArray name = lexem(); Macro macro; macro.isVariadic = false; Token t = next(); if (t == LPAREN) { // we have a function macro macro.isFunction = true; parseDefineArguments(¯o); } else if (t == PP_WHITESPACE) { macro.isFunction = false; } else { error("Moc: internal error"); } int start = index; until(PP_NEWLINE); macro.symbols.reserve(index - start - 1); // remove whitespace where there shouldn't be any: // Before and after the macro, after a # and around ## Token lastToken = HASH; // skip shitespace at the beginning for (int i = start; i < index - 1; ++i) { Token token = symbols.at(i).token; if (token == PP_WHITESPACE || token == WHITESPACE) { if (lastToken == PP_HASH || lastToken == HASH || lastToken == PP_HASHHASH || lastToken == PP_WHITESPACE || lastToken == WHITESPACE) continue; } else if (token == PP_HASHHASH) { if (!macro.symbols.isEmpty() && (lastToken == PP_WHITESPACE || lastToken == WHITESPACE)) macro.symbols.pop_back(); } macro.symbols.append(symbols.at(i)); lastToken = token; } // remove trailing whitespace while (!macro.symbols.isEmpty() && (macro.symbols.last().token == PP_WHITESPACE || macro.symbols.last().token == WHITESPACE)) macro.symbols.pop_back(); if (!macro.symbols.isEmpty()) { if (macro.symbols.first().token == PP_HASHHASH || macro.symbols.last().token == PP_HASHHASH) { error("'##' cannot appear at either end of a macro expansion"); } if (macro.symbols.last().token == HASH || macro.symbols.last().token == PP_HASH) { error("'#' is not followed by a macro parameter"); } } macros.insert(name, macro); continue; } case PP_UNDEF: { next(IDENTIFIER); QByteArray name = lexem(); until(PP_NEWLINE); macros.remove(name); continue; } case PP_IDENTIFIER: { // substitute macros preprocessed += macroExpand(this, symbols, index, symbol().lineNum, true); continue; } case PP_HASH: until(PP_NEWLINE); continue; // skip unknown preprocessor statement case PP_IFDEF: case PP_IFNDEF: case PP_IF: while (!evaluateCondition()) { if (!skipBranch()) break; if (test(PP_ELIF)) { } else { until(PP_NEWLINE); break; } } continue; case PP_ELIF: case PP_ELSE: skipUntilEndif(); // fall through case PP_ENDIF: until(PP_NEWLINE); continue; case PP_NEWLINE: continue; case SIGNALS: case SLOTS: { Symbol sym = symbol(); if (macros.contains("QT_NO_KEYWORDS")) sym.token = IDENTIFIER; else sym.token = (token == SIGNALS ? Q_SIGNALS_TOKEN : Q_SLOTS_TOKEN); preprocessed += sym; } continue; default: break; } preprocessed += symbol(); } currentFilenames.pop(); }
Symbols Preprocessor::macroExpandIdentifier(Preprocessor *that, SymbolStack &symbols, int lineNum, QByteArray *macroName) { Symbol s = symbols.symbol(); // not a macro if (s.token != PP_IDENTIFIER || !that->macros.contains(s) || symbols.dontReplaceSymbol(s.lexem())) { Symbols syms; syms += s; syms.last().lineNum = lineNum; return syms; } const Macro ¯o = that->macros.value(s); *macroName = s.lexem(); Symbols expansion; if (!macro.isFunction) { expansion = macro.symbols; } else { bool haveSpace = false; while (symbols.test(PP_WHITESPACE)) { haveSpace = true; } if (!symbols.test(PP_LPAREN)) { *macroName = QByteArray(); Symbols syms; if (haveSpace) syms += Symbol(lineNum, PP_WHITESPACE); syms += s; syms.last().lineNum = lineNum; return syms; } QList<Symbols> arguments; while (symbols.hasNext()) { Symbols argument; // strip leading space while (symbols.test(PP_WHITESPACE)) {} int nesting = 0; bool vararg = macro.isVariadic && (arguments.size() == macro.arguments.size() - 1); while (symbols.hasNext()) { Token t = symbols.next(); if (t == PP_LPAREN) { ++nesting; } else if (t == PP_RPAREN) { --nesting; if (nesting < 0) break; } else if (t == PP_COMMA && nesting == 0) { if (!vararg) break; } argument += symbols.symbol(); } arguments += argument; if (nesting < 0) break; } // empty VA_ARGS if (macro.isVariadic && arguments.size() == macro.arguments.size() - 1) arguments += Symbols(); if (arguments.size() != macro.arguments.size() && // 0 argument macros are a bit special. They are ok if the // argument is pure whitespace or empty (macro.arguments.size() != 0 || arguments.size() != 1 || !arguments.at(0).isEmpty())) that->error("Macro argument mismatch."); // now replace the macro arguments with the expanded arguments enum Mode { Normal, Hash, HashHash } mode = Normal; for (int i = 0; i < macro.symbols.size(); ++i) { const Symbol &s = macro.symbols.at(i); if (s.token == HASH || s.token == PP_HASHHASH) { mode = (s.token == HASH ? Hash : HashHash); continue; } int index = macro.arguments.indexOf(s); if (mode == Normal) { if (index >= 0) { // each argument undoergoes macro expansion if it's not used as part of a # or ## if (i == macro.symbols.size() - 1 || macro.symbols.at(i + 1).token != PP_HASHHASH) { Symbols arg = arguments.at(index); int idx = 1; expansion += macroExpand(that, arg, idx, lineNum, false); } else { expansion += arguments.at(index); } } else { expansion += s; } } else if (mode == Hash) { if (index < 0) that->error("'#' is not followed by a macro parameter"); const Symbols &arg = arguments.at(index); QByteArray stringified; for (int i = 0; i < arg.size(); ++i) { stringified += arg.at(i).lexem(); } stringified.replace('"', "\\\""); stringified.prepend('"'); stringified.append('"'); expansion += Symbol(lineNum, STRING_LITERAL, stringified); } else if (mode == HashHash) { if (s.token == WHITESPACE) continue; while (expansion.size() && expansion.last().token == PP_WHITESPACE) expansion.pop_back(); Symbol next = s; if (index >= 0) { const Symbols &arg = arguments.at(index); if (arg.size() == 0) { mode = Normal; continue; } next = arg.at(0); } if (!expansion.isEmpty() && expansion.last().token == s.token) { Symbol last = expansion.last(); expansion.pop_back(); if (last.token == STRING_LITERAL || s.token == STRING_LITERAL) that->error("Can't concatenate non identifier tokens"); QByteArray lexem = last.lexem() + next.lexem(); expansion += Symbol(lineNum, last.token, lexem); } else { expansion += next; } if (index >= 0) { const Symbols &arg = arguments.at(index); for (int i = 1; i < arg.size(); ++i) expansion += arg.at(i); } } mode = Normal; } if (mode != Normal) that->error("'#' or '##' found at the end of a macro argument"); } return expansion; }