diff --git a/runtime/src/main/java/dev/ionfusion/fusion/AbstractLetForm.java b/runtime/src/main/java/dev/ionfusion/fusion/AbstractLetForm.java new file mode 100644 index 00000000..4b2f9d7f --- /dev/null +++ b/runtime/src/main/java/dev/ionfusion/fusion/AbstractLetForm.java @@ -0,0 +1,122 @@ +// Copyright Ion Fusion contributors. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package dev.ionfusion.fusion; + +import static dev.ionfusion.fusion.SyntaxSymbol.ensureUniqueIdentifiers; + +import dev.ionfusion.runtime.base.FusionException; +import java.util.ArrayList; + +abstract class AbstractLetForm + extends SyntacticForm +{ + private final boolean myBindingsAreMultiValue; + private final boolean myBindingsAreRecursive; + + protected AbstractLetForm(boolean bindingsAreMultiValue, + boolean bindingsAreRecursive) + { + myBindingsAreMultiValue = bindingsAreMultiValue; + myBindingsAreRecursive = bindingsAreRecursive; + } + + + @Override + SyntaxValue expand(Expander expander, Environment outerEnv, SyntaxSexp stx) + throws FusionException + { + final Evaluator eval = expander.getEvaluator(); + + SyntaxChecker check = check(eval, stx); + check.arityAtLeast(3); + + SyntaxChecker checkBindings = check.subformSeq("sequence of bindings", 1); + SyntaxSequence bindingPairs = checkBindings.form(); + + var binders = collectBinders(eval, checkBindings, bindingPairs); + ensureUniqueIdentifiers(eval, binders, stx); + + var localEnv = LocalEnvironment.forExpansion(outerEnv, binders); + + var subforms = stx.extract(eval); + subforms[1] = expandBindingPairs(expander, outerEnv, localEnv, bindingPairs); + + // TODO #67 Should allow internal definitions + for (int i = 2; i < subforms.length; i++) + { + var bodyForm = localEnv.applyContext(subforms[i]); + subforms[i] = expander.expandExpression(localEnv, bodyForm); + } + + return stx.copyReplacingChildren(eval, subforms); + } + + + SyntaxSymbol[] collectBinders(Evaluator eval, + SyntaxChecker checkPairs, + SyntaxSequence bindingPairs) + throws FusionException + { + int pairCount = bindingPairs.size(eval); + + var binders = new ArrayList(pairCount); + for (int i = 0; i < pairCount; i++) + { + var checkPair = checkPairs.subformSexp("binding pair", i); + checkPair.arityExact(2); + + if (myBindingsAreMultiValue) + { + var checkIds = checkPair.subformSexp("binding names", 0); + int size = checkIds.form().size(eval); + for (int j = 0; j < size; j++) + { + var binder = checkIds.requiredIdentifier("bound name", j); + binders.add(binder); + } + } + else + { + var binder = checkPair.requiredIdentifier("bound name", 0); + binders.add(binder); + } + } + + return binders.toArray(SyntaxSymbol.EMPTY_ARRAY); + } + + + SyntaxSequence expandBindingPairs(Expander expander, + Environment outerEnv, + LocalEnvironment localEnv, + SyntaxSequence bindingPairs) + throws FusionException + { + var eval = expander.getEvaluator(); + + var pairs = bindingPairs.extract(eval); + for (int i = 0; i < pairs.length; i++) + { + var bindingPair = (SyntaxSexp) bindingPairs.get(eval, i); + + var binders = bindingPair.get(eval, 0); + var expr = bindingPair.get(eval, 1); + + // Binders always get the local context so they resolve properly. + binders = localEnv.applyContext(binders); + + // Value expressions only get the local context for recursive forms. + if (myBindingsAreRecursive) + { + expr = localEnv.applyContext(expr); + } + + expr = expander.expandExpression(outerEnv, expr); + + pairs[i] = bindingPair.copyReplacingChildren(eval, binders, expr); + } + + return bindingPairs.copyReplacingChildren(eval, pairs); + } +} diff --git a/runtime/src/main/java/dev/ionfusion/fusion/LambdaForm.java b/runtime/src/main/java/dev/ionfusion/fusion/LambdaForm.java index c2cd28a3..e1ef598d 100644 --- a/runtime/src/main/java/dev/ionfusion/fusion/LambdaForm.java +++ b/runtime/src/main/java/dev/ionfusion/fusion/LambdaForm.java @@ -25,61 +25,31 @@ SyntaxValue expand(Expander expander, Environment env, SyntaxSexp stx) SyntaxChecker check = check(eval, stx); int arity = check.arityAtLeast(3); - var children = stx.extract(eval); + var subforms = stx.extract(eval); - int bodyStart; - var maybeDoc = children[2]; - if (isString(eval, maybeDoc.unwrap(eval)) && arity > 3) - { - bodyStart = 3; - } - else - { - bodyStart = 2; - } + var maybeDoc = subforms[2]; + int bodyStart = ((isString(eval, maybeDoc.unwrap(eval)) && arity > 3) ? 3 : 2); - var formals = children[1]; - var args = flattenFormals(eval, check, formals); + var binders = collectBinders(eval, check, subforms[1]); + ensureUniqueIdentifiers(eval, binders, stx); - LocalEnvironment localEnv = null; - if (args.length != 0) - { - ensureUniqueIdentifiers(eval, args, stx); - env = localEnv = LocalEnvironment.forExpansion(env, args); - } + var localEnv = LocalEnvironment.forExpansion(env, binders); // Prepare the bound names so they resolve to their own binding. - for (int i = 0; i < args.length; i++) - { - args[i] = (SyntaxSymbol) args[i].addWrap(localEnv.getWrap()); - } - - if (formals instanceof SyntaxSymbol) - { - children[1] = args[0]; - } - else - { - children[1] = ((SyntaxSexp) formals).copyReplacingChildren(eval, args); - } + subforms[1] = localEnv.applyContext(subforms[1]); // TODO #67 Should allow internal definitions - for (int i = bodyStart; i < children.length; i++) + for (int i = bodyStart; i < subforms.length; i++) { - SyntaxValue bodyForm = children[i]; - if (localEnv != null) - { - bodyForm = bodyForm.addWrap(localEnv.getWrap()); - } - bodyForm = expander.expandExpression(env, bodyForm); - children[i] = bodyForm; + var bodyForm = localEnv.applyContext(subforms[i]); + subforms[i] = expander.expandExpression(localEnv, bodyForm); } - return stx.copyReplacingChildren(eval, children); + return stx.copyReplacingChildren(eval, subforms); } - private static SyntaxSymbol[] flattenFormals(Evaluator eval, + private static SyntaxSymbol[] collectBinders(Evaluator eval, SyntaxChecker lambdaCheck, SyntaxValue formals) throws FusionException @@ -262,7 +232,7 @@ Object doApply(Evaluator eval, Object[] args) { checkArityExact(eval, 0, args); - // No local store is created to wrap myEnclosure! + // No local bindings, so no local store. return eval.bounceTailForm(myEnclosure, myBody); } } diff --git a/runtime/src/main/java/dev/ionfusion/fusion/LetValuesForm.java b/runtime/src/main/java/dev/ionfusion/fusion/LetValuesForm.java index 7f34ca6b..ea8d72a3 100644 --- a/runtime/src/main/java/dev/ionfusion/fusion/LetValuesForm.java +++ b/runtime/src/main/java/dev/ionfusion/fusion/LetValuesForm.java @@ -7,120 +7,17 @@ import static dev.ionfusion.fusion.FusionSexp.unsafePairHead; import static dev.ionfusion.fusion.FusionSexp.unsafePairTail; import static dev.ionfusion.fusion.ResultFailure.makeResultError; -import static dev.ionfusion.fusion.SyntaxSymbol.ensureUniqueIdentifiers; import dev.ionfusion.commons.resources.ResourcePosition; import dev.ionfusion.runtime.base.FusionException; import java.util.ArrayList; final class LetValuesForm - extends SyntacticForm + extends AbstractLetForm { - @Override - SyntaxValue expand(Expander expander, Environment env, SyntaxSexp stx) - throws FusionException + LetValuesForm() { - final Evaluator eval = expander.getEvaluator(); - - SyntaxChecker check = check(eval, stx); - final int letExprSize = check.arityAtLeast(3); - - SyntaxChecker checkBindings = - check.subformSeq("sequence of bindings", 1); - SyntaxSequence bindingForms = checkBindings.form(); - - final int numBindingForms = bindingForms.size(eval); - - // Gather the bound names - ArrayList boundNameList = - new ArrayList<>(numBindingForms); - for (int i = 0; i < numBindingForms; i++) - { - SyntaxChecker checkPair = - checkBindings.subformSexp("binding pair", i); - checkPair.arityExact(2); - - SyntaxChecker checkBoundNames = - checkPair.subformSexp("binding name sequence", 0); - - int size = checkBoundNames.form().size(eval); - for (int j = 0; j < size; j++) - { - SyntaxSymbol name = - checkBoundNames.requiredIdentifier("binding name", j); - boundNameList.add(name); - } - } - - - SyntaxSymbol[] boundNames; - Environment bodyEnv; - SyntaxWrap localWrap; - - final int bindingCount = boundNameList.size(); - if (bindingCount == 0) - { - boundNames = null; - bodyEnv = env; - localWrap = null; - } - else - { - boundNames = boundNameList.toArray(SyntaxSymbol.EMPTY_ARRAY); - ensureUniqueIdentifiers(eval, boundNames, stx); - var localEnv = LocalEnvironment.forExpansion(env, boundNames); - localWrap = localEnv.getWrap(); - bodyEnv = localEnv; - } - - // Expand the bound-value expressions - SyntaxValue[] expandedForms = new SyntaxValue[numBindingForms]; - int bindingPos = 0; - for (int i = 0; i < numBindingForms; i++) - { - // Already type- and arity-checked this above - SyntaxSexp binding = (SyntaxSexp) bindingForms.get(eval, i); - - SyntaxSexp names = (SyntaxSexp) binding.get(eval, 0); - SyntaxValue[] wrappedNames = names.extract(eval); - int size = names.size(eval); - for (int j = 0; j < size; j++) - { - // Wrap the bound names so they resolve to their own binding. - SyntaxSymbol name = boundNames[bindingPos]; - assert name == wrappedNames[j]; - - wrappedNames[j] = name.addWrap(localWrap); - bindingPos++; - } - names = names.copyReplacingChildren(eval, wrappedNames); - - SyntaxValue boundExpr = binding.get(eval, 1); - boundExpr = expander.expandExpression(env, boundExpr); - expandedForms[i] = - binding.copyReplacingChildren(eval, names, boundExpr); - } - assert bindingPos == bindingCount; - - bindingForms = bindingForms.copyReplacingChildren(eval, expandedForms); - - expandedForms = new SyntaxValue[letExprSize]; - expandedForms[0] = stx.get(eval, 0); - expandedForms[1] = bindingForms; - - // TODO Should allow internal definitions - // https://github.com/ion-fusion/fusion-java/issues/67 - for (int i = 2; i < letExprSize; i++) - { - SyntaxValue subform = stx.get(eval, i); - if (localWrap != null) - { - subform = subform.addWrap(localWrap); - } - expandedForms[i] = expander.expandExpression(bodyEnv, subform); - } - - return stx.copyReplacingChildren(eval, expandedForms); + super(/* multivalue? */ true, /* recursive? */ false); } diff --git a/runtime/src/main/java/dev/ionfusion/fusion/LetrecForm.java b/runtime/src/main/java/dev/ionfusion/fusion/LetrecForm.java index 4fec7336..5b00bcf4 100644 --- a/runtime/src/main/java/dev/ionfusion/fusion/LetrecForm.java +++ b/runtime/src/main/java/dev/ionfusion/fusion/LetrecForm.java @@ -4,73 +4,17 @@ package dev.ionfusion.fusion; import static dev.ionfusion.fusion.FusionValue.UNDEF; -import static dev.ionfusion.fusion.SyntaxSymbol.ensureUniqueIdentifiers; import dev.ionfusion.commons.resources.ResourcePosition; import dev.ionfusion.runtime.base.FusionException; import java.util.Arrays; final class LetrecForm - extends SyntacticForm + extends AbstractLetForm { - @Override - SyntaxValue expand(Expander expander, Environment env, SyntaxSexp stx) - throws FusionException + LetrecForm() { - final Evaluator eval = expander.getEvaluator(); - - SyntaxChecker check = check(eval, stx); - final int letrecExprSize = check.arityAtLeast(3); - - SyntaxChecker checkBindings = - check.subformSeq("sequence of bindings", 1); - SyntaxSequence bindingForms = checkBindings.form(); - - final int numBindings = bindingForms.size(eval); - SyntaxSymbol[] boundNames = new SyntaxSymbol[numBindings]; - for (int i = 0; i < numBindings; i++) - { - SyntaxChecker checkPair = - checkBindings.subformSexp("binding pair", i); - checkPair.arityExact(2); - boundNames[i] = checkPair.requiredIdentifier("bound name", 0); - } - ensureUniqueIdentifiers(eval, boundNames, stx); - - var bodyEnv = LocalEnvironment.forExpansion(env, boundNames); - SyntaxWrap localWrap = bodyEnv.getWrap(); - - // Expand the bound-value expressions - SyntaxValue[] expandedForms = new SyntaxValue[numBindings]; - for (int i = 0; i < numBindings; i++) - { - // Wrap the bound names so they resolve to their own binding. - SyntaxSymbol name = (SyntaxSymbol) boundNames[i].addWrap(localWrap); - - // Already type- and arity-checked this above - SyntaxSexp binding = (SyntaxSexp) bindingForms.get(eval, i); - SyntaxValue boundExpr = binding.get(eval, 1); - boundExpr = boundExpr.addWrap(localWrap); - boundExpr = expander.expandExpression(bodyEnv, boundExpr); - expandedForms[i] = - binding.copyReplacingChildren(eval, name, boundExpr); - } - - bindingForms = bindingForms.copyReplacingChildren(eval, expandedForms); - - expandedForms = new SyntaxValue[letrecExprSize]; - expandedForms[0] = stx.get(eval, 0); - expandedForms[1] = bindingForms; - - // TODO #67 Should allow internal definitions - for (int i = 2; i < letrecExprSize; i++) - { - SyntaxValue subform = stx.get(eval, i); - subform = subform.addWrap(localWrap); - expandedForms[i] = expander.expandExpression(bodyEnv, subform); - } - - return stx.copyReplacingChildren(eval, expandedForms); + super(/* multivalue? */ false, /* recursive? */ true); } diff --git a/runtime/src/main/java/dev/ionfusion/fusion/LocalEnvironment.java b/runtime/src/main/java/dev/ionfusion/fusion/LocalEnvironment.java index 973ca570..9f913f91 100644 --- a/runtime/src/main/java/dev/ionfusion/fusion/LocalEnvironment.java +++ b/runtime/src/main/java/dev/ionfusion/fusion/LocalEnvironment.java @@ -190,9 +190,11 @@ public Namespace namespace() return myEnclosure.namespace(); } - public SyntaxWrap getWrap() + + @SuppressWarnings("unchecked") + public > T applyContext(T stx) { - return myWrap; + return (T) stx.addWrap(myWrap); }