
convex.core.lang.Compiler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of convex-core Show documentation
Show all versions of convex-core Show documentation
Convex core libraries and common utilities
package convex.core.lang;
import java.util.Map;
import convex.core.Constants;
import convex.core.ErrorCodes;
import convex.core.data.ABlob;
import convex.core.data.ACell;
import convex.core.data.ADataStructure;
import convex.core.data.AHashMap;
import convex.core.data.AList;
import convex.core.data.AMap;
import convex.core.data.ASequence;
import convex.core.data.ASet;
import convex.core.data.AVector;
import convex.core.data.Address;
import convex.core.data.Keyword;
import convex.core.data.Keywords;
import convex.core.data.List;
import convex.core.data.MapEntry;
import convex.core.data.Maps;
import convex.core.data.Sets;
import convex.core.data.Symbol;
import convex.core.data.Syntax;
import convex.core.data.Vectors;
import convex.core.data.prim.CVMBool;
import convex.core.data.prim.CVMLong;
import convex.core.data.type.Types;
import convex.core.lang.Context.CompilerState;
import convex.core.lang.impl.AClosure;
import convex.core.lang.impl.CoreFn;
import convex.core.lang.impl.MultiFn;
import convex.core.lang.ops.Cond;
import convex.core.lang.ops.Constant;
import convex.core.lang.ops.Def;
import convex.core.lang.ops.Do;
import convex.core.lang.ops.Invoke;
import convex.core.lang.ops.Lambda;
import convex.core.lang.ops.Let;
import convex.core.lang.ops.Local;
import convex.core.lang.ops.Lookup;
import convex.core.lang.ops.Query;
import convex.core.lang.ops.Special;
import convex.core.util.Utils;
/**
* Compiler class responsible for transforming forms (code as data) into an
* Op tree for execution.
*
* Phases in complete evaluation:
*
* - Expansion (form -> AST)
* - Compile (AST -> op)
* - Execute (op -> result)
*
*
* Expanded form follows certain rules: - No remaining macros / expanders in
* leading list positions
*
* TODO: consider including typechecking in expansion phase as per:
* http://www.ccs.neu.edu/home/stchang/pubs/ckg-popl2017.pdf
*
* "A language that doesn't affect the way you think about programming is not
* worth knowing." ― Alan Perlis
*/
public class Compiler {
/**
* Expands and compiles a form. Equivalent to running expansion followed by
* compile. Should not be used directly, intended for use via
* Context.expandCompile(...)
*
* @param form A form, either raw or wrapped in a Syntax Object
* @param context Compilation context
* @return Context with compiled op as result
*/
static Context expandCompile(ACell form, Context context) {
// expand phase starts with initial expander
AFn ex = INITIAL_EXPANDER;
// Use initial expander both as current and continuation expander
// call expand via context to get correct depth and exception handling
final Context ctx = context.invoke(ex, form,ex);
if (ctx.isExceptional()) return ctx;
ACell c=ctx.getResult();
return ctx.compile(c);
}
/**
* Compiles a single form. Should not be used directly, intended for use via
* Context.compile(...)
*
* Updates context with result, juice consumed
*
* @param expandedForm A fully expanded form expressed as a Syntax Object
* @param context
* @return Context with compiled Op as result
*/
@SuppressWarnings("unchecked")
static Context compile(ACell form, Context context) {
if (form==null) return compileConstant(context,null);
if (form instanceof AList) return compileList((AList) form, context);
if (form instanceof Syntax) return compileSyntax((Syntax) form, context);
if (form instanceof AVector) return compileVector((AVector) form, context);
if (form instanceof AMap) return compileMap((AMap) form, context);
if (form instanceof ASet) return compileSet((ASet) form, context);
if ((form instanceof Keyword) || (form instanceof ABlob)) {
return compileConstant(context, form);
}
if (form instanceof Symbol) {
return compileSymbol((Symbol) form, context);
}
if (form instanceof AOp) {
// already compiled, just return as constant
return context.withResult(Juice.COMPILE_CONSTANT, (AOp>)form);
}
// return as a constant literal
return compileConstant(context,form);
}
/**
* Compiles a sequence of forms, returning a vector of ops in the updated
* context. Equivalent to calling compile sequentially on each form.
*
* @param forms
* @param context
* @return Context with Vector of compiled ops as result
*/
static Context compileAll(ASequence forms, Context context) {
if (forms == null) return context.withResult(Vectors.empty()); // consider null as empty list
int n = forms.size();
AVector> obs = Vectors.empty();
for (int i = 0; i < n; i++) {
ACell form = forms.get(i);
context = context.compile(form);
if (context.isExceptional()) return context;
obs = obs.conj((AOp>) context.getResult());
}
return context.withResult(obs);
}
private static Context compileSyntax(Syntax s, Context context) {
context=compile(s.getValue(),context);
return context;
}
/**
* Compiles a Symbol as found in regular code
* @param sym Symbol
* @param context
* @return Context with compiled symbol lookup
*/
private static Context compileSymbol(Symbol sym, Context context) {
// First check for lexically defined Symbols
CompilerState cs=context.getCompilerState();
// First check if we hit a local declaration within the current compile context
if (cs!=null) {
CVMLong position=cs.getPosition(sym);
if (position!=null) {
Local> op=Local.create(position.longValue());
return context.withResult(Juice.COMPILE_LOOKUP,op);
}
}
// Next check for special values
int ch=sym.getName().charAt(0);
if (ch=='*') {
Special> maybeSpecial=Special.forSymbol(sym);
if (maybeSpecial!=null) {
return context.withResult(maybeSpecial);
}
} else if (ch=='#') {
ACell maybeCoreImplicit = Core.CORE_FORMS.get(sym);
if (maybeCoreImplicit!=null) {
return compileConstant(context,maybeCoreImplicit);
}
}
// Regular symbol lookup in environment
Address address=context.getAddress();
return compileEnvSymbol(address,sym,context);
}
private static Context compileEnvSymbol(Address address,Symbol sym, Context context) {
// Optional code for :static embedding
if (Constants.OPT_STATIC) {
// Get metadata for symbol.
AHashMap meta=context.lookupMeta(sym);
// If static, embed value directly as constant
if ((meta!=null)&&meta.get(Keywords.STATIC)==CVMBool.TRUE) {
ACell value=context.lookupValue(sym);
return context.withResult(Juice.COMPILE_LOOKUP,Constant.create(value));
}
}
// Finally revert to a lookup in the current address / environment
Lookup> lookUp=Lookup.create(Constant.of(address),sym);
return context.withResult(Juice.COMPILE_LOOKUP, lookUp);
}
private static Context compileSetBang(AList list, Context context) {
if (list.count()!=3) return context.withArityError("set! requires two arguments, a symbol and an expression");
ACell a1=list.get(1);
if (!(a1 instanceof Symbol)) return context.withCompileError("set! requires a symbol as first argument");
Symbol sym=(Symbol)a1;
CompilerState cs=context.getCompilerState();
CVMLong position=(cs==null)?null:context.getCompilerState().getPosition(sym);
if (position==null) return context.withCompileError("Trying to set! an undeclared symbol: "+sym);
context=context.compile(list.get(2));
if (context.isExceptional()) return context;
AOp> exp=(AOp>) context.getResult();
AOp> op=convex.core.lang.ops.Set.create(position.longValue(), exp);
return context.withResult(Juice.COMPILE_NODE,op);
}
/**
* Compile a lookup of the form (lookup 'foo) or (lookup addr 'foo)
* @param list Lookup form
* @param context Compiler context
* @return Op performing Lookup
*/
@SuppressWarnings("unchecked")
private static Context compileLookup(AList list, Context context) {
long n=list.count();
if ((n<2)||(n>3)) return context.withArityError("lookup requires one or two arguments: an optional expression specifying an account and a Symbol");
AOp exp=null;
if (n==3) {
// second element of list should be an expression that evaluates to an Address
context=context.compile(list.get(1));
if (context.isExceptional()) return context;
exp=(AOp) context.getResult();
}
ACell a1=list.get(n-1);
if (!(a1 instanceof Symbol)) return context.withCompileError("lookup requires a Symbol as last argument");
Symbol sym=(Symbol)a1;
Lookup> op=Lookup.create(exp,sym);
return context.withResult(Juice.COMPILE_NODE,op);
}
/**
* Compiles a map of the form {a b, c d}
* @param form Form as a Map
* @param context
* @return Op producing the given map
*/
private static Context compileMap(AMap form, Context context) {
int n = form.size();
if (n==0) return compileConstant(context,form);
ACell[] vs = new ACell[1 + n * 2];
vs[0] = Symbols.HASH_MAP;
for (int i = 0; i < n; i++) {
MapEntry me = form.entryAt(i);
vs[1 + i * 2] = me.getKey();
vs[1 + i * 2 + 1] = me.getValue();
}
return compileList(List.create(vs), context);
}
/**
* Compile a set literal of the form #{1 2} as (hash-set 1 2)
*/
private static Context compileSet(ASet form, Context context) {
if (form.isEmpty()) return compileConstant(context,Sets.empty());
AVector vs = Vectors.empty();
for (ACell o : form) {
vs = vs.conj(o);
}
vs = vs.conj(Symbols.HASH_SET);
return compileList(List.reverse(vs), context);
}
// A vector literal needs to be compiled as (vector ....)
private static Context compileVector(AVector vec, Context context) {
int n = vec.size();
// Zero length vector fast path compiles to a constant
if (n == 0) return context.withResult(Juice.COMPILE_CONSTANT, Constant.EMPTY_VECTOR);
context = context.compileAll(vec);
if (context.isError()) return context;
AVector> obs = context.getResult();
// return a 'vector' call - note function arg is a constant, we don't want to
// lookup on the 'vector' symbol
Constant fn = Constant.create(Core.VECTOR);
return context.withResult(Juice.COMPILE_NODE, Invoke.create(fn, obs));
}
private static Context compileConstant(Context context, ACell value) {
return context.withResult(Juice.COMPILE_CONSTANT, Constant.create(value));
}
/**
* Compiles a quoted form, returning an op that will produce a data structure
* after evaluation of all unquotes.
*
* @param context
* @param form Quoted form. May be a regular value or Syntax object.
* @return Context with complied op as result
*/
@SuppressWarnings("unchecked")
private static Context compileQuasiQuoted(Context context, ACell aForm, int depth) {
ACell form;
// Check if form is a Syntax Object and unwrap if necessary
boolean isSyntax;
if (aForm instanceof Syntax) {
form= Syntax.unwrap(aForm);
isSyntax=true;
} else {
form=aForm;
isSyntax=false;
}
if (form instanceof ASequence) {
ASequence seq = (ASequence) form;
int n = seq.size();
if (n == 0) {
return compileConstant(context, aForm);
}
if (isListStarting(Symbols.UNQUOTE, form)) {
if (depth==1) {
if (n != 2) return context.withArityError("unquote requires 1 argument");
ACell unquoted=seq.get(1);
//if (!(unquoted instanceof Syntax)) return context.withCompileError("unquote expects an expanded Syntax Object");
Context opContext = expandCompile(unquoted, context);
return opContext;
} else {
depth-=1;
}
} else if (isListStarting(Symbols.QUASIQUOTE, form)) {
depth+=1;
}
// compile quoted elements
context = compileAllQuasiQuoted(context, seq, depth);
if (context.isExceptional()) return context;
ASequence> rSeq = context.getResult();
ACell fn = (seq instanceof AList) ? Core.LIST : Core.VECTOR;
AOp> inv = Invoke.create( Constant.create(fn), rSeq);
if (isSyntax) {
inv=wrapSyntaxBuilder(inv,(Syntax)aForm);
}
return context.withResult(Juice.COMPILE_NODE, inv);
} else if (form instanceof AMap) {
AMap map = (AMap) form;
AVector rSeq = Vectors.empty();
for (Map.Entry me : map.entrySet()) {
rSeq = rSeq.append(me.getKey());
rSeq = rSeq.append(me.getValue());
}
// compile quoted elements
context = compileAllQuasiQuoted(context, rSeq,depth);
if (context.isExceptional()) return context;
ASequence> cSeq = (ASequence>) context.getResult();
ACell fn = Core.HASHMAP;
AOp> inv = Invoke.create(Constant.create(fn), cSeq);
if (isSyntax) {
inv=wrapSyntaxBuilder(inv,(Syntax)aForm);
}
return context.withResult(Juice.COMPILE_NODE, inv);
} else if (form instanceof ASet) {
ASet set = (ASet) form;
AVector rSeq = set.toVector();
// compile quoted elements
context = compileAllQuasiQuoted(context, rSeq,depth);
if (context.isExceptional()) return context;
ASequence> cSeq = context.getResult();
ACell fn = Core.HASHSET;
AOp> inv = Invoke.create(Constant.create(fn), cSeq);
if (isSyntax) {
inv=wrapSyntaxBuilder(inv,(Syntax)aForm);
}
return context.withResult(Juice.COMPILE_NODE, inv);
}else {
return compileConstant(context, aForm);
}
}
private static AOp wrapSyntaxBuilder(AOp op, Syntax source) {
return Invoke.create(Constant.create(Core.SYNTAX), op, Constant.create(source.getMeta()));
}
/**
* Compiles a sequence of quoted forms
*
* @param context
* @param form
* @return Context with complied sequence of ops as result
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private static Context compileAllQuasiQuoted(Context context, ASequence forms, int depth) {
int n = forms.size();
// create a list of ops producing each sub-element
ASequence> rSeq = Vectors.empty();
for (int i = 0; i < n; i++) {
ACell subSyntax = forms.get(i);
ACell subForm = Syntax.unwrap(subSyntax);
if (isListStarting(Symbols.UNQUOTE_SPLICING, subForm)) {
AList subList = (AList) subForm;
int sn = subList.size();
if (sn != 2) return context.withArityError("unquote-splicing requires 1 argument");
// unquote-splicing looks like it needs flatmap
return context.withError(ErrorCodes.TODO,"unquote-splicing not yet supported");
} else {
Context rctx= compileQuasiQuoted(context, subSyntax,depth);
if (rctx.isExceptional()) return rctx;
rSeq = (ASequence) (rSeq.conj(rctx.getResult()));
}
}
return context.withResult(rSeq);
}
/**
* Returns true if the form is a List starting with value equal to the
* the specified element
*
* @param element
* @param form
* @return True if form is a list starting with a Syntax Object wrapping the
* specified element, false otherwise.
*/
@SuppressWarnings("unchecked")
private static boolean isListStarting(Symbol element, ACell form) {
if (!(form instanceof AList)) return false;
AList list = (AList) form;
if (list.count() == 0) return false;
ACell firstElement=list.get(0);
return Utils.equals(element, Syntax.unwrap(firstElement));
}
@SuppressWarnings("unchecked")
private static Context compileList(AList list, Context context) {
int n = list.size();
if (n == 0) return context.withResult(Juice.COMPILE_CONSTANT, Constant.EMPTY_LIST);
// first entry in list should be syntax
ACell first = list.get(0);
ACell head = Syntax.unwrap(first);
if (head instanceof Symbol) {
Symbol sym = (Symbol) head;
if (sym.equals(Symbols.DO)) {
context = context.compileAll(list.next());
if (context.isExceptional()) return context;
Do> op = Do.create((AVector>) context.getResult());
return context.withResult(Juice.COMPILE_NODE, op);
}
if (sym.equals(Symbols.LET)) return compileLet(list, context, false);
if (sym.equals(Symbols.COND)) {
context = context.compileAll(list.next());
if (context.isExceptional()) return context;
Cond> op = Cond.create((AVector>) (context.getResult()));
return context.withResult(Juice.COMPILE_NODE, op);
}
if (sym.equals(Symbols.DEF)) return compileDef(list, context);
if (sym.equals(Symbols.FN)) return compileFn(list, context);
if (sym.equals(Symbols.QUOTE)) {
if (list.size() != 2) return context.withCompileError(sym + " expects one argument.");
return compileConstant(context,list.get(1));
}
if (sym.equals(Symbols.QUASIQUOTE)) {
if (list.size() != 2) return context.withCompileError(sym + " expects one argument.");
return compileQuasiQuoted(context, list.get(1),1);
}
if (sym.equals(Symbols.UNQUOTE)) {
// execute the unquoted code directly to get a form to compile
if (list.size() != 2) return context.withCompileError(Symbols.UNQUOTE + " expects one argument.");
context = context.expandCompile(list.get(1));
if (context.isExceptional()) return context;
AOp> quotedOp = context.getResult();
Context rctx = context.execute(quotedOp);
if (rctx.isExceptional()) return rctx;
Syntax resultForm = Syntax.create(rctx.getResult());
// need to expand and compile here, since we just created a raw form
return expandCompile(resultForm, context);
}
if (sym.equals(Symbols.QUERY)) {
context = context.compileAll(list.next());
if (context.isExceptional()) return context;
Query> op = Query.create((AVector>) context.getResult());
return context.withResult(Juice.COMPILE_NODE, op);
}
if (sym.equals(Symbols.LOOP)) return compileLet(list, context, true);
if (sym.equals(Symbols.SET_BANG)) return compileSetBang(list, context);
if (sym.equals(Symbols.LOOKUP)) return compileLookup(list, context);
}
// must be a regular function call
context = context.compileAll(list);
if (context.isExceptional()) return context;
Invoke> op = Invoke.create((AVector>) context.getResult());
return context.withResult(Juice.COMPILE_NODE, op);
}
@SuppressWarnings("unchecked")
private static Context compileLet(ASequence list, Context context,
boolean isLoop) {
// list = (let [...] a b c ...)
int n=list.size();
if (n<2) return context.withCompileError(list.get(0) + " requires a binding form vector at minimum");
ACell bo = list.get(1);
if (!(bo instanceof AVector))
return context.withCompileError(list.get(0) + " requires a vector of binding forms but got: " + bo);
AVector bv = (AVector) bo;
int bn = bv.size();
if ((bn & 1) != 0) return context.withCompileError(
list.get(0) + " requires a binding vector with an even number of forms but got: " + bn);
AVector bindingForms = Vectors.empty();
AVector> ops = Vectors.empty();
for (int i = 0; i < bn; i += 2) {
// Get corresponding op
context = context.expandCompile(bv.get(i + 1));
if (context.isExceptional()) return context;
AOp op = (AOp) context.getResult();
ops = ops.conj(op);
// Get a binding form. Note binding happens *after* op
ACell bf = bv.get(i);
context=compileBinding(bf,context);
if (context.isExceptional()) return context;
bindingForms = bindingForms.conj(context.getResult());
}
int exs = n - 2; // expressions in let after binding vector
for (int i = 2; i < 2 + exs; i++) {
context = context.expandCompile(list.get(i));
if (context.isExceptional()) return context;
AOp op = (AOp) context.getResult();
ops = ops.conj(op);
}
AOp> op = Let.create(bindingForms, ops, isLoop);
return context.withResult(Juice.COMPILE_NODE, op);
}
/**
* Compiles a binding form. Updates the current CompilerState. Should save compiler state if used
* @param bindingForm
* @param context
* @return
*/
private static Context compileBinding(ACell bindingForm,Context context) {
CompilerState cs=context.getCompilerState();
if (cs==null) cs=CompilerState.EMPTY;
cs=updateBinding(bindingForm,cs);
if (cs==null) return context.withCompileError("Bad binding form");
context=context.withCompilerState(cs);
return context.withResult(bindingForm);
}
@SuppressWarnings("unchecked")
private static CompilerState updateBinding(ACell bindingForm,CompilerState cs) {
if (bindingForm instanceof Symbol) {
Symbol sym=(Symbol)bindingForm;
if (!sym.equals(Symbols.UNDERSCORE)) {
cs=cs.define(sym, null); // TODO: metadata?
}
} else if (bindingForm instanceof AVector) {
AVector v=(AVector)bindingForm;
boolean foundAmpersand=false;
long vcount=v.count(); // count of binding form symbols (may include & etc.)
for (long i=0; i=(vcount-1)) return null; // trailing ampersand
foundAmpersand=true;
bf=v.get(i+1);
i++;
}
cs=updateBinding(bf,cs);
if (cs==null) return null;
}
} else {
cs=null;
}
return cs;
}
/**
* Compiles a lambda function form "(fn [...] ...)" to create a Lambda op.
*
* @param list
* @param context
* @return Context with compiled op as result.
*/
@SuppressWarnings("unchecked")
private static Context compileFn(AList list, Context context) {
// list.get(0) is presumably fn
int n = list.size();
if (n < 2) return context.withArityError("fn requires parameter vector and body in form: " + list);
// check if we have a vector, in which case we have a single function definition
ACell firstObject = Syntax.unwrap(list.get(1));
if (firstObject instanceof AVector) {
AVector paramsVector=(AVector) firstObject;
AList bodyList=list.drop(2);
return compileFnInstance(paramsVector,bodyList,context);
}
return compileMultiFn(list.drop(1),context);
}
@SuppressWarnings({ "unchecked"})
private static Context compileMultiFn(AList list, Context context) {
AVector> fns=Vectors.empty();
int num=list.size();
for (int i=0; i) o,context);
if (context.isExceptional()) return context;
AClosure compiledFn=((Lambda) context.getResult()).getFunction();
fns=fns.conj(compiledFn);
}
MultiFn> mf=MultiFn.create(fns);
Lambda> op = Lambda.create(mf);
return context.withResult(Juice.COMPILE_NODE, op);
}
/**
* Compiles a function instance function form "([...] ...)" to create a Lambda op.
*
* @param list
* @param context
* @return Context with compiled op as result.
*/
@SuppressWarnings("unchecked")
private static Context compileFnInstance(AList list, Context context) {
int n = list.size();
if (n < 1) return context.withArityError("fn requires parameter vector and body in form: " + list);
ACell firstObject = Syntax.unwrap(list.get(0));
if (firstObject instanceof AVector) {
AVector paramsVector=(AVector) firstObject;
AList bodyList=list.drop(1);
return compileFnInstance(paramsVector,bodyList,context);
}
return context.withError(ErrorCodes.COMPILE,
"fn instance requires a vector of parameters but got form: " + list);
}
@SuppressWarnings("unchecked")
private static Context compileFnInstance(AVector paramsVector, AList bodyList,Context context) {
// need to save compiler state, since we are compiling bindings
CompilerState savedCompilerState=context.getCompilerState();
context=compileBinding(paramsVector,context);
if (context.isExceptional()) return context.withCompilerState(savedCompilerState); // restore before return
paramsVector=(AVector) context.getResult();
context = context.compileAll(bodyList);
if (context.isExceptional()) return context.withCompilerState(savedCompilerState); // restore before return
int n=bodyList.size();
AOp> body;
if (n == 0) {
// no body, so function just returns nil
body = Constant.nil();
} else if (n == 1) {
// one body element, so just unwrap from list
body = ((ASequence>) context.getResult()).get(0);
} else {
// wrap multiple expressions in implicit do
body = Do.create(((ASequence>) context.getResult()));
}
Lambda> op = Lambda.create(paramsVector, body);
context=context.withCompilerState(savedCompilerState);
return context.withResult(Juice.COMPILE_NODE, op);
}
private static Context compileDef(AList list, Context context) {
int n = list.size();
if (n != 3) return context.withCompileError("def requires a symbol and an expression, but got: " + list);
ACell symArg=list.get(1);
{// check we are actually defining a symbol
ACell sym = Syntax.unwrapAll(symArg);
if (!(sym instanceof Symbol)) return context.withCompileError("def requires a Symbol as first argument but got: " + RT.getType(sym));
}
ACell exp=list.get(2);
// move metadata from expression. TODO: do we need to expand this first?
if (exp instanceof Syntax) {
symArg=Syntax.create(symArg).mergeMeta(((Syntax)exp).getMeta());
exp=Syntax.unwrap(exp);
}
context = context.compile(exp);
if (context.isExceptional()) return context;
Def> op = Def.create(symArg, context.getResult());
return context.withResult(Juice.COMPILE_NODE, op);
}
/**
* Initial expander used for expansion of forms prior to compilation.
*
* Should work on both raw forms and syntax objects.
*
* Follows the "Expansion-Passing Style" approach of Dybvig, Friedman, and Haynes
*/
public static final AFn INITIAL_EXPANDER =new CoreFn(Symbols.STAR_INITIAL_EXPANDER) {
@SuppressWarnings("unchecked")
@Override
public Context invoke(Context context,ACell[] args ) {
if (args.length!=2) return context.withArityError(exactArityMessage(2, args.length));
ACell x = args[0];
AFn cont=RT.ensureFunction(args[1]);
if (cont==null) return context.withCastError(1, args,Types.FUNCTION);
// If x is a Syntax object, need to compile the datum
// TODO: check interactions with macros etc.
if (x instanceof Syntax) {
Syntax sx=(Syntax)x;
ACell[] nargs=args.clone();
nargs[0]=sx.getValue();
context=context.invoke(this, nargs);
if (context.isExceptional()) return context;
ACell expanded=context.getResult();
Syntax result=Syntax.mergeMeta(expanded,sx);
return context.withResult(Juice.EXPAND_CONSTANT, result);
}
// Check for data structures, which potentially need expansion
if (x instanceof ADataStructure) {
ACell form = x;
// First check for sequences. This covers most cases.
if (form instanceof ASequence) {
// first check for List containing an expander
if (form instanceof AList) {
AList listForm = (AList) form;
int n = listForm.size();
// consider length 0 lists as constant
if (n == 0) return context.withResult(Juice.EXPAND_CONSTANT, x);
// we need to check if the form itself starts with an expander
ACell first = Syntax.unwrap(listForm.get(0));
// check for macro / expander in initial position.
// Note that 'quote' is handled by this, via QUOTE_EXPANDER
AFn expander = context.lookupExpander(first);
if (expander!=null) {
return context.expand(expander,x, cont); // (exp x cont)
}
}
// need to recursively expand collection elements
// OK for vectors and lists
ASequence seq = (ASequence) form;
if (seq.isEmpty()) return context.withResult(Juice.EXPAND_CONSTANT, x);
long n=seq.count();
for (long i=0; i updated = Sets.empty();
for (ACell elem : ((ASet) form)) {
ctx = ctx.expand(cont, elem, cont);
if (ctx.isExceptional()) return ctx;
ACell newElement = ctx.getResult();
updated = updated.conj(newElement);
}
return ctx.withResult(Juice.EXPAND_SEQUENCE, updated);
}
if (form instanceof AMap) {
Context ctx = context;
AMap updated = Maps.empty();
for (Map.Entry me : ((AMap) form).entrySet()) {
// get new key
ctx = ctx.expand(cont,me.getKey(), cont);
if (ctx.isExceptional()) return ctx;
ACell newKey = ctx.getResult();
// get new value
ctx = ctx.expand(cont,me.getValue(), cont);
if (ctx.isExceptional()) return ctx;
ACell newValue = ctx.getResult();
updated = updated.assoc(newKey, newValue);
}
return ctx.withResult(Juice.EXPAND_SEQUENCE, updated);
}
}
// Return the value directly for anything else
return context.withResult(Juice.EXPAND_CONSTANT, x);
}
};
/**
* Expander used for expansion of `quote` forms.
*
* Should work on both raw forms and syntax objects.
*
* Follows the "Expansion-Passing Style" approach of Dybvig, Friedman, and Haynes
*/
public static final AFn QUOTE_EXPANDER =new CoreFn(Symbols.QUOTE) {
@Override
public Context invoke(Context context,ACell[] args ) {
if (args.length!=2) return context.withArityError(exactArityMessage(2, args.length));
ACell x = args[0];
return context.withResult(Juice.EXPAND_CONSTANT,x);
}
};
/**
* Expander used for expansion of `quasiquote` forms.
*
* Should work on both raw forms and syntax objects.
*
* Follows the "Expansion-Passing Style" approach of Dybvig, Friedman, and Haynes
*/
public static final AFn QUASIQUOTE_EXPANDER =new CoreFn(Symbols.QUASIQUOTE) {
@Override
public Context invoke(Context context,ACell[] args ) {
if (args.length!=2) return context.withArityError(exactArityMessage(2, args.length));
ACell x = args[0];
return context.withResult(Juice.EXPAND_CONSTANT,x);
}
};
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy