All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.jlangch.venice.impl.VeniceInterpreter Maven / Gradle / Ivy

/*   __    __         _
 *   \ \  / /__ _ __ (_) ___ ___
 *    \ \/ / _ \ '_ \| |/ __/ _ \
 *     \  /  __/ | | | | (_|  __/
 *      \/ \___|_| |_|_|\___\___|
 *
 *
 * Copyright 2017-2024 Venice
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.jlangch.venice.impl;

import static com.github.jlangch.venice.impl.debug.breakpoint.FunctionScope.FunctionEntry;
import static com.github.jlangch.venice.impl.types.Constants.Nil;
import static com.github.jlangch.venice.impl.types.VncFunction.createAnonymousFuncName;
import static com.github.jlangch.venice.impl.util.ArityExceptions.assertArity;
import static com.github.jlangch.venice.impl.util.ArityExceptions.assertMinArity;

import java.io.PrintStream;
import java.io.Reader;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;

import com.github.jlangch.venice.IServiceRegistry;
import com.github.jlangch.venice.NotInTailPositionException;
import com.github.jlangch.venice.SecurityException;
import com.github.jlangch.venice.Version;
import com.github.jlangch.venice.VncException;
import com.github.jlangch.venice.impl.debug.agent.DebugAgent;
import com.github.jlangch.venice.impl.debug.breakpoint.BreakpointFnRef;
import com.github.jlangch.venice.impl.env.ComputedVar;
import com.github.jlangch.venice.impl.env.Env;
import com.github.jlangch.venice.impl.env.Var;
import com.github.jlangch.venice.impl.functions.CoreFunctions;
import com.github.jlangch.venice.impl.functions.Functions;
import com.github.jlangch.venice.impl.functions.TransducerFunctions;
import com.github.jlangch.venice.impl.modules.Modules;
import com.github.jlangch.venice.impl.namespaces.Namespace;
import com.github.jlangch.venice.impl.namespaces.NamespaceRegistry;
import com.github.jlangch.venice.impl.namespaces.Namespaces;
import com.github.jlangch.venice.impl.specialforms.SpecialForms_LoadCodeMacros;
import com.github.jlangch.venice.impl.specialforms.SpecialForms_OtherFunctions;
import com.github.jlangch.venice.impl.specialforms.util.SpecialFormsContext;
import com.github.jlangch.venice.impl.thread.ThreadContext;
import com.github.jlangch.venice.impl.types.IVncFunction;
import com.github.jlangch.venice.impl.types.VncBoolean;
import com.github.jlangch.venice.impl.types.VncFunction;
import com.github.jlangch.venice.impl.types.VncJavaObject;
import com.github.jlangch.venice.impl.types.VncKeyword;
import com.github.jlangch.venice.impl.types.VncMultiArityFunction;
import com.github.jlangch.venice.impl.types.VncScalar;
import com.github.jlangch.venice.impl.types.VncSpecialForm;
import com.github.jlangch.venice.impl.types.VncString;
import com.github.jlangch.venice.impl.types.VncSymbol;
import com.github.jlangch.venice.impl.types.VncVal;
import com.github.jlangch.venice.impl.types.collections.VncLazySeq;
import com.github.jlangch.venice.impl.types.collections.VncList;
import com.github.jlangch.venice.impl.types.collections.VncMap;
import com.github.jlangch.venice.impl.types.collections.VncMapEntry;
import com.github.jlangch.venice.impl.types.collections.VncMutableSet;
import com.github.jlangch.venice.impl.types.collections.VncSequence;
import com.github.jlangch.venice.impl.types.collections.VncSet;
import com.github.jlangch.venice.impl.types.collections.VncVector;
import com.github.jlangch.venice.impl.types.util.Coerce;
import com.github.jlangch.venice.impl.types.util.Types;
import com.github.jlangch.venice.impl.util.ArityExceptions.FnType;
import com.github.jlangch.venice.impl.util.CollectionUtil;
import com.github.jlangch.venice.impl.util.MeterRegistry;
import com.github.jlangch.venice.impl.util.callstack.CallFrame;
import com.github.jlangch.venice.impl.util.callstack.CallFrameFnData;
import com.github.jlangch.venice.impl.util.callstack.CallStack;
import com.github.jlangch.venice.impl.util.callstack.WithCallStack;
import com.github.jlangch.venice.javainterop.AcceptAllInterceptor;
import com.github.jlangch.venice.javainterop.IInterceptor;
import com.github.jlangch.venice.util.ImmutableServiceDiscovery;

/**
 * The Venice interpreter runs the scripts and handles the special forms
 *
 *
 * 

Tail recursion (loop-recur): *

 *   +----------------+-------------------------------------------+---------------+
 *   | Form           | Tail Position                             | recur target? |
 *   +----------------+-------------------------------------------+---------------+
 *   | fn, defn       | (fn [args] expressions tail)              | No            |
 *   | loop           | (loop [bindings] expressions tail)        | Yes           |
 *   | let            | (let [bindings] expressions tail)         | No            |
 *   | do             | (do expressions tail)                     | No            |
 *   | if, if-not     | (if test then tail else tail)             | No            |
 *   | when, when-not | (when test expressions tail)              | No            |
 *   | cond           | (cond test tail ... :else else tail)      | No            |
 *   | case           | (case const tail ... default tail)        | No            |
 *   | or, and        | (or test test ... tail)                   | No            |
 *   +----------------+-------------------------------------------+---------------+
 * 
*/ public class VeniceInterpreter implements IVeniceInterpreter, Serializable { public VeniceInterpreter( final IInterceptor interceptor ) { this(interceptor, null, null); } public VeniceInterpreter( final IInterceptor interceptor, final MeterRegistry meterRegistry, final IServiceRegistry serviceRegistry ) { if (interceptor == null) { throw new SecurityException("VeniceInterpreter can not run without an interceptor"); } final MeterRegistry mr = meterRegistry == null ? new MeterRegistry(false) : meterRegistry; this.interceptor = interceptor; this.meterRegistry = mr; this.serviceRegistry = serviceRegistry == null ? new ServiceRegistry() : serviceRegistry; this.nsRegistry = new NamespaceRegistry(); this.sealedSystemNS = new AtomicBoolean(false); // performance optimization this.checkSandbox = !(interceptor instanceof AcceptAllInterceptor); this.optimized = false; // no callstack, no auto TCO, meter, checks, ... this.functionBuilder = new FunctionBuilder(this::evaluate, this.optimized); this.specialFormsContext= new SpecialFormsContext( this, this::evaluate, this::evaluate_values, this::evaluate_sequence_values, this.functionBuilder, this.interceptor, this.nsRegistry, this.meterRegistry, this.sealedSystemNS); ThreadContext.setInterceptor(interceptor); ThreadContext.setMeterRegistry(mr); if (optimized && checkSandbox) { // invalid combination: prevent security problems with partially deactivated sandboxes throw new VncException("Venice interpreter supports optimized mode only with AcceptAllInterceptor!"); } } @Override public void initNS() { nsRegistry.clear(); Namespaces.setCurrentNamespace(nsRegistry.computeIfAbsent(Namespaces.NS_USER)); } @Override public void presetNS(final NamespaceRegistry nsRegistry) { final VncSymbol ns = Namespaces.getCurrentNS(); if (nsRegistry != null) { this.nsRegistry.add(nsRegistry); Namespaces.setCurrentNamespace(nsRegistry.computeIfAbsent(ns)); } } @Override public void sealSystemNS() { sealedSystemNS.set(true); } @Override public void setMacroExpandOnLoad(final boolean macroExpandOnLoad) { // Dynamically turn on/off macroexpand-on-load. The REPL makes use of this. this.macroExpandOnLoad = macroExpandOnLoad; } @Override public boolean isMacroExpandOnLoad() { return macroExpandOnLoad; } @Override public boolean isEvaluateDynamicallyLoadedCode() { return true; } @Override public VncVal READ(final String script, final String filename) { if (meterRegistry.enabled) { final long nanos = System.nanoTime(); final VncVal val = com.github.jlangch.venice.impl.reader.Reader.read_str(script, filename); meterRegistry.record("venice.read", System.nanoTime() - nanos); return val; } else { return com.github.jlangch.venice.impl.reader.Reader.read_str(script, filename); } } @Override public VncVal EVAL(final VncVal ast, final Env env) { if (meterRegistry.enabled) { final long nanos = System.nanoTime(); final VncVal val = evaluate(ast, env, false); meterRegistry.record("venice.eval", System.nanoTime() - nanos); return val; } else { return evaluate(ast, env, false); } } @Override public VncVal MACROEXPAND(final VncVal ast, final Env env) { return macroexpand_all( new CallFrame("macroexpand-all", ast.getMeta()), ast, env); } @Override public VncVal RE( final String script, final String name, final Env env ) { VncVal ast = READ(script, name); if (macroExpandOnLoad) { ast = MACROEXPAND(ast, env); } return EVAL(ast, env); } @Override public String PRINT(final VncVal exp) { return Printer.pr_str(exp, true); } @Override public Env createEnv( final boolean macroexpandOnLoad, final boolean ansiTerminal, final RunMode runMode ) { return createEnv( null, macroexpandOnLoad, ansiTerminal, runMode, null, null, null); } @Override public Env createEnv( final List preloadedExtensionModules, final boolean macroExpandOnLoad, final boolean ansiTerminal, final RunMode runMode ) { return createEnv( preloadedExtensionModules, macroExpandOnLoad, ansiTerminal, runMode, null, null, null); } @Override public Env createEnv( final List preloadedExtensionModules, final boolean macroExpandOnLoad, final boolean ansiTerminal, final RunMode runMode, final PrintStream stdOut, final PrintStream stdErr, final Reader stdIn ) { final CodeLoader codeLoader = new CodeLoader(); sealedSystemNS.set(false); final Env env = new Env(null); for(Map.Entry e: Functions.functions.entrySet()) { final VncSymbol sym = (VncSymbol)e.getKey(); final VncVal val = e.getValue(); if (val instanceof VncFunction) { final VncFunction fn = (VncFunction)e.getValue(); env.setGlobal(new Var(sym, fn, fn.isRedefinable(), Var.Scope.Global)); } else if (val instanceof VncSpecialForm) { env.setGlobal(new Var(sym, val, false, Var.Scope.Global)); // not redefinable } else { env.setGlobal(new Var(sym, val, true, Var.Scope.Global)); } } // set Venice version env.setGlobal(new Var(new VncSymbol("*version*"), new VncString(Version.VERSION), false, Var.Scope.Global)); // set current namespace env.setGlobal(new ComputedVar(new VncSymbol("*ns*"), () -> Namespaces.getCurrentNS(), false, Var.Scope.Global)); // set system newline env.setGlobal(new Var(new VncSymbol("*newline*"), new VncString(System.lineSeparator()), false, Var.Scope.Global)); // ansi terminal (set when run from a REPL in an ANSI terminal) env.setGlobal(new Var(new VncSymbol("*ansi-term*"), VncBoolean.of(ansiTerminal), false, Var.Scope.Global)); // set the run mode env.setGlobal(new Var(new VncSymbol("*run-mode*"), runMode == null ? Nil : runMode.mode, false, Var.Scope.Global)); // command line args (default nil) env.setGlobal(new Var(new VncSymbol("*ARGV*"), Nil, true, Var.Scope.Global)); // service discovery (lookup access on the service registry only) final VncJavaObject servicRegistry = new VncJavaObject(new ImmutableServiceDiscovery(serviceRegistry)); env.setGlobal(new Var(new VncSymbol("*service-registry*"), servicRegistry, false, Var.Scope.Global)); // loaded modules & files final VncMutableSet loadedModules = new VncMutableSet(); env.setGlobal(new Var(new VncSymbol("*loaded-modules*"), loadedModules, true, Var.Scope.Global)); env.setGlobal(new Var(new VncSymbol("*loaded-files*"), new VncMutableSet(), true, Var.Scope.Global)); if (stdOut != null) { env.setStdoutPrintStream(stdOut); } if (stdErr != null) { env.setStderrPrintStream(stdErr); } if (stdIn != null) { env.setStdinReader(stdIn); } // init namespaces initNS(); // Activates macroexpand on load setMacroExpandOnLoad(macroExpandOnLoad); // add all native modules (implicitly preloaded) loadedModules.addAll(VncMutableSet.ofAll(Modules.NATIVE_MODULES)); // load core modules codeLoader.loadModule(new VncKeyword("core"), this, null, env, false, null); // security: seal system namespaces (Namespaces::SYSTEM_NAMESPACES) - no further changes allowed! sealedSystemNS.set(true); // load other modules requested for preload CollectionUtil.toEmpty(preloadedExtensionModules) .forEach(m -> codeLoader.loadModule(new VncKeyword(m), this, null, env, false, null)); // set current namespace to 'user' after loading modules Namespaces.setCurrentNamespace(nsRegistry.computeIfAbsent(Namespaces.NS_USER)); return env; } @Override public List getAvailableModules() { final List modules = new ArrayList<>(Modules.VALID_MODULES); modules.removeAll(Arrays.asList("core", "test-support", "http", "jackson")); Collections.sort(modules); return modules; } @Override public IInterceptor getInterceptor() { return interceptor; } @Override public MeterRegistry getMeterRegistry() { return meterRegistry; } @Override public NamespaceRegistry getNamespaceRegistry() { return nsRegistry; } private VncVal evaluate( final VncVal ast_, final Env env_, final boolean inTailPosition ) { VncVal orig_ast = ast_; Env env = env_; RecursionPoint recursionPoint = null; boolean tailPosition = inTailPosition; while (true) { // System.out.println("EVAL: " + Types.getType(orig_ast) + " > " + orig_ast.toString(true)); if (ast_ == Nil) return Nil; if (!(orig_ast instanceof VncList)) { // not an s-expr return evaluate_values(orig_ast, env); } final VncList ast = (VncList)orig_ast; if (ast.isEmpty()) { return ast; } final VncVal a0 = ast.first(); final VncVal a0meta = a0.getMeta(); final String a0sym = (a0 instanceof VncSymbol) ? ((VncSymbol)a0).getName() : "__<*fn*>__"; final VncList args = ast.rest(); // special form / function dispatcher switch (a0sym) { case "do": { // (do expr*) final VncList expressions = args; evaluate_sequence_values(expressions.butlast(), env); orig_ast = expressions.last(); tailPosition = true; } break; case "if": { // (if cond expr-true expr-false*) final int numArgs = args.size(); if (numArgs == 2 || numArgs == 3) { final VncVal cond = evaluate(args.first(), env, false); if (!optimized) { final ThreadContext threadCtx = ThreadContext.get(); final DebugAgent debugAgent = threadCtx.getDebugAgent_(); if (debugAgent != null && debugAgent.hasBreakpointFor(BreakpointFnRef.IF)) { debugAgent.onBreakSpecialForm( "if", FunctionEntry, VncVector.of(new VncString("cond")), VncList.of(cond), a0meta, env, threadCtx.getCallStack_()); } } orig_ast = VncBoolean.isFalseOrNil(cond) ? args.third() // eval false slot form (nil if not available) : args.second(); // eval true slot form tailPosition = true; } else { // only create callstack when needed! final CallFrame cf = new CallFrame("if", args, a0meta); try (WithCallStack cs = new WithCallStack(cf)) { assertArity("if", FnType.SpecialForm, args, 2, 3); } } } break; case "let": { // (let [bindings*] exprs*) if (args.isEmpty()) { // only create callstack when needed! final CallFrame cf = new CallFrame("let", args, a0meta); try (WithCallStack cs = new WithCallStack(cf)) { assertMinArity("let", FnType.SpecialForm, args, 1); } } env = new Env(env); // let introduces a new environment final ThreadContext threadCtx = ThreadContext.get(); final DebugAgent debugAgent = threadCtx.getDebugAgent_(); final VncVector bindings = Coerce.toVncVector(args.first()); final VncList expressions = args.rest(); if (bindings.size() % 2 != 0) { final CallFrame cf = new CallFrame("let", args, a0meta); try (WithCallStack cs = new WithCallStack(cf)) { throw new VncException("let requires an even number of forms in the binding vector!"); } } final Iterator bindingsIter = bindings.iterator(); final List vars = new ArrayList<>(); while(bindingsIter.hasNext()) { final VncVal sym = bindingsIter.next(); final boolean symIsSymbol = sym instanceof VncSymbol; if (symIsSymbol && ((VncSymbol)sym).hasNamespace()) { final VncSymbol s = (VncSymbol)sym; final CallFrame cf = new CallFrame(s.getQualifiedName(), args, s.getMeta()); try (WithCallStack cs = new WithCallStack(cf)) { throw new VncException("Can't use qualified symbols with let!"); } } final VncVal val = evaluate(bindingsIter.next(), env, false); if (symIsSymbol) { // optimized with plain symbol when destructuring is not used env.setLocal(new Var((VncSymbol)sym, val, Var.Scope.Local)); if (debugAgent != null) { vars.add(new Var((VncSymbol)sym, val, Var.Scope.Local)); } } else { final List varTmp = Destructuring.destructure(sym, val); env.addLocalVars(varTmp); if (debugAgent != null) { vars.addAll(varTmp); } } } if (debugAgent != null && debugAgent.hasBreakpointFor(BreakpointFnRef.LET)) { final CallStack callStack = threadCtx.getCallStack_(); debugAgent.onBreakSpecialForm( "let", FunctionEntry, vars, a0meta, env, callStack); } if (expressions.size() == 1) { orig_ast = expressions.first(); } else { evaluate_sequence_values(expressions.butlast(), env); orig_ast = expressions.last(); } tailPosition = true; } break; case "loop": { // (loop [bindings*] exprs*) recursionPoint = null; if (args.size() < 2) { // only create callstack when needed! final CallFrame cf = new CallFrame("loop", args, a0meta); try (WithCallStack cs = new WithCallStack(cf)) { assertMinArity("loop", FnType.SpecialForm, args, 2); } } env = new Env(env); final VncVector bindings = Coerce.toVncVector(args.first()); final VncList expressions = args.rest(); if (bindings.size() % 2 != 0) { final CallFrame cf = new CallFrame("loop", args, a0meta); try (WithCallStack cs = new WithCallStack(cf)) { throw new VncException("loop requires an even number of forms in the binding vector!"); } } final List bindingNames = new ArrayList<>(bindings.size() / 2); final Iterator bindingsIter = bindings.iterator(); while(bindingsIter.hasNext()) { final VncVal symVal = bindingsIter.next(); final VncVal bindVal = evaluate(bindingsIter.next(), env, false); bindingNames.add(symVal); RecursionPoint.addToLocalEnv(symVal, bindVal, env); } final ThreadContext threadCtx = ThreadContext.get(); final DebugAgent debugAgent = threadCtx.getDebugAgent_(); recursionPoint = new RecursionPoint( bindingNames, expressions, env, a0meta, debugAgent); if (debugAgent != null && debugAgent.hasBreakpointFor(BreakpointFnRef.LOOP)) { final CallStack cs = threadCtx.getCallStack_(); debugAgent.onBreakLoop(FunctionEntry, recursionPoint, env, cs); } if (expressions.size() == 1) { orig_ast = expressions.first(); } else { evaluate_sequence_values(expressions.butlast(), env); orig_ast = expressions.last(); } tailPosition = true; } break; case "recur": { // (recur exprs*) // Note: (recur) is valid, it's used by the while macro if (recursionPoint == null) { final CallFrame cf = new CallFrame("recur", args, a0meta); try (WithCallStack cs = new WithCallStack(cf)) { throw new NotInTailPositionException( "The recur expression is not in tail position!"); } } if (args.size() != recursionPoint.getLoopBindingNamesCount()) { final CallFrame cf = new CallFrame("recur", args, a0meta); try (WithCallStack cs = new WithCallStack(cf)) { throw new VncException(String.format( "The recur args (%d) do not match the loop args (%d) !", args.size(), recursionPoint.getLoopBindingNamesCount())); } } env = buildRecursionEnv(args, env, recursionPoint); // for performance reasons the DebugAgent is stored in the // RecursionPoint. This saves repeated ThreadLocal access! if (recursionPoint.isDebuggingActive()) { final DebugAgent debugAgent = recursionPoint.getDebugAgent(); if (debugAgent.hasBreakpointFor(BreakpointFnRef.LOOP)) { final CallStack cs = ThreadContext.getCallStack(); debugAgent.onBreakLoop(FunctionEntry, recursionPoint, env, cs); } } final VncList expressions = recursionPoint.getLoopExpressions(); if (expressions.size() == 1) { orig_ast = expressions.first(); } else { evaluate_sequence_values(expressions.butlast(), env); orig_ast = expressions.last(); } tailPosition = true; } break; case "quasiquote": // (quasiquote form) orig_ast = SpecialForms_OtherFunctions.quasiquote.apply(a0meta, args, env, specialFormsContext); break; case "macroexpand": // (macroexpand form) return macroexpand(args, env, a0meta); case "macroexpand-all*": // (macroexpand-all* form) // Note: This special form is exposed through the public Venice // function 'core/macroexpand-all' in the 'core' module. return macroexpand_all( new CallFrame("macroexpand-all*", args, a0meta), evaluate(args.first(), env, false), env); case "tail-pos": return tail_pos_check_(tailPosition, args, env, a0meta); default: { // special forms, functions, macros, collections/keywords as functions final VncVal fn0 = a0 instanceof VncSymbol ? env.get((VncSymbol)a0) : evaluate(a0, env, false); // ((resolve '+) 1 2) if (fn0 instanceof VncSpecialForm) { final VncSpecialForm sf = (VncSpecialForm)fn0; if (sf.addCallFrame()) { final CallFrame callframe = new CallFrame(sf.getName(), args, a0meta); try (WithCallStack cs = new WithCallStack(callframe)) { return sf.apply(a0meta, args, env, specialFormsContext); } } else { return sf.apply(a0meta, args, env, specialFormsContext); } } else if (fn0 instanceof VncFunction) { final VncFunction fn = (VncFunction)fn0; if (fn.isMacro()) { // macro final VncVal expandedAst = doMacroexpand(ast, env); if (expandedAst instanceof VncList) { orig_ast = expandedAst; continue; } else { return evaluate_values(expandedAst, env); // not an s-expr } } else { final String fnName = fn.getQualifiedName(); if (optimized) { // evaluate function args final VncList fnArgs = (VncList)evaluate_sequence_values(args, env); return fn.apply(fnArgs); } else { final ThreadContext threadCtx = ThreadContext.get(); final CallStack callStack = threadCtx.getCallStack_(); final DebugAgent debugAgent = threadCtx.getDebugAgent_(); if (debugAgent != null && debugAgent.hasBreakpointFor(new BreakpointFnRef(fnName))) { debugAgent.onBreakFnCall(fnName, fn, args, env, callStack); } // evaluate function args final VncList fnArgs = (VncList)evaluate_sequence_values(args, env); final long nanos = meterRegistry.enabled ? System.nanoTime() : 0L; // validate function call allowed by sandbox if (checkSandbox) { final CallFrame cf = new CallFrame(fnName, fnArgs, a0meta, env); try (WithCallStack cs = new WithCallStack(cf)) { interceptor.validateVeniceFunction(fnName); } interceptor.validateMaxExecutionTime(); } final Thread currThread = Thread.currentThread(); InterruptChecker.checkInterrupted(currThread, fn); // Automatic TCO (tail call optimization) if (tailPosition && !fn.isNative() // native functions do not have an AST body && !callStack.isEmpty() && fnName.equals(callStack.peek().getFnName()) ) { // fn may be a normal function, a multi-arity, or a multi-method function final VncFunction effFn = fn.getFunctionForArgs(fnArgs); env.addLocalVars(Destructuring.destructure(effFn.getParams(), fnArgs)); if (debugAgent != null && debugAgent.hasBreakpointFor(new BreakpointFnRef(fnName))) { debugAgent.onBreakFnEnter(fnName, effFn, fnArgs, env, callStack); } final VncList body = (VncList)effFn.getBody(); evaluate_sequence_values(body.butlast(), env); orig_ast = body.last(); } else { // invoke function with a new call frame try { if (fn.isNative()) { callStack.push(new CallFrame(fnName, fnArgs, a0meta, env)); if (debugAgent != null && debugAgent.hasBreakpointFor(new BreakpointFnRef(fnName))) { // Debugging handled for native functions only. env.setLocal(new Var(new VncSymbol("debug::fn-args"), fnArgs, Var.Scope.Local)); try { debugAgent.onBreakFnEnter(fnName, fn, fnArgs, env, callStack); final VncVal retVal = fn.apply(fnArgs); debugAgent.onBreakFnExit(fnName, fn, fnArgs, retVal, env, callStack); return retVal; } catch(Exception ex) { debugAgent.onBreakFnException(fnName, fn, fnArgs, ex, env, callStack); throw ex; } } else { return fn.apply(fnArgs); } } else { // Debugging for non native functions is handled in the // implementation of VncFunction::apply. See the builder // FunctionBuilder::buildFunction(..) threadCtx.setCallFrameFnData_(new CallFrameFnData(fnName, a0meta)); return fn.apply(fnArgs); } } finally { threadCtx.setCallFrameFnData_(null); if (fn.isNative()) { callStack.pop(); } InterruptChecker.checkInterrupted(currThread, fn); if (checkSandbox) { interceptor.validateMaxExecutionTime(); } if (meterRegistry.enabled) { final long elapsed = System.nanoTime() - nanos; if (fn instanceof VncMultiArityFunction) { final VncFunction f = fn.getFunctionForArgs(fnArgs); meterRegistry.record(fnName, f.getParams().size(), elapsed); } else { meterRegistry.record(fnName, elapsed); } } } } } } } else if (fn0 instanceof IVncFunction) { // collection/keyword as function final CallFrame cf = new CallFrame(fn0.getType().toString(), args, a0meta, env); try (WithCallStack cs = new WithCallStack(cf)) { final IVncFunction fn = (IVncFunction)fn0; final VncList fnArgs = (VncList)evaluate_sequence_values(args, env); return fn.apply(fnArgs); } } else { final CallFrame cf = new CallFrame("unknown", args, a0meta); try (WithCallStack cs = new WithCallStack(cf)) { throw new VncException(String.format( "Expected a function or keyword/set/map/vector as " + "s-expression symbol value but got a value " + "of type '%s'!", Types.getType(fn0))); } } } break; } } } private VncVal evaluate_values(final VncVal ast, final Env env) { // System.out.println("EVAL VALUES: " + Types.getType(ast) + " > " + ast.toString(true)); if (ast == Nil) { return Nil; } else if (ast instanceof VncSymbol) { return env.get((VncSymbol)ast); } else if (ast instanceof VncSequence) { return evaluate_sequence_values((VncSequence)ast, env); } else if (ast instanceof VncScalar) { return ast; } else if (ast instanceof VncJavaObject) { return ast; } else if (ast instanceof VncMap) { final VncMap map = (VncMap)ast; final Map vals = new HashMap<>(map.size()); for(Entry e: map.getJavaMap().entrySet()) { vals.put( evaluate(e.getKey(), env, false), evaluate(e.getValue(), env, false)); } return map.withValues(vals); } else if (ast instanceof VncSet) { final VncSet set = (VncSet)ast; final List vals = new ArrayList<>(set.size()); for(VncVal v: set) { vals.add(evaluate(v, env, false)); } return set.withValues(vals); } else { return ast; } } private VncSequence evaluate_sequence_values(final VncSequence seq, final Env env) { if (seq instanceof VncLazySeq) { return seq; } else { // System.out.println("EVAL SEQ VALUES: " + Types.getType(seq) + " > " + seq.toString(true)); switch(seq.size()) { case 0: return seq; case 1: { final VncVal v1 = seq.first(); return v1 instanceof VncScalar ? seq : seq.withVariadicValues( evaluate(v1, env, false)); } case 2: { final VncVal v1 = seq.first(); final VncVal v2 = seq.second(); return v1 instanceof VncScalar && v2 instanceof VncScalar ? seq : seq.withVariadicValues( evaluate(v1, env, false), evaluate(v2, env, false)); } case 3: { final VncVal v1 = seq.first(); final VncVal v2 = seq.second(); final VncVal v3 = seq.third(); return v1 instanceof VncScalar && v2 instanceof VncScalar && v3 instanceof VncScalar ? seq : seq.withVariadicValues( evaluate(v1, env, false), evaluate(v2, env, false), evaluate(v3, env, false)); } case 4: { return seq.withVariadicValues( evaluate(seq.first(), env, false), evaluate(seq.second(), env, false), evaluate(seq.third(), env, false), evaluate(seq.fourth(), env, false)); } default: return seq.map(v -> evaluate(v, env, false)); } } } /** * Recursively expands a macro. Inside the loop, the first element * of the ast list (a symbol), is looked up in the environment to get * the macro function. This macro function is then called/applied with * the rest of the ast elements (2nd through the last) as arguments. * The return value of the macro call becomes the new value of ast. * When the loop completes because ast no longer represents a macro call, * the current value of ast is returned. * *

Macro check: * An ast is a macro if the ast is a list that contains a symbol as the * first element and that symbol refers to a function in the env environment * and that function has the macro attribute set to true. * * @param ast ast * @param env env * @return the expanded macro */ private VncVal doMacroexpand( final VncVal ast, final Env env ) { final long nanos = meterRegistry.enabled ? System.nanoTime() : 0L; VncVal ast_ = ast; int expandedMacros = 0; while (ast_ instanceof VncList) { final VncVal a0 = ((VncList)ast_).first(); if (!(a0 instanceof VncSymbol)) break; final VncSymbol a0Sym = (VncSymbol)a0; final String a0SymName = a0Sym.getName(); final VncList macroArgs = ((VncList)ast_).rest(); if ("load-module".equals(a0SymName)) { return SpecialForms_LoadCodeMacros.load_module.apply(a0Sym, macroArgs, env, specialFormsContext); } else if ("load-file".equals(a0SymName)) { return SpecialForms_LoadCodeMacros.load_file.apply(a0Sym, macroArgs, env, specialFormsContext); } else if ("load-classpath-file".equals(a0SymName)) { return SpecialForms_LoadCodeMacros.load_classpath_file.apply(a0Sym, macroArgs, env, specialFormsContext); } else if ("load-string".equals(a0SymName)) { return SpecialForms_LoadCodeMacros.load_string.apply(a0Sym, macroArgs, env, specialFormsContext); } final VncVal fn = env.getGlobalOrNull(a0Sym); if (!(fn != null && fn instanceof VncFunction && ((VncFunction)fn).isMacro())) break; final VncFunction macro = (VncFunction)fn; // validate that the macro is allowed by the sandbox if (checkSandbox) { interceptor.validateVeniceFunction(macro.getQualifiedName()); } expandedMacros++; final CallFrame cf = new CallFrame(macro.getQualifiedName(), macroArgs, a0.getMeta()); try (WithCallStack cs = new WithCallStack(cf)) { if (meterRegistry.enabled) { final long nanosRun = System.nanoTime(); ast_ = macro.apply(macroArgs); meterRegistry.record(macro.getQualifiedName() + "[m]", System.nanoTime() - nanosRun); } else { ast_ = macro.apply(macroArgs); } } } if (expandedMacros > 0) { if (meterRegistry.enabled) { meterRegistry.record("macroexpand", System.nanoTime() - nanos); } } return ast_; } private VncVal macroexpand( final VncList args, final Env env, final VncVal meta ) { final CallFrame callframe = new CallFrame("macroexpand", args, meta); try (WithCallStack cs = new WithCallStack(callframe)) { assertArity("macroexpand", FnType.SpecialForm, args, 1); final VncVal ast = evaluate(args.first(), env, false); return doMacroexpand(ast, env); } } /** * Expands recursively all macros in the form. * *

An approach with core/prewalk does not work, because * this function does not apply namespaces definitions (ns x). * Remember that macros are always executed in the namespace of the caller * as opposed to functions that are executed in the namespace they are * defined in. * *

With core/prewalk we cannot execute (ns x) * because the functions involved like core/walk and * core/partial will reset the changed namespace upon leaving * its body. * *

     *     (core/prewalk (fn [x] (if (list? x) (macroexpand x) x)) form)
     * 
* *

Note: only macros that have already been parsed in another parse unit * can be expanded! * 'macroexpand-all' is not an interpreter thus it cannot not * run macro definitions and put them to the symbol table for later * reference! * * @param callframe a callframe * @param form the form to expand * @param env the env * @return the expanded form */ @Override public VncVal macroexpand_all( final CallFrame callframe, final VncVal form, final Env env ) { try (WithCallStack cs = new WithCallStack(callframe)) { final VncFunction handler = new VncFunction(createAnonymousFuncName("macroexpand-all-handler")) { @Override public VncVal apply(final VncList args) { final VncVal form = args.first(); if (Types.isVncList(form)) { final VncList list = (VncList)form; final VncVal first = list.first(); if (Types.isVncSymbol(first)) { final VncVal second = list.second(); // check if the expression is of the form (ns x) if (list.size() == 2 && "ns".equals(((VncSymbol)first).getName()) && second instanceof VncSymbol ) { // we've encountered a '(ns x)' symbolic expression -> apply it Namespaces.setCurrentNamespace(nsRegistry.computeIfAbsent((VncSymbol)second)); } else { // try to expand return doMacroexpand(list, env); } } } return form; } private static final long serialVersionUID = -1L; }; final VncFunction walk = new VncFunction(createAnonymousFuncName("macroexpand-all-walk")) { // Java implementation of 'core/walk' from 'core' module with // the optimization for 'outer' function as 'identity' for 'core/prewalk' @Override public VncVal apply(final VncList args) { final VncFunction inner = (VncFunction)args.first(); final VncVal form = args.second(); if (Types.isVncJavaObject(form) || Types.isVncJavaList(form) || Types.isVncJavaSet(form) || Types.isVncJavaMap(form) || Types.isVncStack(form) || Types.isVncQueue(form) || Types.isVncDelayQueue(form) || Types.isVncCustomType(form) || Types.isVncDAG(form) ) { return form; } else if (Types.isVncList(form)) { // (outer (apply list (map inner form))) return TransducerFunctions.map.applyOf(inner, form); } else if (Types.isVncMapEntry(form)) { // (outer (map-entry (inner (key form)) (inner (val form)))) return CoreFunctions.new_map_entry.applyOf( inner.applyOf(((VncMapEntry)form).getKey()), inner.applyOf(((VncMapEntry)form).getValue())); } else if (Types.isVncCollection(form)) { // (outer (into (empty form) (map inner form))) return CoreFunctions.into.applyOf( CoreFunctions.empty.applyOf(form), TransducerFunctions.map.applyOf(inner, form)); } else { // (outer form) return form; } } private static final long serialVersionUID = -1L; }; final VncFunction prewalk = new VncFunction(createAnonymousFuncName("macroexpand-all-prewalk")) { // Java implementation of 'core/prewalk' from 'core' module @Override public VncVal apply(final VncList args) { final VncFunction f = (VncFunction)args.first(); final VncVal form = args.second(); return walk.applyOf( CoreFunctions.partial.applyOf(this, f), f.applyOf(form)); } private static final long serialVersionUID = -1L; }; // remember the original namespace final Namespace original_ns = Namespaces.getCurrentNamespace(); try { return prewalk.applyOf(handler, form); } finally { // set the original namespace back Namespaces.setCurrentNamespace(original_ns); } } } private VncVal tail_pos_check_( final boolean inTailPosition, final VncList args, final Env env, final VncVal meta ) { if (!inTailPosition) { final CallFrame callframe = new CallFrame("tail-pos", args, meta); final VncString name = Coerce.toVncString(args.nthOrDefault(0, VncString.empty())); try (WithCallStack cs = new WithCallStack(callframe)) { throw new NotInTailPositionException( name.isEmpty() ? "Not in tail position" : String.format( "The tail-pos expression '%s' is not in tail position", name.getValue())); } } else { return Nil; } } private Env buildRecursionEnv( final VncList args, final Env env, final RecursionPoint recursionPoint ) { final Env recur_env = recursionPoint.getLoopEnv(); final int argCount = args.size(); // denormalize for best performance (short loops are performance critical) switch(argCount) { case 0: break; case 1: // [1][2] calculate and bind the single new value final VncVal symVal0 = recursionPoint.getLoopBindingName(0); final VncVal v0 = evaluate(args.first(), env, false); RecursionPoint.addToLocalEnv(symVal0, v0, recur_env); break; case 2: final VncVal symVal1 = recursionPoint.getLoopBindingName(0); final VncVal symVal2 = recursionPoint.getLoopBindingName(1); // [1] calculate the new values final VncVal v1 = evaluate(args.first(), env, false); final VncVal v2 = evaluate(args.second(), env, false); // [2] bind the new values RecursionPoint.addToLocalEnv(symVal1, v1, recur_env); RecursionPoint.addToLocalEnv(symVal2, v2, recur_env); break; default: // [1] calculate new values final VncVal[] newValues = new VncVal[argCount]; for(int ii=0; ii





© 2015 - 2025 Weber Informatics LLC | Privacy Policy