/* Resp: A programming language * Copyright (C) 2008-2009 David Robillard * * Resp is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * Resp is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General * Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Resp. If not, see . */ /** @file * @brief Expand built-in macros (i.e. def) */ #include "resp.hpp" using namespace std; static bool match(PEnv& penv, Subst& subst, std::set keywords, const AST* p, const AST* e, bool bind) { if (p->to_tuple() && e->to_tuple()) { ATuple::const_iterator pi = p->as_tuple()->begin(); ATuple::const_iterator ei = e->as_tuple()->begin(); ATuple::const_iterator prev_pi = pi; if (!match(penv, subst, keywords, *pi++, *ei++, true)) { return false; } for (; pi != p->as_tuple()->end() && ei != e->as_tuple()->end(); ++pi, ++ei, ++prev_pi) { if (is_dots(*pi)) { List out; for (; ei != e->as_tuple()->end(); ++ei) { if (match(penv, subst, keywords, *prev_pi, *ei, false)) { out.push_back(*ei); } else { return false; } } subst.add(*pi, out); ++pi; break; } else if (!match(penv, subst, keywords, *pi, *ei, true)) { return false; } } if ((pi != p->as_tuple()->end()) || (ei != e->as_tuple()->end())) return false; } else if (p->to_symbol()) { if (keywords.count(p->str())) { if (!e->to_symbol() || e->str() != p->str()) { return false; // Keyword mismatch } } else if (p->as_symbol()->str() != "_" && bind) { subst.add(p, e); // Symbol p match with symbol e } // else _, ... matches with everything but creates no bindings } else { THROW_IF(p->tag() != e->tag(), e->loc, "expr type mismatch"); return false; // Recursive list p mismatch with list e } return true; } static const AST* apply_mac(PEnv& penv, const Macro& mac, const ATuple* exp) { const AST* out = exp; for (auto r : mac.rules) { Subst subst; if (match(penv, subst, mac.keywords, r.pattern, out, true)) { return subst.apply(r.templ); } } return out; } static const AST* expand_list(PEnv& penv, const ATuple* e) { // Attempt to match against macro rule const ASymbol* sym = e->fst()->to_symbol(); if (sym) { PEnv::Macros::const_iterator m = penv.macros.find(sym->str()); if (m != penv.macros.end()) { const AST* out = apply_mac(penv, m->second, e); if (out) { return out; } } } // No match, try to expand children List ret; for (const auto& i : *e) ret.push_back(penv.expand(i)); ret.head->loc = e->loc; return ret.head; } static const AST* expand_fn(PEnv& penv, const AST* exp) { 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("lambda"), NULL, exp->loc)); ret.push_back(prot); while (a != tup->end()) ret.push_back(penv.expand(*a++)); return ret.head; } static const AST* expand_def(PEnv& penv, const AST* exp) { const ATuple* tup = exp->as_tuple(); THROW_IF(tup->list_len() < 3, tup->loc, "`def' requires at least 2 arguments"); ATuple::const_iterator i = tup->begin(); const AST* arg1 = *(++i); if (arg1->to_tuple()) { // (def (f x) y) => (def f (fn (x) y)) const ATuple* pat = arg1->to_tuple(); List argsExp; ATuple::const_iterator j = pat->begin(); for (++j; j != pat->end(); ++j) argsExp.push_back(*j); argsExp.head->loc = exp->loc; List fnExp; fnExp.push_back(penv.sym("lambda")); fnExp.push_back(argsExp.head); for (++i; i != tup->end(); ++i) fnExp.push_back(*i); fnExp.head->loc = exp->loc; List ret; ret.push_back(tup->fst()); ret.push_back(pat->fst()); ret.push_back(fnExp.head); ret.head->loc = exp->loc; return expand_list(penv, ret.head); } else { return expand_list(penv, tup); } } static const AST* expand_mac(PEnv& penv, const AST* exp) { const ATuple* tup = exp->as_tuple(); THROW_IF(tup->list_len() < 3, tup->loc, "`define-syntax' requires at least 2 arguments"); ATuple::const_iterator i = tup->begin(); const ASymbol* sym = (*(++i))->as_symbol(); THROW_IF(!sym, (*i)->loc, "expected symbol"); const ATuple* xform = (*(++i))->as_tuple(); THROW_IF(!xform, (*i)->loc, "expected list expression"); const ASymbol* form = xform->fst()->as_symbol(); THROW_IF(!form || form->str() != "syntax-rules", form->loc, "expected syntax-rules"); Macro macro; const ATuple* keywords = xform->frst()->as_tuple(); for (auto k : *keywords) { THROW_IF(!k->to_symbol(), k->loc, "keyword must be a symbol"); macro.keywords.insert(k->as_symbol()->str()); } for (ATuple::const_iterator r = xform->iter_at(2); r != xform->end(); ++r) { const ATuple* rule = (*r)->as_tuple(); const ATuple* pat = rule->fst()->as_tuple(); const ATuple* plate = rule->frst()->as_tuple(); macro.rules.push_back(Macro::Rule(pat, plate)); } penv.macros.insert(std::make_pair(sym->str(), macro)); return NULL; } const AST* PEnv::expand(const AST* exp) { while (true) { const AST* out = exp; const ATuple* tup = out->to_tuple(); if (!tup) return out; THROW_IF(tup->empty(), exp->loc, "Call to empty list"); if (is_form(tup, "define")) out = expand_def(*this, out); else if (is_form(tup, "define-syntax")) return expand_mac(*this, out); else if (is_form(tup, "lambda")) out = expand_fn(*this, out); else out = expand_list(*this, tup); const bool done = !out || *out == *exp; exp = out; if (done) { break; } } return exp; } /*************************************************************************** * Language Definition * ***************************************************************************/ void initLang(PEnv& penv, TEnv& tenv) { // Types const char* types[] = { "Bool", "Float", "Int", "Nothing", "String", "Symbol", "List", "Expr", 0 }; for (const char** t = types; *t; ++t) { const ASymbol* sym = penv.sym(*t); tenv.def(sym, sym); // FIXME: define to NULL? } const char* primitives[] = { "+", "-", "*", "/", "%", "and", "or", "xor", "=", "!=", ">", ">=", "<", "<=", 0 }; for (const char** p = primitives; *p; ++p) penv.primitives.insert(*p); }