From 7682c4ceab935d39aafac369c9b110b658b1e575 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Tue, 7 Dec 2010 23:21:08 +0000 Subject: Saner recursive descent lexer/parser. git-svn-id: http://svn.drobilla.net/resp/resp@306 ad02d1e2-f140-0410-9f75-f8b11f17cedd --- src/c.cpp | 1 - src/compile.cpp | 4 - src/constrain.cpp | 12 --- src/lex.cpp | 222 ++++++++++++++++++++++++++++++++++++++---------------- src/lift.cpp | 4 +- src/llvm.cpp | 5 -- src/parse.cpp | 122 ++++++++---------------------- src/pprint.cpp | 6 -- src/repl.cpp | 4 +- src/resp.cpp | 4 +- src/resp.hpp | 32 ++------ test.sh | 1 - test/quote.resp | 3 - 13 files changed, 200 insertions(+), 220 deletions(-) delete mode 100644 test/quote.resp diff --git a/src/c.cpp b/src/c.cpp index 6493ff3..19cc1d5 100644 --- a/src/c.cpp +++ b/src/c.cpp @@ -51,7 +51,6 @@ llType(const AType* t) if (t->head()->str() == "Float") return new string("float"); if (t->head()->str() == "String") return new string("char*"); if (t->head()->str() == "Quote") return new string("char*"); - if (t->head()->str() == "Lexeme") return new string("char*"); throw Error(t->loc, string("Unknown primitive type `") + t->str() + "'"); } else if (t->kind == AType::EXPR && t->head()->str() == "Fn") { AType::const_iterator i = t->begin(); diff --git a/src/compile.cpp b/src/compile.cpp index 4ac7dfc..35fb042 100644 --- a/src/compile.cpp +++ b/src/compile.cpp @@ -148,8 +148,6 @@ resp_compile(CEnv& cenv, const AST* ast) throw() case T_FLOAT: case T_INT32: return cenv.engine()->compileLiteral(cenv, ast); - case T_LEXEME: - return cenv.engine()->compileString(cenv, ((ALexeme*)ast)->cppstr.c_str()); case T_STRING: return cenv.engine()->compileString(cenv, ((AString*)ast)->cppstr.c_str()); case T_SYMBOL: @@ -171,8 +169,6 @@ resp_compile(CEnv& cenv, const AST* ast) throw() return compile_cons(cenv, call); else if (form == ".") return compile_dot(cenv, call); - else if (form == "quote") - return resp_compile(cenv, call->list_ref(1)); else if (form == "match") return cenv.engine()->compileMatch(cenv, call); else if (form == "def-type") diff --git a/src/constrain.cpp b/src/constrain.cpp index e151eb6..3aee45a 100644 --- a/src/constrain.cpp +++ b/src/constrain.cpp @@ -229,13 +229,6 @@ constrain_dot(TEnv& tenv, Constraints& c, const ATuple* call) throw(Error) c.constrain(tenv, obj, objT); } -static void -constrain_quote(TEnv& tenv, Constraints& c, const ATuple* call) throw(Error) -{ - c.constrain(tenv, call, tenv.named("Quote")); - resp_constrain(tenv, c, call->list_ref(1)); -} - static void constrain_call(TEnv& tenv, Constraints& c, const ATuple* call) throw(Error) { @@ -347,8 +340,6 @@ constrain_tuple(TEnv& tenv, Constraints& c, const ATuple* tup) throw(Error) constrain_cons(tenv, c, tup); else if (form == ".") constrain_dot(tenv, c, tup); - else if (form == "quote") - constrain_quote(tenv, c, tup); else constrain_call(tenv, c, tup); } @@ -369,9 +360,6 @@ resp_constrain(TEnv& tenv, Constraints& c, const AST* ast) throw(Error) case T_INT32: c.constrain(tenv, ast, tenv.named("Int")); break; - case T_LEXEME: - c.constrain(tenv, ast, tenv.named("Lexeme")); - break; case T_STRING: c.constrain(tenv, ast, tenv.named("String")); break; diff --git a/src/lex.cpp b/src/lex.cpp index 5b6eb73..2ac838f 100644 --- a/src/lex.cpp +++ b/src/lex.cpp @@ -16,8 +16,7 @@ */ /** @file - * @brief Lexing (build a CST from a string). - * A CST is a lexeme, or a tuple of CST's. + * @brief Parsing (build an AST from text) */ #include @@ -26,8 +25,8 @@ using namespace std; -inline int -readChar(Cursor& cur, istream& in) +static inline int +read_char(Cursor& cur, istream& in) { int ch = in.get(); switch (ch) { @@ -37,81 +36,172 @@ readChar(Cursor& cur, istream& in) return ch; } +static inline void +skip_space(Cursor& cur, istream& in) +{ + while (isspace(in.peek())) + read_char(cur, in); +} + +static inline void +eat_char(Cursor& cur, istream& in, const char character) +{ + const char c = read_char(cur, in); + assert(c == character); + return; +} + +static AST* +read_string(Cursor& cur, istream& in) +{ + string str; + char c; + Cursor loc = cur; + eat_char(cur, in, '"'); + while ((c = read_char(cur, in)) != '"') { + if (c == '\\') { // string escape + switch (c = read_char(cur, in)) { + case '"': + str.push_back('"'); + break; + case '\\': + str.push_back('\\'); + break; + default: + cin.putback(c); + throw Error(cur, string("unknown string escape `\\") + (char)c + "'"); + } + } else { // any other character + str.push_back(c); + } + } + return new AString(loc, str); +} + +static AST* +read_line_comment(Cursor& cur, istream& in) +{ + char c; + while ((c = read_char(cur, in)) != '\n') {} + return NULL; +} + +static AST* +read_list(PEnv& penv, Cursor& cur, istream& in) +{ + List list; + + eat_char(cur, in, '('); + while (true) { + skip_space(cur, in); + if (in.peek() == ')') { + eat_char(cur, in, ')'); + return list.head; + } + + list.push_back(read_expression(penv, cur, in)); + } + assert(false); +} + +static AST* +read_special(Cursor& cur, istream& in) +{ + eat_char(cur, in, '#'); + switch (in.peek()) { + case '|': + while (!(read_char(cur, in) == '|' && read_char(cur, in) == '#')) {} + return NULL; + case 't': + eat_char(cur, in, 't'); + return new ALiteral(T_BOOL, true, cur); + case 'f': + return new ALiteral(T_BOOL, false, cur); + default: + throw Error(cur, (format("unknown special lexeme `%1%'") % in.peek()).str()); + } + assert(false); + return NULL; +} + +static AST* +read_number(Cursor& cur, istream& in) +{ + string str; + char c; + Cursor loc = cur; + while ((c = in.peek()) != EOF) { + if (isdigit(c) || c == '.') + str += read_char(cur, in); + else + break; + } + + if (str.find('.') == string::npos) + return new ALiteral(T_INT32, strtol(str.c_str(), NULL, 10), loc); + else + return new ALiteral(T_FLOAT, strtod(str.c_str(), NULL), loc); +} + +static AST* +read_symbol(PEnv& penv, Cursor& cur, istream& in) +{ + string str; + char c; + Cursor loc = cur; + while ((c = in.peek()) != EOF) { + if (!isspace(c) && c != ')' && c != '(' && c != EOF && c != -1) { + str += read_char(cur, in); + } else { + break; + } + } + + return penv.sym(str); +} + /// Read an expression from @a in AST* -readExpression(Cursor& cur, istream& in) +read_expression(PEnv& penv, Cursor& cur, istream& in) { -#define PUSH(s, t) { if (t != "") { s.top().push_back(new ALexeme(loc, t)); t = ""; } } -#define YIELD(s, t) { if (s.empty()) { return new ALexeme(loc, t); } else PUSH(s, t) } - stack< List > stk; - string tok; - Cursor loc; // start of tok - while (int c = readChar(cur, in)) { + while (!cin.eof()) { + skip_space(cur, in); + const char c = in.peek(); switch (c) { case EOF: - THROW_IF(!stk.empty(), cur, "unexpected end of file"); - return new ATuple(cur); + return NULL; case ';': - while ((c = readChar(cur, in)) != '\n') {} - case '\n': case ' ': case '\t': case '\r': case '\f': - if (tok != "") YIELD(stk, tok); + read_line_comment(cur, in); break; case '"': - loc = cur; - tok.push_back(c); // leading quote - while ((c = readChar(cur, in)) != '"') { - if (c == '\\') { // string escape - switch (c = readChar(cur, in)) { - case '"': - tok.push_back('"'); - break; - case '\\': - tok.push_back('\\'); - break; - default: - cin.putback(c); - throw Error(cur, string("unknown string escape `\\") + (char)c + "'"); - } - } else { // any other character - tok.push_back(c); - } - } - tok.push_back(c); // trailing quote - YIELD(stk, tok); - break; + return read_string(cur, in); case '(': - stk.push(List()); - break; + return read_list(penv, cur, in); case ')': - switch (stk.size()) { - case 0: - cin.putback(c); - throw Error(cur, "unexpected `)'"); - case 1: - PUSH(stk, tok); - return stk.top().head; - default: - PUSH(stk, tok); - List l = stk.top(); - stk.pop(); - stk.top().push_back(l.head); - } - break; + throw Error(cur, "unexpected `)'"); case '#': - if (in.peek() == '|') { - while (!(readChar(cur, in) == '|' && readChar(cur, in) == '#')) {} - break; + { + AST* ret = read_special(cur, in); + if (ret) + return ret; + break; + } + case '-': + case '+': + read_char(cur, in); + if (isdigit(in.peek())) { + in.putback(c); + return read_number(cur, in); + } else { + in.putback(c); + return read_symbol(penv, cur, in); } default: - if (tok == "") loc = cur; - tok += c; + if (isdigit(c)) + return read_number(cur, in); + else + return read_symbol(penv, cur, in); } } - switch (stk.size()) { - case 0: return new AString(loc, tok); - case 1: return stk.top().head; - default: throw Error(cur, "missing `)'"); - } - assert(false); - return new ATuple(cur); // never reached + return NULL; } diff --git a/src/lift.cpp b/src/lift.cpp index fe901f0..e0a5b5a 100644 --- a/src/lift.cpp +++ b/src/lift.cpp @@ -33,7 +33,7 @@ lift_symbol(CEnv& cenv, Code& code, const ASymbol* sym) throw() const std::string& cppstr = sym->cppstr; if (!cenv.liftStack.empty() && cppstr == cenv.name(cenv.liftStack.top().fn)) { return cenv.penv.sym("_me"); // Reference to innermost function - } else if (!cenv.penv.handler(cppstr) && !cenv.code.innermost(sym)) { + } else if (!cenv.code.innermost(sym)) { const int32_t index = cenv.liftStack.top().index(sym); @@ -247,8 +247,6 @@ resp_lift(CEnv& cenv, Code& code, const AST* ast) throw() return lift_builtin_call(cenv, code, call); else if (form == ".") return lift_builtin_call(cenv, code, call); - else if (form == "quote") - return lift_builtin_call(cenv, code, call); else if (form == "match" || form == "def-type") return call; // FIXME else diff --git a/src/llvm.cpp b/src/llvm.cpp index b03713d..bc90977 100644 --- a/src/llvm.cpp +++ b/src/llvm.cpp @@ -96,7 +96,6 @@ struct LLVMEngine : public Engine { if (t->head()->str() == "Float") return Type::getFloatTy(context); if (t->head()->str() == "String") return PointerType::get(Type::getInt8Ty(context), NULL); if (t->head()->str() == "Quote") return PointerType::get(Type::getInt8Ty(context), NULL); - if (t->head()->str() == "Lexeme") return PointerType::get(Type::getInt8Ty(context), NULL); throw Error(t->loc, string("Unknown primitive type `") + t->str() + "'"); } else if (t->kind == AType::EXPR && t->head()->str() == "Fn") { AType::const_iterator i = t->begin(); @@ -260,10 +259,6 @@ struct LLVMEngine : public Engine { } } ss << "\""; - } else if (retT->head()->str() == "Lexeme") { - ss << ((char* (*)())fp)(); - } else if (retT->head()->str() == "Quote") { - ss << "(quote " << ((char* (*)())fp)() << ")"; } else if (t != Type::getVoidTy(context)) { ss << ((void* (*)())fp)(); } else { diff --git a/src/parse.cpp b/src/parse.cpp index 81aaa83..3fb8375 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -16,7 +16,7 @@ */ /** @file - * @brief Parsing (build a code AST from a textual AST) + * @brief Expand built-in macros (i.e. def) */ #include "resp.hpp" @@ -24,7 +24,7 @@ using namespace std; const ATuple* -parseTuple(PEnv& penv, const ATuple* e) +parseList(PEnv& penv, const ATuple* e) { List ret; FOREACHP(ATuple::const_iterator, i, e) @@ -32,84 +32,19 @@ parseTuple(PEnv& penv, const ATuple* e) return ret.head; } -const AST* -PEnv::parse(const AST* exp) -{ - const ATuple* tup = exp->to_tuple(); - if (tup) { - THROW_IF(tup->empty(), exp->loc, "Call to empty list"); - const ALexeme* form = tup->head()->to_lexeme(); - if (form) { - if (isupper(form->cppstr.c_str()[0])) // Call constructor (any uppercase symbol) - return parseTuple(*this, tup); - - const PEnv::Handler* h = handler(form->cppstr); - if (h) - return h->func(*this, exp, h->arg); // Parse special form - } - - return parseTuple(*this, tup); // Parse regular call - } - - const ALexeme* lex = exp->to_lexeme(); - assert(lex); - if (isdigit(lex->cppstr[0])) { - const std::string& s = lex->cppstr; - if (s.find('.') == string::npos) - return new ALiteral(T_INT32, strtol(s.c_str(), NULL, 10), exp->loc); - else - return new ALiteral(T_FLOAT, strtod(s.c_str(), NULL), exp->loc); - } else if (lex->cppstr[0] == '\"') { - return new AString(exp->loc, lex->cppstr.substr(1, lex->cppstr.length() - 2)); - } else if (lex->cppstr == "#t") { - return new ALiteral(T_BOOL, true, exp->loc); - } else if (lex->cppstr == "#f") { - return new ALiteral(T_BOOL, false, exp->loc); - } - return sym(lex->cppstr, exp->loc); -} - - -/*************************************************************************** - * Parser Functions * - ***************************************************************************/ - -inline const AST* -parseCall(PEnv& penv, const AST* exp, void* arg) -{ - return parseTuple(penv, exp->to_tuple()); -} - -inline const AST* -parseBool(PEnv& penv, const AST* exp, void* arg) -{ - return new ALiteral(T_BOOL, *reinterpret_cast(arg), exp->loc); -} - inline const AST* parseFn(PEnv& penv, const AST* exp, void* arg) { - const ATuple* texp = exp->to_tuple(); - ATuple::const_iterator a = texp->begin(); - THROW_IF(++a == texp->end(), exp->loc, "Unexpected end of `fn' form"); - const ATuple* prot = parseTuple(penv, (*a++)->to_tuple()); - List ret(new ATuple(penv.sym("fn"), NULL, Cursor())); + const ATuple* tup = exp->to_tuple(); + ATuple::const_iterator a = tup->begin(); + THROW_IF(++a == tup->end(), exp->loc, "Unexpected end of `fn' form"); + THROW_IF(!(*a)->to_tuple(), (*a)->loc, "First argument of `fn' is not a list"); + const ATuple* prot = (*a++)->to_tuple(); + List ret(new ATuple(penv.sym("fn"), NULL, exp->loc)); ret.push_back(prot); - while (a != texp->end()) + while (a != tup->end()) ret.push_back(penv.parse(*a++)); - ret.head->loc = exp->loc; - return new ATuple(*ret.head); -} - -inline const AST* -parseQuote(PEnv& penv, const AST* exp, void* arg) -{ - const ATuple* texp = exp->to_tuple(); - THROW_IF(texp->list_len() != 2, exp->loc, "`quote' requires exactly 1 argument"); - const ALexeme* quotee = texp->list_ref(1)->to_lexeme(); - THROW_IF(!quotee, exp->loc, "`quote' argument is not a lexeme"); - ATuple* ret = tup(texp->loc, penv.sym("quote"), quotee, NULL); - return ret; + return ret.head; } inline const AST* @@ -121,8 +56,8 @@ parseDef(PEnv& penv, const AST* exp, void* arg) THROW_IF(i == tup->end(), tup->loc, "Unexpected end of `def' form"); const AST* arg1 = *(++i); THROW_IF(i == tup->end(), arg1->loc, "Unexpected end of `def' form"); - if (arg1->to_lexeme()) { - return parseCall(penv, exp, arg); + if (arg1->to_symbol()) { + return parseList(penv, tup); } else { // (def (f x) y) => (def f (fn (x) y)) const ATuple* pat = arg1->to_tuple(); @@ -135,7 +70,7 @@ parseDef(PEnv& penv, const AST* exp, void* arg) const AST* body = *(++i); List fnExp; - fnExp.push_back(new ALexeme(exp->loc, "fn")); + fnExp.push_back(penv.sym("fn")); fnExp.push_back(argsExp.head); for (; i != tup->end(); ++i) fnExp.push_back(*i); @@ -147,10 +82,28 @@ parseDef(PEnv& penv, const AST* exp, void* arg) ret.push_back(fnExp.head); ret.head->loc = exp->loc; - return parseCall(penv, ret.head, arg); + return parseList(penv, ret.head); } } +const AST* +PEnv::parse(const AST* exp) +{ + const ATuple* tup = exp->to_tuple(); + if (!tup) + return exp; + + THROW_IF(tup->empty(), exp->loc, "Call to empty list"); + + if (is_form(tup, "def")) + return parseDef(*this, exp, NULL); + else if (is_form(tup, "fn")) + return parseFn(*this, exp, NULL); + else + return parseList(*this, tup); +} + + /*************************************************************************** * Language Definition * @@ -168,15 +121,6 @@ initLang(PEnv& penv, TEnv& tenv) tenv.def(penv.sym("Quote"), new AType(penv.sym("Quote"), AType::PRIM)); tenv.def(penv.sym("String"), new AType(penv.sym("String"), AType::PRIM)); - // Special forms - penv.reg(".", PEnv::Handler(parseCall)); - penv.reg("def", PEnv::Handler(parseDef)); - penv.reg("def-type", PEnv::Handler(parseCall)); - penv.reg("fn", PEnv::Handler(parseFn)); - penv.reg("if", PEnv::Handler(parseCall)); - penv.reg("match", PEnv::Handler(parseCall)); - penv.reg("quote", PEnv::Handler(parseQuote)); - // Numeric primitives penv.primitives.insert("+"); penv.primitives.insert("-"); @@ -192,6 +136,4 @@ initLang(PEnv& penv, TEnv& tenv) penv.primitives.insert(">="); penv.primitives.insert("<"); penv.primitives.insert("<="); - FOREACH (PEnv::Primitives::const_iterator, i, penv.primitives) - penv.reg(*i, PEnv::Handler(parseCall)); } diff --git a/src/pprint.cpp b/src/pprint.cpp index 88ed90f..5253316 100644 --- a/src/pprint.cpp +++ b/src/pprint.cpp @@ -85,10 +85,6 @@ print_to(ostream& out, const AST* ast, unsigned indent, CEnv* cenv, bool types) const ASymbol* sym = (*i)->to_symbol(); if (sym) { form = sym->cppstr; - } else { - const ALexeme* lexeme = (*i)->to_lexeme(); - if (lexeme) - form = lexeme->cppstr; } } @@ -149,8 +145,6 @@ print_to(ostream& out, const AST* ast, unsigned indent, CEnv* cenv, bool types) return out << showpoint << ((const ALiteral*)ast)->val; case T_INT32: return out << showpoint << ((const ALiteral*)ast)->val; - case T_LEXEME: - return out << ((const ALexeme*)ast)->cppstr; case T_STRING: return out << '"' << ((const AString*)ast)->cppstr << '"'; case T_SYMBOL: diff --git a/src/repl.cpp b/src/repl.cpp index 4ea4d68..8b4faab 100644 --- a/src/repl.cpp +++ b/src/repl.cpp @@ -30,13 +30,13 @@ static bool readParseType(CEnv& cenv, Cursor& cursor, istream& is, AST*& exp, const AST*& ast) { try { - exp = readExpression(cursor, is); + exp = read_expression(cenv.penv, cursor, is); } catch (Error e) { is.ignore(std::numeric_limits::max(), '\n'); // Skip REPL junk throw e; } - if (exp->to_tuple() && exp->to_tuple()->empty()) + if (!exp || (exp->to_tuple() && exp->to_tuple()->empty())) return false; ast = cenv.penv.parse(exp); // Parse input diff --git a/src/resp.cpp b/src/resp.cpp index 0d1af5a..824acff 100644 --- a/src/resp.cpp +++ b/src/resp.cpp @@ -143,8 +143,8 @@ main(int argc, char** argv) try { while (is.good() && !is.eof()) { Cursor loc(*f); - AST* exp = readExpression(loc, is); - if (!exp || (exp->as_tuple() && exp->as_tuple()->tup_len() == 1)) + AST* exp = read_expression(cenv->penv, loc, is); + if (!exp || (exp->to_tuple() && exp->to_tuple()->tup_len() == 1)) break; const AST* ast = penv.parse(exp); diff --git a/src/resp.hpp b/src/resp.hpp index 0c698a9..77f771c 100644 --- a/src/resp.hpp +++ b/src/resp.hpp @@ -117,8 +117,9 @@ ostream& operator<<(ostream& out, const Env& env) { * Lexer: Text (istream) -> S-Expressions (SExp) * ***************************************************************************/ +struct PEnv; struct AST; -AST* readExpression(Cursor& cur, std::istream& in); +AST* read_expression(PEnv& penv, Cursor& cur, std::istream& in); /*************************************************************************** @@ -141,11 +142,10 @@ enum Tag { T_BOOL = 4, T_FLOAT = 6, T_INT32 = 8, - T_LEXEME = 10, - T_STRING = 12, - T_SYMBOL = 14, - T_TUPLE = 16, - T_TYPE = 18 + T_STRING = 10, + T_SYMBOL = 12, + T_TUPLE = 14, + T_TYPE = 16 }; /// Garbage collector @@ -204,7 +204,6 @@ struct CEnv; ///< Compile-Time Environment struct ATuple; struct ASymbol; struct AType; -struct ALexeme; class AST; extern ostream& operator<<(ostream& out, const AST* ast); @@ -241,7 +240,6 @@ struct AST : public Object { const ASymbol* as_symbol() const { return as_a(T_SYMBOL); } const ASymbol* to_symbol() const { return to_a(T_SYMBOL); } - const ALexeme* to_lexeme() const { return to_a(T_LEXEME); } const AType* as_type() const { return as_a(T_TYPE); } const AType* to_type() const { return to_a(T_TYPE); } @@ -265,12 +263,6 @@ struct ALiteral : public AST { const T val; }; -/// Lexeme (any atom in the CST, e.g. "a", "3.4", ""hello"", etc.) -struct ALexeme : public AST { - ALexeme(Cursor c, const string& s) : AST(T_LEXEME, c), cppstr(s) {} - const string cppstr; -}; - /// String, e.g. ""a"" struct AString : public AST { AString(Cursor c, const string& s) : AST(T_STRING, c), cppstr(s) {} @@ -538,7 +530,6 @@ AST::operator==(const AST& rhs) const } case T_UNKNOWN: - case T_LEXEME: case T_STRING: case T_SYMBOL: return this == &rhs; @@ -553,19 +544,10 @@ AST::operator==(const AST& rhs) const /// Parse Time Environment (really just a symbol table) struct PEnv : private map { PEnv() : symID(0) {} - typedef const AST* (*PF)(PEnv&, const AST*, void*); ///< Parse Function - struct Handler { Handler(PF f, void* a=0) : func(f), arg(a) {} PF func; void* arg; }; - map handlers; ///< List parse functions - void reg(const string& s, const Handler& h) { - handlers.insert(make_pair(sym(s)->str(), h)); - } - const Handler* handler(const string& s) const { - map::const_iterator i = handlers.find(s); - return (i != handlers.end()) ? &i->second : NULL; - } string gensymstr(const char* s="_") { return (format("%s_%d") % s % symID++).str(); } ASymbol* gensym(const char* s="_") { return sym(gensymstr(s)); } ASymbol* sym(const string& s, Cursor c=Cursor()) { + assert(s != ""); const const_iterator i = find(s); if (i != end()) { return i->second; diff --git a/test.sh b/test.sh index 53ce693..c307cc3 100755 --- a/test.sh +++ b/test.sh @@ -22,7 +22,6 @@ run './test/inlinefn.resp' '2 : Int' run './test/nest.resp' '8 : Int' run './test/tup.resp' '5 : Int' run './test/string.resp' '"Hello, world!" : String' -run './test/quote.resp' '(quote hello) : Quote' run './test/match.resp' '"Hello, rectangle!" : String' #run './test/poly.resp' '#t : Bool' diff --git a/test/quote.resp b/test/quote.resp deleted file mode 100644 index a7b41a1..0000000 --- a/test/quote.resp +++ /dev/null @@ -1,3 +0,0 @@ -(def q (quote hello)) - -q -- cgit v1.2.1