// slang-fiddle-scrape.cpp #include "slang-fiddle-scrape.h" #include "core/slang-string-util.h" #include "slang-fiddle-script.h" namespace fiddle { // Parser struct Parser { private: DiagnosticSink& _sink; List _tokens; TokenWithTrivia const* _cursor = nullptr; TokenWithTrivia const* _end = nullptr; LogicalModule* _module = nullptr; ContainerDecl* _currentParentDecl = nullptr; struct WithParentDecl { public: WithParentDecl(Parser* outer, ContainerDecl* decl) { _outer = outer; _saved = outer->_currentParentDecl; outer->_currentParentDecl = decl; } ~WithParentDecl() { _outer->_currentParentDecl = _saved; } private: Parser* _outer; ContainerDecl* _saved; }; public: Parser(DiagnosticSink& sink, List const& tokens, LogicalModule* module) : _sink(sink), _tokens(tokens), _module(module) { _cursor = tokens.begin(); _end = tokens.end() - 1; } bool _isRecovering = false; TokenWithTrivia const& peek() { return *_cursor; } SourceLoc const& peekLoc() { return peek().getLoc(); } TokenType peekType() { return peek().getType(); } TokenWithTrivia read() { _isRecovering = false; if (peekType() != TokenType::EndOfFile) return *_cursor++; else return *_cursor; } TokenWithTrivia expect(TokenType expected) { if (peekType() == expected) { return read(); } if (!_isRecovering) { _sink.diagnose(peekLoc(), fiddle::Diagnostics::unexpected, peekType(), expected); } else { // TODO: need to skip until we see what we expected... _sink.diagnose(SourceLoc(), fiddle::Diagnostics::internalError); } return TokenWithTrivia(); } TokenWithTrivia expect(const char* expected) { if (peekType() == TokenType::Identifier) { if (peek().getContent() == expected) { return read(); } } if (!_isRecovering) { _sink.diagnose(peekLoc(), fiddle::Diagnostics::unexpected, peekType(), expected); } else { // TODO: need to skip until we see what we expected... _sink.diagnose(SourceLoc(), fiddle::Diagnostics::internalError); } return TokenWithTrivia(); } bool advanceIf(TokenType type) { if (peekType() == type) { read(); return true; } return false; } bool advanceIf(char const* name) { if (peekType() == TokenType::Identifier) { if (peek().getContent() == name) { read(); return true; } } return false; } RefPtr parseCppSimpleExpr() { switch (peekType()) { case TokenType::Identifier: { auto nameToken = expect(TokenType::Identifier); return new NameExpr(nameToken); } break; case TokenType::IntegerLiteral: case TokenType::StringLiteral: { auto token = read(); return new LiteralExpr(token); } break; case TokenType::LParent: { expect(TokenType::LParent); auto inner = parseCppExpr(); expect(TokenType::RParent); // TODO: handle a cast, in the case that the lookahead // implies we should parse one... switch (peekType()) { case TokenType::Identifier: case TokenType::LParent: { auto arg = parseCppExpr(); return inner; } break; default: return inner; } } break; default: expect(TokenType::Identifier); _sink.diagnose(SourceLoc(), fiddle::Diagnostics::internalError); return nullptr; } return nullptr; } RefPtr parseCppExpr() { auto base = parseCppSimpleExpr(); for (;;) { switch (peekType()) { default: return base; case TokenType::OpMul: { expect(TokenType::OpMul); switch (peekType()) { default: // treat as introducting a pointer type return base; } } break; case TokenType::Scope: { expect(TokenType::Scope); auto memberName = expect(TokenType::Identifier); base = new StaticMemberRef(base, memberName); } break; case TokenType::LParent: { // TODO: actually parse this! readBalanced(); } break; case TokenType::OpLess: { auto specialize = RefPtr(new SpecializeExpr()); specialize->base = base; // Okay, we have a template application here. expect(TokenType::OpLess); specialize->args = parseCppTemplateArgs(); parseGenericCloser(); base = specialize; } break; } } } RefPtr parseCppSimpleTypeSpecififer() { while (advanceIf("const") || advanceIf("static")) ; switch (peekType()) { case TokenType::Identifier: { auto nameToken = expect(TokenType::Identifier); return new NameExpr(nameToken); } break; default: expect(TokenType::Identifier); _sink.diagnose(SourceLoc(), fiddle::Diagnostics::internalError); return nullptr; } } List> parseCppTemplateArgs() { List> args; for (;;) { switch (peekType()) { case TokenType::OpGeq: case TokenType::OpGreater: case TokenType::OpRsh: case TokenType::EndOfFile: return args; } auto arg = parseCppExpr(); if (arg) args.add(arg); if (!advanceIf(TokenType::Comma)) return args; } } void parseGenericCloser() { if (advanceIf(TokenType::OpGreater)) return; if (peekType() == TokenType::OpRsh) { peek().setType(TokenType::OpGreater); return; } expect(TokenType::OpGreater); } RefPtr parseCppTypeSpecifier() { auto result = parseCppSimpleTypeSpecififer(); for (;;) { switch (peekType()) { default: return result; case TokenType::Scope: { expect(TokenType::Scope); auto memberName = expect(TokenType::Identifier); auto memberRef = RefPtr(new StaticMemberRef(result, memberName)); result = memberRef; } break; case TokenType::OpLess: { auto specialize = RefPtr(new SpecializeExpr()); specialize->base = result; // Okay, we have a template application here. expect(TokenType::OpLess); specialize->args = parseCppTemplateArgs(); parseGenericCloser(); result = specialize; } break; } } } struct UnwrappedDeclarator { RefPtr type; TokenWithTrivia nameToken; }; UnwrappedDeclarator unwrapDeclarator(RefPtr declarator, RefPtr type) { if (!declarator) { UnwrappedDeclarator result; result.type = type; return result; } if (auto ptrDeclarator = as(declarator)) { return unwrapDeclarator(ptrDeclarator->base, new PtrType(type)); } else if (auto nameDeclarator = as(declarator)) { UnwrappedDeclarator result; result.type = type; result.nameToken = nameDeclarator->nameToken; return result; } else { _sink.diagnose(SourceLoc(), Diagnostics::unexpected, "declarator type", "known"); return UnwrappedDeclarator(); } } RefPtr parseCppType() { auto typeSpecifier = parseCppTypeSpecifier(); auto declarator = parseCppDeclarator(); return unwrapDeclarator(declarator, typeSpecifier).type; } RefPtr parseCppBase() { // TODO: allow `private` and `protected` // TODO: insert a default `public` keyword, if one is missing... advanceIf("public"); return parseCppType(); } void parseCppAggTypeDecl(RefPtr decl) { decl->mode = Mode::Cpp; // read the type name decl->nameToken = expect(TokenType::Identifier); // Read the bases clause. // // TODO: handle multiple bases... // if (advanceIf(TokenType::Colon)) { decl->directBaseType = parseCppBase(); } expect(TokenType::LBrace); addDecl(decl); WithParentDecl withParent(this, decl); // We expect any `FIDDLE()`-marked aggregate type declaration to start // with a `FIDDLE(...)` or `FIDDLE(myFunc(a,b,c))` invocation, so that // there is a suitable insertion point for the expansion step or the // user has specific a custom step // { auto saved = _cursor; bool found = peekFiddleEllipsisInvocation() || peekFiddleLuaCall(); _cursor = saved; if (!found) { _sink.diagnose( peekLoc(), fiddle::Diagnostics::expectedFiddleEllipsisInvocation, decl->nameToken.getContent()); } } parseCppDecls(decl); expect(TokenType::RBrace); } bool peekFiddleEllipsisInvocation() { if (!advanceIf("FIDDLE")) return false; if (!advanceIf(TokenType::LParent)) return false; if (!advanceIf(TokenType::Ellipsis)) return false; return true; } bool peekFiddleLuaCall() { auto saved = _cursor; const bool found = advanceIf(TokenType::Identifier) && (peekType() == TokenType::LParent || peekType() == TokenType::LBrace); _cursor = saved; return found; } RefPtr parseCppSimpleDeclarator() { switch (peekType()) { case TokenType::Identifier: { auto nameToken = expect(TokenType::Identifier); return RefPtr(new NameDeclarator(nameToken)); } default: return nullptr; } } RefPtr parseCppPostfixDeclarator() { auto result = parseCppSimpleDeclarator(); for (;;) { switch (peekType()) { default: return result; case TokenType::LBracket: readBalanced(); return result; } } return result; } RefPtr parseCppDeclarator() { while (advanceIf("const") || advanceIf("static")) ; if (advanceIf(TokenType::OpMul)) { auto base = parseCppDeclarator(); return RefPtr(new PtrDeclarator(base)); } else { return parseCppPostfixDeclarator(); } } void parseCppDeclaratorBasedDecl(List> const& fiddleModifiers) { auto typeSpecifier = parseCppTypeSpecifier(); auto declarator = parseCppDeclarator(); auto unwrapped = unwrapDeclarator(declarator, typeSpecifier); auto varDecl = RefPtr(new VarDecl()); varDecl->nameToken = unwrapped.nameToken; varDecl->type = unwrapped.type; addDecl(varDecl); if (advanceIf(TokenType::OpAssign)) { varDecl->initExpr = parseCppExpr(); } expect(TokenType::Semicolon); } void parseNativeDeclaration(List> const& fiddleModifiers) { auto keyword = peek(); if (advanceIf("namespace")) { RefPtr namespaceDecl = new PhysicalNamespaceDecl(); namespaceDecl->modifiers = fiddleModifiers; // read the namespace name namespaceDecl->nameToken = expect(TokenType::Identifier); expect(TokenType::LBrace); addDecl(namespaceDecl); WithParentDecl withNamespace(this, namespaceDecl); parseCppDecls(namespaceDecl); expect(TokenType::RBrace); } else if (advanceIf("class")) { auto decl = RefPtr(new ClassDecl()); decl->modifiers = fiddleModifiers; parseCppAggTypeDecl(decl); } else if (advanceIf("struct")) { auto decl = RefPtr(new StructDecl()); decl->modifiers = fiddleModifiers; parseCppAggTypeDecl(decl); } else if (peekType() == TokenType::Identifier) { // try to parse a declarator-based declaration // (which for now is probably a field); // parseCppDeclaratorBasedDecl(fiddleModifiers); } else { _sink.diagnose(peekLoc(), fiddle::Diagnostics::unexpected, peekType(), "OTHER"); _sink.diagnose(SourceLoc(), fiddle::Diagnostics::internalError); } } List> parseFiddleModifiers() { List> modifiers; for (;;) { switch (peekType()) { default: return modifiers; case TokenType::Identifier: if (advanceIf("abstract")) { modifiers.add(new AbstractModifier()); } else if (advanceIf("hidden")) { modifiers.add(new HiddenModifier()); } else { return modifiers; } break; case TokenType::LBrace: { const auto b = read(); StringBuilder sb; sb << b.getContent(); for (int i = 0; i < b.getSkipCount(); ++i) sb << read().getContent() << " "; modifiers.add(new TableModifier(std::move(sb))); } break; } } return modifiers; } RefPtr parseFiddlePrimaryExpr() { switch (peekType()) { case TokenType::Identifier: return new NameExpr(read()); case TokenType::LParent: { expect(TokenType::LParent); auto expr = parseFiddleExpr(); expect(TokenType::RParent); return expr; } default: expect(TokenType::Identifier); return nullptr; } } List> parseFiddleArgs() { List> args; for (;;) { switch (peekType()) { case TokenType::RBrace: case TokenType::RBracket: case TokenType::RParent: case TokenType::EndOfFile: return args; default: break; } auto arg = parseFiddleExpr(); args.add(arg); if (!advanceIf(TokenType::Comma)) return args; } } RefPtr parseFiddlePostifxExpr() { auto result = parseFiddlePrimaryExpr(); for (;;) { switch (peekType()) { default: return result; case TokenType::Dot: { expect(TokenType::Dot); auto memberName = expect(TokenType::Identifier); result = new MemberExpr(result, memberName); } break; case TokenType::LParent: { expect(TokenType::LParent); auto args = parseFiddleArgs(); expect(TokenType::RParent); result = new CallExpr(result, args); } break; } } } RefPtr parseFiddleExpr() { return parseFiddlePostifxExpr(); } RefPtr parseFiddleTypeExpr() { return parseFiddleExpr(); } void parseFiddleAggTypeDecl(RefPtr decl) { decl->mode = Mode::Fiddle; // read the type name decl->nameToken = expect(TokenType::Identifier); // Read the bases clause. if (advanceIf(TokenType::Colon)) { decl->directBaseType = parseFiddleTypeExpr(); } addDecl(decl); WithParentDecl withParent(this, decl); if (advanceIf(TokenType::LBrace)) { parseOptionalFiddleModeDecls(); expect(TokenType::RBrace); } else { expect(TokenType::Semicolon); } } void parseFiddleModeDecl(List> modifiers) { if (advanceIf("class")) { auto decl = RefPtr(new ClassDecl()); decl->modifiers = modifiers; parseFiddleAggTypeDecl(decl); } else { _sink.diagnose( peekLoc(), Diagnostics::unexpected, peekType(), "fiddle-mode declaration"); } } void parseFiddleModeDecl() { auto modifiers = parseFiddleModifiers(); parseFiddleModeDecl(modifiers); } void parseOptionalFiddleModeDecls() { for (;;) { switch (peekType()) { case TokenType::RParent: case TokenType::RBrace: case TokenType::RBracket: case TokenType::EndOfFile: return; } parseFiddleModeDecl(); } } void parseFiddleModeDecls(List> modifiers) { parseFiddleModeDecl(modifiers); parseOptionalFiddleModeDecls(); } void parseFiddleNode() { auto fiddleToken = expect("FIDDLE"); // We will capture the token at this invocation site, // because later on we will generate a macro that // this invocation will expand into. // auto fiddleMacroInvocation = RefPtr(new FiddleMacroInvocation()); fiddleMacroInvocation->fiddleToken = fiddleToken; addDecl(fiddleMacroInvocation); // The `FIDDLE` keyword can be followed by parentheses around a bunch of // fiddle-mode modifiers. List> fiddleModifiers; if (advanceIf(TokenType::LParent)) { if (advanceIf(TokenType::Ellipsis)) { // A `FIDDLE(...)` invocation is a hook for // our expansion step to insert the generated // declarations that go into the body of // the parent declaration. fiddleMacroInvocation->node = _currentParentDecl; expect(TokenType::RParent); return; } // We start off by parsing optional modifiers fiddleModifiers = parseFiddleModifiers(); if (peekType() != TokenType::RParent) { if (peekFiddleLuaCall()) { StringBuilder sb; const auto f = expect(TokenType::Identifier); const auto b = read(); sb << f.getContent() << b.getContent(); for (int i = 0; i < b.getSkipCount(); ++i) sb << read().getContent() << " "; auto fiddleLuaCall = RefPtr(new FiddleLuaCallInvocation()); fiddleLuaCall->fiddleToken = fiddleToken; fiddleLuaCall->parentDecl = _currentParentDecl; fiddleLuaCall->callString = std::move(sb); addDecl(fiddleLuaCall); expect(TokenType::RParent); return; } else { // In this case we are expecting a fiddle-mode declaration // to appear, in which case we will allow any number of full // fiddle-mode declarations, but won't expect a C++-mode // declaration to follow. // TODO: We should associate these declarations // as children of the `FiddleMacroInvocation`, // so that they can be emitted as part of its // expansion (if we decide to make more use // of the `FIDDLE()` approach...). parseFiddleModeDecls(fiddleModifiers); expect(TokenType::RParent); return; } } expect(TokenType::RParent); } else { // TODO: diagnose this! } // Any tokens from here on are expected to be in C++-mode parseNativeDeclaration(fiddleModifiers); } void addDecl(ContainerDecl* parentDecl, Decl* memberDecl) { if (!memberDecl) return; parentDecl->members.add(memberDecl); auto physicalParent = as(parentDecl); if (!physicalParent) return; auto logicalParent = physicalParent->logicalVersion; if (!logicalParent) return; if (auto physicalNamespace = as(memberDecl)) { auto namespaceName = physicalNamespace->nameToken.getContent(); auto logicalNamespace = findDecl(logicalParent, namespaceName); if (!logicalNamespace) { logicalNamespace = new LogicalNamespace(); logicalNamespace->nameToken = physicalNamespace->nameToken; logicalParent->members.add(logicalNamespace); logicalParent->mapNameToMember.add(namespaceName, logicalNamespace); } physicalNamespace->logicalVersion = logicalNamespace; } else { logicalParent->members.add(memberDecl); } } void addDecl(RefPtr decl) { addDecl(_currentParentDecl, decl); } void parseCppDecls(RefPtr parentDecl) { for (;;) { switch (peekType()) { case TokenType::EndOfFile: case TokenType::RBrace: case TokenType::RBracket: case TokenType::RParent: return; default: break; } parseCppDecl(); } } void readBalanced() { Count skipCount = read().getSkipCount(); _cursor = _cursor + skipCount; } void parseCppDecl() { // We consume raw tokens until we see something // that ought to start a reflected/extracted declaration. // for (;;) { switch (peekType()) { default: { readBalanced(); continue; } case TokenType::RBrace: case TokenType::RBracket: case TokenType::RParent: case TokenType::EndOfFile: return; case TokenType::Identifier: break; case TokenType::Pound: // a `#` means we have run into a preprocessor directive // (or, somehow, we are already *inside* one...). // // We don't want to try to intercept anything to do with // these lines, so we will read until the next end-of-line. // read(); while (!(peek().getToken().flags & TokenFlag::AtStartOfLine)) { if (peekType() == TokenType::EndOfFile) break; read(); } continue; } // Okay, we have an identifier, but is its name // one that we want to pay attention to? // // auto name = peek().getContent(); if (name == "FIDDLE") { // If the `FIDDLE` is the first token we are seeing, then we will // start parsing a construct in fiddle-mode: // parseFiddleNode(); } else { // If the name isn't one we recognize, then // we are just reading raw tokens as usual. // readBalanced(); continue; } } } RefPtr parseSourceUnit() { RefPtr sourceUnit = new SourceUnit(); sourceUnit->logicalVersion = _module; WithParentDecl withSourceUnit(this, sourceUnit); while (_cursor != _end) { parseCppDecl(); switch (peekType()) { default: break; case TokenType::RBrace: case TokenType::RBracket: case TokenType::RParent: case TokenType::EndOfFile: read(); break; } } read(); return sourceUnit; } }; // Check struct CheckContext { private: DiagnosticSink& sink; public: CheckContext(DiagnosticSink& sink) : sink(sink) { } void checkModule(LogicalModule* module) { checkMemberDecls(module); } private: struct Scope { public: Scope(ContainerDecl* containerDecl, Scope* outer) : containerDecl(containerDecl), outer(outer) { } ContainerDecl* containerDecl = nullptr; Scope* outer = nullptr; }; Scope* currentScope = nullptr; struct WithScope : Scope { WithScope(CheckContext* context, ContainerDecl* containerDecl) : Scope(containerDecl, context->currentScope) , _context(context) , _saved(context->currentScope) { context->currentScope = this; } ~WithScope() { _context->currentScope = _saved; } private: CheckContext* _context = nullptr; Scope* _saved = nullptr; }; // void checkDecl(Decl* decl) { if (auto aggTypeDecl = as(decl)) { checkTypeExprInPlace(aggTypeDecl->directBaseType); if (auto baseType = aggTypeDecl->directBaseType) { if (auto baseDeclRef = as(baseType)) { auto baseDecl = baseDeclRef->decl; if (auto baseAggTypeDecl = as(baseDecl)) { baseAggTypeDecl->directSubTypeDecls.add(aggTypeDecl); } } } checkMemberDecls(aggTypeDecl); } else if (auto namespaceDecl = as(decl)) { checkMemberDecls(namespaceDecl); } else if (auto varDecl = as(decl)) { // Note: for now we aren't trying to check the type // or the initial-value expression of a field. } else if (as(decl)) { } else if (as(decl)) { } else { sink.diagnose(SourceLoc(), Diagnostics::unexpected, "case in checkDecl", "known type"); } } void checkMemberDecls(ContainerDecl* containerDecl) { WithScope moduleScope(this, containerDecl); for (auto memberDecl : containerDecl->members) { checkDecl(memberDecl); } } void checkTypeExprInPlace(RefPtr& ioTypeExpr) { if (!ioTypeExpr) return; ioTypeExpr = checkTypeExpr(ioTypeExpr); } RefPtr checkTypeExpr(Expr* expr) { return checkExpr(expr); } RefPtr checkExpr(Expr* expr) { if (auto nameExpr = as(expr)) { return lookUp(nameExpr->nameToken.getContent()); } else { sink.diagnose(SourceLoc(), Diagnostics::unexpected, "case in checkExpr", "known type"); return nullptr; } } RefPtr lookUp(UnownedStringSlice const& name) { for (auto scope = currentScope; scope; scope = scope->outer) { auto containerDecl = scope->containerDecl; // TODO: accelerate lookup with a dictionary on the container... for (auto memberDecl : containerDecl->members) { if (memberDecl->nameToken.getContent() == name) { return new DirectDeclRef(memberDecl); } } } sink.diagnose(SourceLoc(), Diagnostics::undefinedIdentifier, name); return nullptr; } }; void push(lua_State* L, Val* val); // Emit struct EmitContext { private: SourceManager& _sourceManager; RefPtr _module; StringBuilder& _builder; DiagnosticSink& _sink; public: EmitContext( StringBuilder& builder, DiagnosticSink& sink, SourceManager& sourceManager, LogicalModule* module) : _builder(builder), _sourceManager(sourceManager), _module(module), _sink(sink) { } void emitMacrosRec(Decl* decl) { emitMacrosForDecl(decl); if (auto container = as(decl)) { for (auto member : container->members) emitMacrosRec(member); } } private: void emitMacrosForDecl(Decl* decl) { if (auto fiddleMacroInvocation = as(decl)) { emitMacroForFiddleInvocation(fiddleMacroInvocation); } else if (const auto fiddleLuaCallInvocation = as(decl)) { emitMacroForFiddleLuaCallInvocation(fiddleLuaCallInvocation); } else { // do nothing with most decls } } #define MACRO_LINE_ENDING " \\\n" void emitMacroForFiddleInvocationPreamble(const TokenWithTrivia& fiddleToken) { const auto loc = fiddleToken.getLoc(); const auto humaneLoc = _sourceManager.getHumaneLoc(loc); const auto lineNumber = humaneLoc.line; // Un-define the old `FIDDLE_#` macro for the // given line number, since this file might // be pulling in another generated header // via one of its dependencies. // _builder.append("#ifdef FIDDLE_"); _builder.append(lineNumber); _builder.append("\n#undef FIDDLE_"); _builder.append(lineNumber); _builder.append("\n#endif\n"); _builder.append("#define FIDDLE_"); _builder.append(lineNumber); _builder.append("(...)"); _builder.append(MACRO_LINE_ENDING); } void emitMacroForFiddleInvocationPostamble() { _builder.append("/* end */\n\n"); } void emitMacroForFiddleLuaCallInvocation(FiddleLuaCallInvocation* fiddleInvocation) { _builder.append("/*\n"); _builder.append(fiddleInvocation->callString); _builder.append("\n*/\n"); emitMacroForFiddleInvocationPreamble(fiddleInvocation->fiddleToken); const auto file = _sourceManager.getHumaneLoc(fiddleInvocation->fiddleToken.getLoc()).pathInfo.getName(); StringBuilder sb; sb << "require(\"" << file << ".lua\")." << fiddleInvocation->callString; // Create the fiddle table const auto L = getLuaState(); lua_newtable(L); push(L, fiddleInvocation->parentDecl); lua_setfield(L, -2, "current_decl"); lua_setglobal(L, "fiddle"); const auto output = evaluateLuaExpression( fiddleInvocation->fiddleToken.getLoc(), file, sb.produceString(), &_sink); // Deregister the fiddle table lua_pushnil(L); lua_setglobal(L, "fiddle"); _builder.append(StringUtil::replaceAll( output.getUnownedSlice(), UnownedStringSlice("\n"), UnownedStringSlice(MACRO_LINE_ENDING))); _builder.append(MACRO_LINE_ENDING); emitMacroForFiddleInvocationPostamble(); } void emitMacroForFiddleInvocation(FiddleMacroInvocation* fiddleInvocation) { emitMacroForFiddleInvocationPreamble(fiddleInvocation->fiddleToken); auto decl = as(fiddleInvocation->node); if (decl) { if (auto base = decl->directBaseType) { _builder.append("private: typedef "); emitTypedDecl(base, "Super"); _builder.append(";" MACRO_LINE_ENDING); } if (decl->isSubTypeOf("NodeBase")) { _builder.append("friend class ::Slang::ASTBuilder;" MACRO_LINE_ENDING); _builder.append("friend struct ::Slang::SyntaxClassInfo;" MACRO_LINE_ENDING); _builder.append("public: static const ::Slang::SyntaxClassInfo " "kSyntaxClassInfo;" MACRO_LINE_ENDING); _builder.append("public: static constexpr ASTNodeType kType = ASTNodeType::"); _builder.append(decl->nameToken.getContent()); _builder.append(";" MACRO_LINE_ENDING); if (decl->findModifier()) { _builder.append("protected: "); } else { _builder.append("public: "); } _builder.append(decl->nameToken.getContent()); _builder.append("() {}" MACRO_LINE_ENDING); } _builder.append("public:" MACRO_LINE_ENDING); } emitMacroForFiddleInvocationPostamble(); } void emitTypedDecl(Expr* expr, const char* name) { if (auto declRef = as(expr)) { _builder.append(declRef->decl->nameToken.getContent()); _builder.append(" "); _builder.append(name); } } #if 0 void emitLineDirective(Token const& lexeme) { SourceLoc loc = lexeme.getLoc(); auto humaneLoc = _sourceManager.getHumaneLoc(loc); _builder.append("\n#line "); _builder.append(humaneLoc.line); _builder.append(" \""); for (auto c : humaneLoc.pathInfo.getName()) { if (c == '\\') _builder.append("\\\\"); else _builder.append(c); } _builder.append("\"\n"); } void emitLineDirective(TokenWithTrivia const& token) { if (token.getLeadingTrivia().getCount() != 0) emitLineDirective(token.getLeadingTrivia()[0]); else emitLineDirective(token.getToken()); } void emitLineDirective(RawNode* node) { emitLineDirective(node->tokens[0]); } void emitTrivia(List const& trivia) { for (auto trivium : trivia) _builder.append(trivium.getContent()); } void emitRawNode(RawNode* rawNode) { for (auto token : rawNode->tokens) { emitTrivia(token.getLeadingTrivia()); _builder.append(token.getContent()); emitTrivia(token.getTrailingTrivia()); } } void emitTopLevelNode(Decl* node) { if (!node) return; if (node->findModifier()) return; if (auto rawNode = as(node)) { // TODO: should emit a `#line` to point back to // the original source file... emitLineDirective(rawNode); emitRawNode(rawNode); } else if (auto decl = as(node)) { for (auto child : decl->members) { emitTopLevelNode(child); } } else if (auto decl = as(node)) { emitExtraMembersForAggTypeDecl(decl); for (auto child : decl->members) { emitTopLevelNode(child); } } else if (auto varDecl = as(node)) { // Note: nothing to be done here... } else { _sink.diagnose(SourceLoc(), fiddle::Diagnostics::unexpected, "emitTopLevelNode", "unhandled case"); } } void emitSourceUnit(SourceUnit* sourceUnit) { for (auto node : sourceUnit->members) { emitTopLevelNode(node); } } private: #endif }; Decl* findDecl_(ContainerDecl* outerDecl, UnownedStringSlice const& name) { for (auto memberDecl : outerDecl->members) { if (memberDecl->nameToken.getContent() == name) return memberDecl; } return nullptr; } bool AggTypeDecl::isSubTypeOf(char const* name) { Decl* decl = this; while (decl) { if (decl->nameToken.getContent() == UnownedTerminatedStringSlice(name)) { return true; } auto aggType = as(decl); if (!aggType) break; auto baseTypeExpr = aggType->directBaseType; if (!baseTypeExpr) break; auto declRef = as(baseTypeExpr); if (!declRef) break; decl = declRef->decl; } return false; } bool isTrivia(TokenType lexemeType) { switch (lexemeType) { default: return false; case TokenType::LineComment: case TokenType::BlockComment: case TokenType::NewLine: case TokenType::WhiteSpace: return true; } } List collectTokensWithTrivia(TokenList const& lexemes) { TokenReader reader(lexemes); List allTokensWithTrivia; for (;;) { RefPtr currentTokenWithTriviaNode = new TokenWithTriviaNode(); TokenWithTrivia currentTokenWithTrivia = currentTokenWithTriviaNode; allTokensWithTrivia.add(currentTokenWithTrivia); while (isTrivia(reader.peekTokenType())) { auto trivia = reader.advanceToken(); currentTokenWithTriviaNode->leadingTrivia.add(trivia); } auto token = reader.advanceToken(); currentTokenWithTriviaNode->token = token; if (token.type == TokenType::EndOfFile) return allTokensWithTrivia; while (isTrivia(reader.peekTokenType())) { auto trivia = reader.advanceToken(); currentTokenWithTriviaNode->trailingTrivia.add(trivia); if (trivia.type == TokenType::NewLine) break; } } } void readTokenTree(List const& tokens, Index& ioIndex); void readBalancedToken(List const& tokens, Index& ioIndex, TokenType closeType) { auto open = tokens[ioIndex++]; auto openNode = (TokenWithTriviaNode*)open; Index startIndex = ioIndex; for (;;) { auto token = tokens[ioIndex]; if (token.getType() == closeType) { ioIndex++; break; } switch (token.getType()) { default: readTokenTree(tokens, ioIndex); continue; case TokenType::RBrace: case TokenType::RBracket: case TokenType::RParent: case TokenType::EndOfFile: break; } break; } openNode->skipCount = ioIndex - startIndex; } void readTokenTree(List const& tokens, Index& ioIndex) { switch (tokens[ioIndex].getType()) { default: ioIndex++; return; case TokenType::LBrace: return readBalancedToken(tokens, ioIndex, TokenType::RBrace); case TokenType::LBracket: return readBalancedToken(tokens, ioIndex, TokenType::RBracket); case TokenType::LParent: return readBalancedToken(tokens, ioIndex, TokenType::RParent); } } void matchBalancedTokens(List tokens) { Index index = 0; for (;;) { auto& token = tokens[index]; switch (token.getType()) { case TokenType::EndOfFile: return; default: readTokenTree(tokens, index); break; case TokenType::RBrace: case TokenType::RBracket: case TokenType::RParent: // error!!! index++; break; } } } bool findOutputFileIncludeDirective(List tokens, String outputFileName) { auto cursor = tokens.begin(); auto end = tokens.end() - 1; while (cursor != end) { if (cursor->getType() != TokenType::Pound) { cursor++; continue; } cursor++; if (cursor->getContent() != "include") continue; cursor++; if (cursor->getType() != TokenType::StringLiteral) continue; auto includedFileName = getStringLiteralTokenValue(cursor->getToken()); if (includedFileName == outputFileName) return true; } return false; } RefPtr parseSourceUnit( SourceView* inputSourceView, LogicalModule* logicalModule, NamePool* namePool, DiagnosticSink* sink, SourceManager* sourceManager, String outputFileName) { Lexer lexer; // We suppress any diagnostics that might get emitted during lexing, // so that we can ignore any files we don't understand. // DiagnosticSink lexerSink; lexer.initialize(inputSourceView, &lexerSink, namePool, sourceManager->getMemoryArena()); auto inputTokens = lexer.lexAllTokens(); auto tokensWithTrivia = collectTokensWithTrivia(inputTokens); matchBalancedTokens(tokensWithTrivia); Parser parser(*sink, tokensWithTrivia, logicalModule); auto sourceUnit = parser.parseSourceUnit(); // As a quick validation check, if the source file had // any `FIDDLE()` invocations in it, then we check to // make sure it also has a `#include` of the corresponding // output file name... if (hasAnyFiddleInvocations(sourceUnit)) { if (!findOutputFileIncludeDirective(tokensWithTrivia, outputFileName)) { sink->diagnose( inputSourceView->getRange().begin, fiddle::Diagnostics::expectedIncludeOfOutputHeader, outputFileName); } } return sourceUnit; } void push(lua_State* L, Val* val); void push(lua_State* L, UnownedStringSlice const& text) { lua_pushlstring(L, text.begin(), text.getLength()); } template void push(lua_State* L, List const& values) { // Note: Lua tables are naturally indexed starting at 1. Index nextIndex = 1; lua_newtable(L); for (auto value : values) { Index index = nextIndex++; push(L, value); lua_seti(L, -2, index); } } List> getDirectSubclasses(AggTypeDecl* decl) { List> result; for (auto subclass : decl->directSubTypeDecls) result.add(subclass); return result; } void getAllSubclasses(AggTypeDecl* decl, List>& ioSubclasses) { ioSubclasses.add(decl); for (auto subclass : decl->directSubTypeDecls) getAllSubclasses(subclass, ioSubclasses); } List> getAllSubclasses(AggTypeDecl* decl) { List> result; getAllSubclasses(decl, result); return result; } int _toStringVal(lua_State* L) { Val* val = (Val*)lua_touserdata(L, 1); if (auto directDeclRef = as(val)) { val = directDeclRef->decl; } if (auto decl = as(val)) { push(L, decl->nameToken.getContent()); return 1; } lua_pushfstring(L, "fiddle::Val @ 0x%p", val); return 1; } int _indexVal(lua_State* L) { Val* val = (Val*)lua_touserdata(L, 1); char const* name = lua_tostring(L, 2); // If we have some user data attached to this declaration, index that if (auto decl = as(val)) { if (auto tableModifier = decl->findModifier()) { // Check if we have a cached table if (tableModifier->tableRef == LUA_NOREF) { // Evaluate the table string and cache it std::string tableCode = "return " + std::string(tableModifier->tableSource.getBuffer()); if (luaL_dostring(L, tableCode.c_str()) == LUA_OK) { // Store the table in the registry tableModifier->tableRef = luaL_ref(L, LUA_REGISTRYINDEX); } else { // Handle error - pop error message and continue lua_pop(L, 1); } } // If we have a cached table, try to index it if (tableModifier->tableRef != LUA_NOREF) { // Get the cached table from registry lua_rawgeti(L, LUA_REGISTRYINDEX, tableModifier->tableRef); // Index the table with the requested name lua_pushstring(L, name); lua_gettable(L, -2); // Remove the table from stack, leaving just the result lua_remove(L, -2); // Check if we found something if (!lua_isnil(L, -1)) { return 1; } else { lua_pop(L, 1); // Pop the nil // Fall through to check other properties } } } } if (auto containerDecl = as(val)) { for (auto m : containerDecl->members) { if (m->nameToken.getContent() == UnownedTerminatedStringSlice(name)) { push(L, m); return 1; } } } if (auto aggTypeDecl = as(val)) { if (strcmp(name, "directSubclasses") == 0) { auto value = getDirectSubclasses(aggTypeDecl); push(L, value); return 1; } if (strcmp(name, "subclasses") == 0) { auto value = getAllSubclasses(aggTypeDecl); push(L, value); return 1; } if (strcmp(name, "directSuperClass") == 0) { push(L, aggTypeDecl->directBaseType); return 1; } } if (auto aggTypeDecl = as(val)) { if (strcmp(name, "directFields") == 0) { List> fields; for (auto m : aggTypeDecl->members) { if (auto f = as(m)) fields.add(f); } push(L, fields); return 1; } } if (auto decl = as(val)) { if (strcmp(name, "isAbstract") == 0) { lua_pushboolean(L, decl->findModifier() != nullptr); return 1; } if (strcmp(name, "getDebugVisType") == 0) { auto aggTypeDecl = as(decl); if (aggTypeDecl) { if (aggTypeDecl->isSubTypeOf("Decl")) lua_pushstring(L, "SyntaxClassInfoDebugVisType::Decl"); else if (aggTypeDecl->isSubTypeOf("Expr")) lua_pushstring(L, "SyntaxClassInfoDebugVisType::Expr"); else if (aggTypeDecl->isSubTypeOf("Modifier")) lua_pushstring(L, "SyntaxClassInfoDebugVisType::Modifier"); else if (aggTypeDecl->isSubTypeOf("Stmt")) lua_pushstring(L, "SyntaxClassInfoDebugVisType::Stmt"); else if (aggTypeDecl->isSubTypeOf("Val")) lua_pushstring(L, "SyntaxClassInfoDebugVisType::Val"); else if (aggTypeDecl->isSubTypeOf("Scope")) lua_pushstring(L, "SyntaxClassInfoDebugVisType::Scope"); else lua_pushstring(L, "SyntaxClassInfoDebugVisType::Unknown"); } else lua_pushstring(L, "SyntaxClassInfoDebugVisType::Unknown"); return 1; } } if (auto varDecl = as(val)) { if (strcmp(name, "initExpr") == 0) { // TODO: do any expression here if (const auto literalExpr = as(varDecl->initExpr)) { lua_pushlstring( L, literalExpr->token.getContent().begin(), literalExpr->token.getContent().getLength()); return 1; } return 0; } } return 0; } void push(lua_State* L, Val* val) { if (!val) { lua_pushnil(L); return; } lua_pushlightuserdata(L, val); if (luaL_newmetatable(L, "fiddle::Val")) { lua_pushcfunction(L, &_indexVal); lua_setfield(L, -2, "__index"); lua_pushcfunction(L, &_toStringVal); lua_setfield(L, -2, "__tostring"); } lua_setmetatable(L, -2); } void registerValWithScript(String name, Val* val) { auto L = getLuaState(); push(L, val); lua_setglobal(L, name.getBuffer()); } void registerScrapedStuffWithScript(LogicalModule* logicalModule) { for (auto decl : logicalModule->members) { if (!decl->nameToken) continue; registerValWithScript(decl->nameToken.getContent(), decl); } } bool _hasAnyFiddleInvocationsRec(Decl* decl) { if (as(decl)) return true; if (auto container = as(decl)) { for (auto m : container->members) { if (_hasAnyFiddleInvocationsRec(m)) return true; } } return false; } bool hasAnyFiddleInvocations(SourceUnit* sourceUnit) { return _hasAnyFiddleInvocationsRec(sourceUnit); } void checkModule(LogicalModule* module, DiagnosticSink* sink) { CheckContext context(*sink); context.checkModule(module); } void emitSourceUnitMacros( SourceUnit* sourceUnit, StringBuilder& builder, DiagnosticSink* sink, SourceManager* sourceManager, LogicalModule* logicalModule) { // The basic task here is to find each of the // `FIDDLE()` macro invocations, and for each // of them produce a matching definition that // will be used as the expansion of that one // EmitContext context(builder, *sink, *sourceManager, logicalModule); context.emitMacrosRec(sourceUnit); } } // namespace fiddle