mirror of
https://github.com/enso-org/enso.git
synced 2024-12-24 01:51:31 +03:00
Add lambdas, types, and methods support to new syntax (#358)
This commit is contained in:
parent
1e3e105232
commit
9a4332108f
@ -81,9 +81,9 @@ part of the compiler that they relate to.
|
||||
In order to build and run Enso you will need the following tools:
|
||||
|
||||
- [sbt](https://www.scala-sbt.org/) with version at least 1.3.0.
|
||||
- [GraalVM](https://www.graalvm.org/) with version at least 19.2.0 and
|
||||
configured as your default JVM. [Jenv](http://www.jenv.be/) is a useful tool
|
||||
for managing multiple JVMs.
|
||||
- [GraalVM](https://www.graalvm.org/) with version at least that described in
|
||||
the [`build.sbt`](build.sbt) file configured as your default JVM.
|
||||
[Jenv](http://www.jenv.be/) is a useful tool for managing multiple JVMs.
|
||||
|
||||
### Getting the Sources
|
||||
Given you've probably been reading this document on GitHub, you might have an
|
||||
@ -243,7 +243,9 @@ need to follow these steps:
|
||||
4. In the options for that configuration select 'Listen to remote JVM' under
|
||||
'Debugger mode:'
|
||||
5. Where it provides the command-line arguments for the remote JVM, copy these
|
||||
and add them to `truffleRunOptions` in [`build.sbt`](build.sbt).
|
||||
and add them to `truffleRunOptions` in [`build.sbt`](build.sbt). Remove the
|
||||
portion of these options after `suspend=y`, including the comma. They are
|
||||
placeholders that we don't use.
|
||||
6. Now, when you want to debug something, you can place a breakpoint as usual in
|
||||
IntelliJ, and then execute your remote debugging configuration. Now, in the
|
||||
SBT shell, run a command to execute the code you want to debug (e.g.
|
||||
|
@ -356,9 +356,7 @@ lazy val runtime = (project in file("engine/runtime"))
|
||||
version := "0.1",
|
||||
commands += WithDebugCommand.withDebug,
|
||||
inConfig(Compile)(truffleRunOptionsSettings),
|
||||
inConfig(Test)(truffleRunOptionsSettings),
|
||||
inConfig(Benchmark)(Defaults.testSettings),
|
||||
inConfig(Benchmark)(truffleRunOptionsSettings),
|
||||
parallelExecution in Test := false,
|
||||
logBuffered in Test := false,
|
||||
libraryDependencies ++= jmh ++ Seq(
|
||||
|
@ -1,31 +0,0 @@
|
||||
package org.enso.syntax.text
|
||||
|
||||
/** This object contains view patterns that allow matching on the parser AST for
|
||||
* more sophisticated constructs.
|
||||
*
|
||||
* These view patterns are implemented as custom unapply methods that only
|
||||
* return [[Some]] when more complex conditions are met.
|
||||
*/
|
||||
|
||||
object View {
|
||||
object Assignment {
|
||||
val assignmentOpSym = AST.Ident.Opr("=")
|
||||
|
||||
def unapply(ast: AST): Option[(AST, AST)] = {
|
||||
ast match {
|
||||
case AST.App.Infix.any(ast) => {
|
||||
val left = ast.larg
|
||||
val op = ast.opr
|
||||
val right = ast.rarg
|
||||
|
||||
if (op == assignmentOpSym) {
|
||||
Some((left, right))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -119,6 +119,24 @@ object Builtin {
|
||||
}
|
||||
}
|
||||
|
||||
val case_of = Definition(
|
||||
Var("case") -> Pattern.Expr(),
|
||||
Var("of") -> Pattern.Block()
|
||||
) { ctx =>
|
||||
ctx.body match {
|
||||
case List(scrutineePart, branchesPart) =>
|
||||
(scrutineePart.body.toStream, branchesPart.body.toStream) match {
|
||||
case (List(scrutinee), List(branches)) =>
|
||||
AST.Mixfix(
|
||||
List1(scrutineePart.head, branchesPart.head),
|
||||
List1(scrutinee.el, branches.el)
|
||||
)
|
||||
case _ => internalError
|
||||
}
|
||||
case _ => internalError
|
||||
}
|
||||
}
|
||||
|
||||
val nonSpacedExpr = Pattern.Any(Some(false)).many1.build
|
||||
|
||||
val arrow = Definition(
|
||||
@ -226,6 +244,7 @@ object Builtin {
|
||||
|
||||
Registry(
|
||||
group,
|
||||
case_of,
|
||||
if_then,
|
||||
if_then_else,
|
||||
imp,
|
||||
|
@ -108,7 +108,7 @@ object Pattern {
|
||||
def apply(spaced: Boolean): Block = Block(Some(spaced))
|
||||
}
|
||||
|
||||
def Any(spaced: Spaced = None): Pattern =
|
||||
def Any(spaced: Spaced = None, allowBlocks: Boolean = true): Pattern =
|
||||
Blank(spaced) |
|
||||
Var(spaced) |
|
||||
Cons(spaced) |
|
||||
@ -116,15 +116,21 @@ object Pattern {
|
||||
Mod(spaced) |
|
||||
Num(spaced) |
|
||||
Text(spaced) |
|
||||
Block(spaced) |
|
||||
Macro(spaced) |
|
||||
Invalid(spaced)
|
||||
Invalid(spaced) |
|
||||
(if (allowBlocks) {
|
||||
Block(spaced)
|
||||
} else {
|
||||
Nothing()
|
||||
})
|
||||
def Any(spaced: Boolean): Pattern = Any(Some(spaced))
|
||||
def ErrTillEnd(msg: String) = Any().tillEnd.err(msg)
|
||||
def ErrUnmatched(msg: String) = End() | ErrTillEnd(msg)
|
||||
def Expr() = Any().many1.build
|
||||
def NonSpacedExpr() = Any(spaced = false).many1.build
|
||||
def NonSpacedExpr_() = (Any().but(Block()) :: Any(spaced = false).many).build
|
||||
def ErrTillEnd(msg: String): Pattern = Any().tillEnd.err(msg)
|
||||
def ErrUnmatched(msg: String): Pattern = End() | ErrTillEnd(msg)
|
||||
def Expr(allowBlocks: Boolean = true): Pattern =
|
||||
Any(allowBlocks = allowBlocks).many1.build
|
||||
def NonSpacedExpr(): Pattern = Any(spaced = false).many1.build
|
||||
def NonSpacedExpr_(): Pattern =
|
||||
(Any().but(Block()) :: Any(spaced = false).many).build
|
||||
def SepList(pat: Pattern, div: Pattern): Pattern = pat :: (div :: pat).many
|
||||
def SepList(pat: Pattern, div: Pattern, err: String): Pattern = {
|
||||
val seg = pat | Any().till(div).err(err)
|
||||
|
@ -1,5 +1,7 @@
|
||||
package org.enso.syntax.text
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import org.enso.data.Index
|
||||
import org.enso.data.Span
|
||||
import org.enso.flexer
|
||||
@ -194,7 +196,7 @@ class Parser {
|
||||
Builtin.registry.get(resolvedAST.path) match {
|
||||
case None => throw MissingMacroDefinition
|
||||
case Some(spec) =>
|
||||
val id = resolvedAST.id.getOrElse(throw new Error(s"Missing ID"))
|
||||
val id = resolvedAST.id.getOrElse(UUID.randomUUID)
|
||||
val segments = resolvedAST.segs.toList().map(_.el)
|
||||
val ctx = AST.Macro.Resolver.Context(resolvedAST.pfx, segments, id)
|
||||
resolvedAST.copy(shape = resolvedAST.shape.copy[AST](resolved = {
|
||||
|
@ -12,7 +12,7 @@ import org.enso.interpreter.node.callable.ForceNodeGen;
|
||||
import org.enso.interpreter.node.callable.InvokeCallableNode;
|
||||
import org.enso.interpreter.node.callable.argument.ReadArgumentNode;
|
||||
import org.enso.interpreter.node.callable.function.CreateFunctionNode;
|
||||
import org.enso.interpreter.node.callable.function.FunctionBodyNode;
|
||||
import org.enso.interpreter.node.callable.function.BlockNode;
|
||||
import org.enso.interpreter.node.controlflow.*;
|
||||
import org.enso.interpreter.node.expression.constant.ConstructorNode;
|
||||
import org.enso.interpreter.node.expression.constant.DynamicSymbolNode;
|
||||
@ -204,12 +204,11 @@ public class ExpressionFactory implements AstExpressionVisitor<ExpressionNode> {
|
||||
* function arguments into a state where they can actually be read.
|
||||
*
|
||||
* @param arguments the arguments the function is defined for
|
||||
* @param expressions the body of the function
|
||||
* @param retValue the return value of the function
|
||||
* @param body the body of the function
|
||||
* @return a runtime node representing the function body
|
||||
*/
|
||||
public CreateFunctionNode processFunctionBody(
|
||||
List<AstArgDefinition> arguments, List<AstExpression> expressions, AstExpression retValue) {
|
||||
List<AstArgDefinition> arguments, AstExpression body) {
|
||||
|
||||
ArgDefinitionFactory argFactory =
|
||||
new ArgDefinitionFactory(scope, language, scopeName, moduleScope);
|
||||
@ -235,17 +234,9 @@ public class ExpressionFactory implements AstExpressionVisitor<ExpressionNode> {
|
||||
}
|
||||
}
|
||||
|
||||
List<ExpressionNode> fnBodyExpressionNodes =
|
||||
expressions.stream().map(stmt -> stmt.visit(this)).collect(Collectors.toList());
|
||||
ExpressionNode bodyExpr = body.visit(this);
|
||||
|
||||
List<ExpressionNode> allFnExpressions = new ArrayList<>();
|
||||
allFnExpressions.addAll(argExpressions);
|
||||
allFnExpressions.addAll(fnBodyExpressionNodes);
|
||||
|
||||
ExpressionNode returnExpr = retValue.visit(this);
|
||||
|
||||
FunctionBodyNode fnBodyNode =
|
||||
new FunctionBodyNode(allFnExpressions.toArray(new ExpressionNode[0]), returnExpr);
|
||||
BlockNode fnBodyNode = new BlockNode(argExpressions.toArray(new ExpressionNode[0]), bodyExpr);
|
||||
RootNode fnRootNode =
|
||||
new ClosureRootNode(language, scope, moduleScope, fnBodyNode, null, "lambda::" + scopeName);
|
||||
RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(fnRootNode);
|
||||
@ -278,20 +269,18 @@ public class ExpressionFactory implements AstExpressionVisitor<ExpressionNode> {
|
||||
/**
|
||||
* Creates a runtime node representing a function.
|
||||
*
|
||||
* <p>Given that most of the work takes place in {@link #processFunctionBody(List, List,
|
||||
* AstExpression) processFunctionBody}, this node is solely responsible for handling the creation
|
||||
* of a new scope for the function, and marking it as tail recursive.
|
||||
* <p>Given that most of the work takes place in {@link #processFunctionBody(List, AstExpression)
|
||||
* processFunctionBody}, this node is solely responsible for handling the creation of a new scope
|
||||
* for the function, and marking it as tail recursive.
|
||||
*
|
||||
* @param arguments the arguments to the function
|
||||
* @param expressions the expressions that make up the function body
|
||||
* @param retValue the return value of the function
|
||||
* @param body the body of the function
|
||||
* @return a runtime node representing the function
|
||||
*/
|
||||
@Override
|
||||
public ExpressionNode visitFunction(
|
||||
List<AstArgDefinition> arguments, List<AstExpression> expressions, AstExpression retValue) {
|
||||
public ExpressionNode visitFunction(List<AstArgDefinition> arguments, AstExpression body) {
|
||||
ExpressionFactory child = createChild(currentVarName);
|
||||
ExpressionNode fun = child.processFunctionBody(arguments, expressions, retValue);
|
||||
ExpressionNode fun = child.processFunctionBody(arguments, body);
|
||||
fun.markTail();
|
||||
return fun;
|
||||
}
|
||||
@ -299,20 +288,18 @@ public class ExpressionFactory implements AstExpressionVisitor<ExpressionNode> {
|
||||
/**
|
||||
* Creates a runtime node representing a case function.
|
||||
*
|
||||
* <p>Given that most of the work takes place in {@link #processFunctionBody(List, List,
|
||||
* AstExpression) processFunctionBody}, this node is solely responsible for handling the creation
|
||||
* of a new scope for the function.
|
||||
* <p>Given that most of the work takes place in {@link #processFunctionBody(List, AstExpression)
|
||||
* processFunctionBody}, this node is solely responsible for handling the creation of a new scope
|
||||
* for the function.
|
||||
*
|
||||
* @param arguments the arguments to the function
|
||||
* @param expressions the expressions that make up the function body
|
||||
* @param retValue the return value of the function
|
||||
* @param body the body of the function
|
||||
* @return a runtime node representing the function
|
||||
*/
|
||||
@Override
|
||||
public ExpressionNode visitCaseFunction(
|
||||
List<AstArgDefinition> arguments, List<AstExpression> expressions, AstExpression retValue) {
|
||||
public ExpressionNode visitCaseFunction(List<AstArgDefinition> arguments, AstExpression body) {
|
||||
ExpressionFactory child = createChild(currentVarName);
|
||||
return child.processFunctionBody(arguments, expressions, retValue);
|
||||
return child.processFunctionBody(arguments, body);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -403,4 +390,19 @@ public class ExpressionFactory implements AstExpressionVisitor<ExpressionNode> {
|
||||
public ExpressionNode visitDesuspend(AstExpression target) {
|
||||
return ForceNodeGen.create(target.visit(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a runtime representation of a block.
|
||||
*
|
||||
* @param statements the statements making up the body of this block
|
||||
* @param retValue the return value expression
|
||||
* @return AST fragment representing the block
|
||||
*/
|
||||
@Override
|
||||
public ExpressionNode visitBlock(List<AstExpression> statements, AstExpression retValue) {
|
||||
ExpressionNode[] statementExprs =
|
||||
statements.stream().map(expr -> expr.visit(this)).toArray(ExpressionNode[]::new);
|
||||
ExpressionNode retExpr = retValue.visit(this);
|
||||
return new BlockNode(statementExprs, retExpr);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
package org.enso.interpreter.builder;
|
||||
|
||||
import com.oracle.truffle.api.RootCallTarget;
|
||||
import com.oracle.truffle.api.Truffle;
|
||||
import org.enso.compiler.core.IR;
|
||||
import org.enso.interpreter.*;
|
||||
import org.enso.interpreter.node.ClosureRootNode;
|
||||
import org.enso.interpreter.node.ExpressionNode;
|
||||
import org.enso.interpreter.node.callable.function.CreateFunctionNode;
|
||||
import org.enso.interpreter.runtime.Context;
|
||||
@ -9,9 +13,12 @@ import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
|
||||
import org.enso.interpreter.runtime.error.VariableDoesNotExistException;
|
||||
import org.enso.interpreter.runtime.scope.LocalScope;
|
||||
import org.enso.interpreter.runtime.scope.ModuleScope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@ -19,7 +26,7 @@ import java.util.stream.IntStream;
|
||||
* A {@code GlobalScopeExpressionFactory} is responsible for converting the top-level definitions of
|
||||
* an Enso program into AST nodes for the interpreter to evaluate.
|
||||
*/
|
||||
public class ModuleScopeExpressionFactory implements AstModuleScopeVisitor<ExpressionNode> {
|
||||
public class ModuleScopeExpressionFactory implements AstModuleScopeVisitor<Function> {
|
||||
private final Language language;
|
||||
private final ModuleScope moduleScope;
|
||||
|
||||
@ -40,7 +47,7 @@ public class ModuleScopeExpressionFactory implements AstModuleScopeVisitor<Expre
|
||||
* @param expr the expression to execute on
|
||||
* @return a runtime node representing the top-level expression
|
||||
*/
|
||||
public ExpressionNode run(AstModuleScope expr) {
|
||||
public Optional<Function> run(AstModuleScope expr) {
|
||||
return expr.visit(this);
|
||||
}
|
||||
|
||||
@ -54,11 +61,11 @@ public class ModuleScopeExpressionFactory implements AstModuleScopeVisitor<Expre
|
||||
* @return a runtime node representing the whole top-level program scope
|
||||
*/
|
||||
@Override
|
||||
public ExpressionNode visitModuleScope(
|
||||
public Optional<Function> visitModuleScope(
|
||||
List<AstImport> imports,
|
||||
List<AstTypeDef> typeDefs,
|
||||
List<AstMethodDef> bindings,
|
||||
AstExpression executableExpression) {
|
||||
Optional<AstExpression> executableExpression) {
|
||||
Context context = language.getCurrentContext();
|
||||
|
||||
for (AstImport imp : imports) {
|
||||
@ -87,14 +94,21 @@ public class ModuleScopeExpressionFactory implements AstModuleScopeVisitor<Expre
|
||||
});
|
||||
|
||||
for (AstMethodDef method : bindings) {
|
||||
scala.Option<AstExpression> scalaNone = scala.Option.apply(null);
|
||||
AstArgDefinition thisArgument =
|
||||
new AstArgDefinition(Constants.THIS_ARGUMENT_NAME, scalaNone, false);
|
||||
|
||||
ExpressionFactory expressionFactory =
|
||||
new ExpressionFactory(
|
||||
language,
|
||||
method.typeName() + Constants.SCOPE_SEPARATOR + method.methodName(),
|
||||
moduleScope);
|
||||
|
||||
List<AstArgDefinition> realArgs = new ArrayList<>(method.fun().getArguments());
|
||||
realArgs.add(0, thisArgument);
|
||||
|
||||
CreateFunctionNode funNode =
|
||||
expressionFactory.processFunctionBody(
|
||||
method.fun().getArguments(), method.fun().getStatements(), method.fun().ret());
|
||||
expressionFactory.processFunctionBody(realArgs, method.fun().body());
|
||||
funNode.markTail();
|
||||
Function function =
|
||||
new Function(
|
||||
@ -113,7 +127,19 @@ public class ModuleScopeExpressionFactory implements AstModuleScopeVisitor<Expre
|
||||
}
|
||||
}
|
||||
|
||||
ExpressionFactory factory = new ExpressionFactory(this.language, moduleScope);
|
||||
return factory.run(executableExpression);
|
||||
return executableExpression.map(this::wrapExecutableExpression);
|
||||
}
|
||||
|
||||
private Function wrapExecutableExpression(AstExpression expr) {
|
||||
LocalScope scope = new LocalScope();
|
||||
String name = "executable_expression";
|
||||
ExpressionFactory expressionFactory =
|
||||
new ExpressionFactory(this.language, scope, name, moduleScope);
|
||||
ExpressionNode expression = expressionFactory.run(expr);
|
||||
ClosureRootNode rootNode =
|
||||
new ClosureRootNode(language, scope, moduleScope, expression, null, name);
|
||||
RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(rootNode);
|
||||
return new Function(
|
||||
callTarget, null, new FunctionSchema(FunctionSchema.CallStrategy.CALL_LOOP));
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,20 @@ package org.enso.interpreter.node;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.DirectCallNode;
|
||||
import com.oracle.truffle.api.nodes.RootNode;
|
||||
import com.oracle.truffle.api.source.Source;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.enso.interpreter.Language;
|
||||
import org.enso.interpreter.node.callable.dispatch.CallOptimiserNode;
|
||||
import org.enso.interpreter.node.expression.atom.InstantiateNode;
|
||||
import org.enso.interpreter.runtime.Context;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.scope.LocalScope;
|
||||
import org.enso.interpreter.runtime.scope.ModuleScope;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* This node handles static transformation of the input AST before execution and represents the root
|
||||
* of an Enso program.
|
||||
@ -20,7 +27,8 @@ import org.enso.interpreter.runtime.scope.ModuleScope;
|
||||
public class ProgramRootNode extends EnsoRootNode {
|
||||
|
||||
private final Source sourceCode;
|
||||
@Child private ExpressionNode ensoProgram = null;
|
||||
@Child private CallOptimiserNode executableExpressionCaller = CallOptimiserNode.build();
|
||||
private @CompilerDirectives.CompilationFinal Function executableExpression = null;
|
||||
private boolean programShouldBeTailRecursive = false;
|
||||
|
||||
/**
|
||||
@ -40,12 +48,7 @@ public class ProgramRootNode extends EnsoRootNode {
|
||||
String name,
|
||||
SourceSection sourceSection,
|
||||
Source sourceCode) {
|
||||
super(
|
||||
language,
|
||||
localScope,
|
||||
moduleScope,
|
||||
name,
|
||||
sourceSection);
|
||||
super(language, localScope, moduleScope, name, sourceSection);
|
||||
this.sourceCode = sourceCode;
|
||||
}
|
||||
|
||||
@ -60,16 +63,16 @@ public class ProgramRootNode extends EnsoRootNode {
|
||||
Context context = getContext();
|
||||
|
||||
// Note [Static Passes]
|
||||
if (this.ensoProgram == null) {
|
||||
if (this.executableExpression == null) {
|
||||
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||
this.ensoProgram = this.insert(context.compiler().run(this.sourceCode));
|
||||
this.ensoProgram.setTail(programShouldBeTailRecursive);
|
||||
Optional<Function> program = context.compiler().run(this.sourceCode);
|
||||
executableExpression =
|
||||
program.orElseGet(() -> getContext().getUnit().getConstructorFunction());
|
||||
}
|
||||
|
||||
Object state = getContext().getUnit().newInstance();
|
||||
frame.setObject(this.getStateFrameSlot(), state);
|
||||
|
||||
return this.ensoProgram.executeGeneric(frame);
|
||||
return this.executableExpressionCaller
|
||||
.executeDispatch(executableExpression, null, context.getUnit().newInstance(), new Object[0])
|
||||
.getValue();
|
||||
}
|
||||
|
||||
/* Note [Static Passes]
|
||||
|
@ -37,18 +37,26 @@ public abstract class MethodResolverNode extends Node {
|
||||
Function resolveAtomCached(
|
||||
UnresolvedSymbol symbol,
|
||||
Atom atom,
|
||||
@CachedContext(Language.class) TruffleLanguage.ContextReference<Context> contextRef,
|
||||
@Cached("symbol") UnresolvedSymbol cachedSymbol,
|
||||
@Cached("atom.getConstructor()") AtomConstructor cachedConstructor,
|
||||
@Cached("resolveMethodOnAtom(cachedConstructor, cachedSymbol)") Function function) {
|
||||
return function;
|
||||
}
|
||||
|
||||
@Specialization(guards = {"cachedSymbol == symbol", "atomConstructor == cachedConstructor"})
|
||||
Function resolveAtomConstructorCached(
|
||||
UnresolvedSymbol symbol,
|
||||
AtomConstructor atomConstructor,
|
||||
@Cached("symbol") UnresolvedSymbol cachedSymbol,
|
||||
@Cached("atomConstructor") AtomConstructor cachedConstructor,
|
||||
@Cached("resolveMethodOnAtom(cachedConstructor, cachedSymbol)") Function function) {
|
||||
return function;
|
||||
}
|
||||
|
||||
@Specialization(guards = "cachedSymbol == symbol")
|
||||
Function resolveNumberCached(
|
||||
UnresolvedSymbol symbol,
|
||||
long self,
|
||||
@CachedContext(Language.class) TruffleLanguage.ContextReference<Context> contextReference,
|
||||
@Cached("symbol") UnresolvedSymbol cachedSymbol,
|
||||
@Cached("resolveMethodOnNumber(cachedSymbol)") Function function) {
|
||||
return function;
|
||||
@ -58,7 +66,6 @@ public abstract class MethodResolverNode extends Node {
|
||||
Function resolveFunctionCached(
|
||||
UnresolvedSymbol symbol,
|
||||
Function self,
|
||||
@CachedContext(Language.class) TruffleLanguage.ContextReference<Context> contextReference,
|
||||
@Cached("symbol") UnresolvedSymbol cachedSymbol,
|
||||
@Cached("resolveMethodOnFunction(cachedSymbol)") Function function) {
|
||||
return function;
|
||||
@ -68,7 +75,6 @@ public abstract class MethodResolverNode extends Node {
|
||||
Function resolveErrorCached(
|
||||
UnresolvedSymbol symbol,
|
||||
RuntimeError self,
|
||||
@CachedContext(Language.class) TruffleLanguage.ContextReference<Context> contextReference,
|
||||
@Cached("symbol") UnresolvedSymbol cachedSymbol,
|
||||
@Cached("resolveMethodOnError(cachedSymbol)") Function function) {
|
||||
return function;
|
||||
|
@ -10,7 +10,7 @@ import org.enso.interpreter.node.ExpressionNode;
|
||||
* function body.
|
||||
*/
|
||||
@NodeInfo(shortName = "{}")
|
||||
public class FunctionBodyNode extends ExpressionNode {
|
||||
public class BlockNode extends ExpressionNode {
|
||||
@Children private final ExpressionNode[] statements;
|
||||
@Child private ExpressionNode returnExpr;
|
||||
|
||||
@ -20,7 +20,7 @@ public class FunctionBodyNode extends ExpressionNode {
|
||||
* @param expressions the function body
|
||||
* @param returnExpr the return expression from the function
|
||||
*/
|
||||
public FunctionBodyNode(ExpressionNode[] expressions, ExpressionNode returnExpr) {
|
||||
public BlockNode(ExpressionNode[] expressions, ExpressionNode returnExpr) {
|
||||
this.statements = expressions;
|
||||
this.returnExpr = returnExpr;
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package org.enso.compiler
|
||||
|
||||
import java.util.Optional
|
||||
|
||||
import com.oracle.truffle.api.TruffleFile
|
||||
import com.oracle.truffle.api.source.Source
|
||||
import org.enso.compiler.generate.AstToAstExpression
|
||||
@ -11,10 +13,11 @@ import org.enso.interpreter.builder.ModuleScopeExpressionFactory
|
||||
import org.enso.interpreter.node.ExpressionNode
|
||||
import org.enso.interpreter.runtime.Context
|
||||
import org.enso.interpreter.runtime.Module
|
||||
import org.enso.interpreter.runtime.callable.function.Function
|
||||
import org.enso.interpreter.runtime.error.ModuleDoesNotExistException
|
||||
import org.enso.interpreter.runtime.scope.LocalScope
|
||||
import org.enso.interpreter.runtime.scope.ModuleScope
|
||||
import org.enso.syntax.text.{AST, Parser}
|
||||
import org.enso.syntax.text.{AST, Debug, Parser}
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.collection.mutable
|
||||
@ -41,7 +44,7 @@ class Compiler(
|
||||
* @return an interpreter node whose execution corresponds to the top-level
|
||||
* executable functionality in the module corresponding to `source`.
|
||||
*/
|
||||
def run(source: Source, scope: ModuleScope): ExpressionNode = {
|
||||
def run(source: Source, scope: ModuleScope): Optional[Function] = {
|
||||
val mimeType = source.getMimeType
|
||||
|
||||
val expr: AstModuleScope = if (mimeType == Constants.MIME_TYPE) {
|
||||
@ -63,7 +66,7 @@ class Compiler(
|
||||
* @return an interpreter node whose execution corresponds to the top-level
|
||||
* executable functionality in the module corresponding to `source`.
|
||||
*/
|
||||
def run(file: TruffleFile, scope: ModuleScope): ExpressionNode = {
|
||||
def run(file: TruffleFile, scope: ModuleScope): Optional[Function] = {
|
||||
run(Source.newBuilder(Constants.LANGUAGE_ID, file).build, scope)
|
||||
}
|
||||
|
||||
@ -75,7 +78,7 @@ class Compiler(
|
||||
* @return an interpreter node whose execution corresponds to the top-level
|
||||
* executable functionality in the module corresponding to `source`.
|
||||
*/
|
||||
def run(source: Source): ExpressionNode = {
|
||||
def run(source: Source): Optional[Function] = {
|
||||
run(source, context.createScope)
|
||||
}
|
||||
|
||||
@ -87,7 +90,7 @@ class Compiler(
|
||||
* @return an interpreter node whose execution corresponds to the top-level
|
||||
* executable functionality in the module corresponding to `source`.
|
||||
*/
|
||||
def run(file: TruffleFile): ExpressionNode = {
|
||||
def run(file: TruffleFile): Optional[Function] = {
|
||||
run(Source.newBuilder(Constants.LANGUAGE_ID, file).build)
|
||||
}
|
||||
|
||||
|
@ -1,21 +1,17 @@
|
||||
package org.enso.compiler.generate
|
||||
|
||||
import org.enso.compiler.exception.UnhandledEntity
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.interpreter.{
|
||||
AstArithOp,
|
||||
AstExpression,
|
||||
AstImport,
|
||||
AstLong,
|
||||
AstModuleScope,
|
||||
AstModuleSymbol
|
||||
}
|
||||
import org.enso.compiler.exception.UnhandledEntity
|
||||
import org.enso.interpreter._
|
||||
import org.enso.syntax.text.{AST, Debug}
|
||||
|
||||
// TODO [AA] Please note that this entire translation is _very_ work-in-progress
|
||||
// and is hence quite ugly right now. It will be cleaned up as work progresses,
|
||||
// but it was thought best to land in increments where possible.
|
||||
|
||||
// TODO [Generic]
|
||||
// - Type signatures
|
||||
|
||||
/**
|
||||
* This is a representation of the raw conversion from the Parser [[AST AST]]
|
||||
* to the internal [[IR IR]] used by the static transformation passes.
|
||||
@ -37,13 +33,32 @@ object AstToAstExpression {
|
||||
inputAST match {
|
||||
case AST.Module.any(inputAST) => translateModule(inputAST)
|
||||
case _ => {
|
||||
throw new UnhandledEntity(inputAST, "process")
|
||||
throw new UnhandledEntity(inputAST, "translate")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def translateModuleSymbol(inputAST: AST): AstModuleSymbol = {
|
||||
???
|
||||
inputAST match {
|
||||
case AST.Def(consName, args, body) =>
|
||||
if (body.isDefined) {
|
||||
throw new RuntimeException("Cannot support complex type defs yet!!!!")
|
||||
} else {
|
||||
AstTypeDef(consName.name, args.map(translateArgumentDefinition))
|
||||
}
|
||||
case AstView.MethodDefinition(targetPath, name, definition) =>
|
||||
val path =
|
||||
targetPath.collect { case AST.Ident.Cons(name) => name }.mkString(".")
|
||||
val nameStr = name match { case AST.Ident.Var(name) => name }
|
||||
val defExpression = translateExpression(definition)
|
||||
val defExpr: AstFunction = defExpression match {
|
||||
case fun: AstFunction => fun
|
||||
case expr => AstFunction(List(), expr)
|
||||
}
|
||||
AstMethodDef(path, nameStr, defExpr)
|
||||
case _ =>
|
||||
throw new UnhandledEntity(inputAST, "translateModuleSymbol")
|
||||
}
|
||||
}
|
||||
|
||||
def translateLiteral(literal: AST.Literal): AstLong = {
|
||||
@ -60,9 +75,49 @@ object AstToAstExpression {
|
||||
}
|
||||
}
|
||||
|
||||
def translateApplication(application: AST): AstExpression = {
|
||||
def translateArgumentDefinition(arg: AST): AstArgDefinition = {
|
||||
// TODO [AA] Do this properly
|
||||
arg match {
|
||||
case AstView.DefinitionArgument(arg) =>
|
||||
AstArgDefinition(arg.name, None, false)
|
||||
case AstView.AssignedArgument(name, value) =>
|
||||
AstArgDefinition(name.name, Some(translateExpression(value)), false)
|
||||
case _ =>
|
||||
throw new UnhandledEntity(arg, "translateDefinitionArgument")
|
||||
}
|
||||
}
|
||||
|
||||
def translateCallArgument(arg: AST): AstCallArg = arg match {
|
||||
case AstView.AssignedArgument(left, right) =>
|
||||
AstNamedCallArg(left.name, translateExpression(right))
|
||||
case _ => AstUnnamedCallArg(translateExpression(arg))
|
||||
}
|
||||
|
||||
def translateMethodCall(
|
||||
target: AST,
|
||||
ident: AST.Ident,
|
||||
args: List[AST]
|
||||
): AstExpression = {
|
||||
AstApply(
|
||||
translateExpression(ident),
|
||||
(target :: args).map(translateCallArgument),
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
def translateCallable(application: AST): AstExpression = {
|
||||
application match {
|
||||
case AST.App.Infix(left, fn, right) => {
|
||||
case AstView.Application(name, args) =>
|
||||
AstApply(
|
||||
translateExpression(name),
|
||||
args.map(translateCallArgument),
|
||||
false
|
||||
)
|
||||
case AstView.Lambda(args, body) =>
|
||||
val realArgs = args.map(translateArgumentDefinition)
|
||||
val realBody = translateExpression(body)
|
||||
AstFunction(realArgs, realBody)
|
||||
case AST.App.Infix(left, fn, right) =>
|
||||
// FIXME [AA] We should accept all ops when translating to core
|
||||
val validInfixOps = List("+", "/", "-", "*", "%")
|
||||
|
||||
@ -77,7 +132,6 @@ object AstToAstExpression {
|
||||
s"${fn.name} is not currently a valid infix operator"
|
||||
)
|
||||
}
|
||||
}
|
||||
// case AST.App.Prefix(fn, arg) =>
|
||||
// case AST.App.Section.any(application) => // TODO [AA] left, sides, right
|
||||
// case AST.Mixfix(application) => // TODO [AA] translate if
|
||||
@ -85,11 +139,39 @@ object AstToAstExpression {
|
||||
}
|
||||
}
|
||||
|
||||
def translateIdent(identifier: AST.Ident): AstExpression = {
|
||||
identifier match {
|
||||
// case AST.Ident.Blank(_) => throw new UnhandledEntity("Blank") IR.Identifier.Blank()
|
||||
case AST.Ident.Var(name) => AstVariable(name)
|
||||
case AST.Ident.Cons(name) => AstVariable(name)
|
||||
// case AST.Ident.Opr.any(identifier) => processIdentOperator(identifier)
|
||||
// case AST.Ident.Mod(name) => IR.Identifier.Module(name)
|
||||
case _ =>
|
||||
throw new UnhandledEntity(identifier, "translateIdent")
|
||||
}
|
||||
}
|
||||
|
||||
def translateAssignment(name: AST, expr: AST): AstAssignment = {
|
||||
name match {
|
||||
case AST.Ident.Var(name) => AstAssignment(name, translateExpression(expr))
|
||||
case _ =>
|
||||
throw new UnhandledEntity(name, "translateAssignment")
|
||||
}
|
||||
}
|
||||
|
||||
def translateExpression(inputAST: AST): AstExpression = {
|
||||
inputAST match {
|
||||
case AST.App.any(inputAST) => translateApplication(inputAST)
|
||||
case AstView.Assignment(name, expr) => translateAssignment(name, expr)
|
||||
case AstView.MethodCall(target, name, args) =>
|
||||
translateMethodCall(target, name, args)
|
||||
case AstView.CaseExpression(scrutinee, branches) =>
|
||||
???
|
||||
case AST.App.any(inputAST) => translateCallable(inputAST)
|
||||
case AST.Literal.any(inputAST) => translateLiteral(inputAST)
|
||||
case AST.Group.any(inputAST) => translateGroup(inputAST)
|
||||
case AST.Ident.any(inputAST) => translateIdent(inputAST)
|
||||
case AstView.Block(lines, retLine) =>
|
||||
AstBlock(lines.map(translateExpression), translateExpression(retLine))
|
||||
case _ =>
|
||||
throw new UnhandledEntity(inputAST, "translateExpression")
|
||||
}
|
||||
@ -137,15 +219,22 @@ object AstToAstExpression {
|
||||
case _ => true
|
||||
}
|
||||
|
||||
if (nonImportBlocks.isEmpty) {
|
||||
// FIXME [AA] This is temporary, and should be moved to generate an
|
||||
// error node in Core.
|
||||
throw new RuntimeException("Cannot have no expressions")
|
||||
val definitions = nonImportBlocks.takeWhile {
|
||||
case AST.Def(_, _, _) => true
|
||||
case AstView.MethodDefinition(_) => true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
val statements = nonImportBlocks.dropRight(1).map(translateModuleSymbol)
|
||||
val expression = translateExpression(nonImportBlocks.last)
|
||||
AstModuleScope(imports, statements, expression)
|
||||
val executableExpressions = nonImportBlocks.drop(definitions.length)
|
||||
|
||||
val statements = definitions.map(translateModuleSymbol)
|
||||
val expressions = executableExpressions.map(translateExpression)
|
||||
val block = expressions match {
|
||||
case List() => None
|
||||
case List(expr) => Some(expr)
|
||||
case _ => Some(AstBlock(expressions.dropRight(1), expressions.last))
|
||||
}
|
||||
AstModuleScope(imports, statements, block)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,320 @@
|
||||
package org.enso.compiler.generate
|
||||
|
||||
import org.enso.data
|
||||
import org.enso.data.Shifted.List1
|
||||
import org.enso.interpreter.AstBlock
|
||||
import org.enso.syntax.text.{AST, Debug}
|
||||
|
||||
// TODO [AA] Handle arbitrary parens
|
||||
|
||||
/** This object contains view patterns that allow matching on the parser AST for
|
||||
* more sophisticated constructs.
|
||||
*
|
||||
* These view patterns are implemented as custom unapply methods that only
|
||||
* return [[Some]] when more complex conditions are met.
|
||||
*/
|
||||
object AstView {
|
||||
object Block {
|
||||
def unapply(ast: AST): Option[(List[AST], AST)] = ast match {
|
||||
case AST.Block(_, _, firstLine, lines) =>
|
||||
val actualLines = firstLine.elem :: lines.flatMap(_.elem)
|
||||
if (actualLines.nonEmpty) {
|
||||
Some((actualLines.dropRight(1), actualLines.last))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
object Binding {
|
||||
val bindingOpSym = AST.Ident.Opr("=")
|
||||
|
||||
def unapply(ast: AST): Option[(AST, AST)] = {
|
||||
ast match {
|
||||
case AST.App.Infix.any(ast) =>
|
||||
val left = ast.larg
|
||||
val op = ast.opr
|
||||
val right = ast.rarg
|
||||
|
||||
if (op == bindingOpSym) {
|
||||
Some((left, right))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Assignment {
|
||||
val assignmentOpSym = AST.Ident.Opr("=")
|
||||
|
||||
def unapply(ast: AST): Option[(AST.Ident.Var, AST)] = {
|
||||
ast match {
|
||||
case Binding(AST.Ident.Var.any(left), right) => Some((left, right))
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Lambda {
|
||||
val lambdaOpSym = AST.Ident.Opr("->")
|
||||
|
||||
def unapply(ast: AST): Option[(List[AST], AST)] = {
|
||||
ast match {
|
||||
case AST.App.Infix.any(ast) =>
|
||||
val left = ast.larg
|
||||
val op = ast.opr
|
||||
val right = ast.rarg
|
||||
|
||||
if (op == lambdaOpSym) {
|
||||
left match {
|
||||
case LambdaParamList(args) => Some((args, right))
|
||||
case _ => None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object PatternMatch {
|
||||
// Cons, args
|
||||
def unapply(ast: AST): Option[(AST.Ident.Cons, List[AST])] = {
|
||||
ast match {
|
||||
case SpacedList(AST.Ident.Cons.any(cons) :: xs) =>
|
||||
val realArgs = xs.collect { case a @ MatchParam(_) => a }
|
||||
|
||||
if (realArgs.length == xs.length) {
|
||||
Some((cons, xs))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object MatchParam {
|
||||
def unapply(ast: AST): Option[AST] = ast match {
|
||||
case DefinitionArgument(_) => Some(ast)
|
||||
case PatternMatch(_, _) => Some(ast)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
object FunctionParam {
|
||||
def unapply(ast: AST): Option[AST] = ast match {
|
||||
case AssignedArgument(_, _) => Some(ast)
|
||||
case DefinitionArgument(_) => Some(ast)
|
||||
case PatternMatch(_) => Some(ast)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
object LambdaParamList {
|
||||
//TODO suspended arguments
|
||||
|
||||
def unapply(ast: AST): Option[List[AST]] = {
|
||||
ast match {
|
||||
case SpacedList(args) =>
|
||||
val realArgs = args.collect { case a @ FunctionParam(_) => a }
|
||||
|
||||
if (realArgs.length == args.length) {
|
||||
Some(args)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
// TODO [AA] This really isn't true........
|
||||
case _ => Some(List(ast))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object MaybeParensed {
|
||||
def unapply(ast: AST): Option[AST] = {
|
||||
ast match {
|
||||
case AST.Group(mExpr) => mExpr.flatMap(unapply)
|
||||
case a => Some(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Used for named and defaulted argument syntactic forms. */
|
||||
object AssignedArgument {
|
||||
def unapply(ast: AST): Option[(AST.Ident.Var, AST)] =
|
||||
MaybeParensed.unapply(ast).flatMap(Assignment.unapply)
|
||||
}
|
||||
|
||||
object DefinitionArgument {
|
||||
def unapply(ast: AST): Option[AST.Ident.Var] = ast match {
|
||||
case AST.Ident.Var.any(ast) => Some(ast)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
/** Used for arguments declared as lazy. */
|
||||
object SuspendedArgument {}
|
||||
|
||||
object Application {
|
||||
def unapply(ast: AST): Option[(AST, List[AST])] =
|
||||
SpacedList.unapply(ast).flatMap {
|
||||
case fun :: args =>
|
||||
fun match {
|
||||
case MethodCall(target, function, methodArgs) =>
|
||||
Some((function, target :: methodArgs ++ args))
|
||||
case _ => Some((fun, args))
|
||||
}
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
object MethodCall {
|
||||
def unapply(ast: AST): Option[(AST, AST.Ident, List[AST])] = ast match {
|
||||
case OperatorDot(target, Application(ConsOrVar(ident), args)) =>
|
||||
Some((target, ident, args))
|
||||
case OperatorDot(target, ConsOrVar(ident)) =>
|
||||
Some((target, ident, List()))
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
object SpacedList {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ast
|
||||
* @return the constructor, and a list of its arguments
|
||||
*/
|
||||
def unapply(ast: AST): Option[List[AST]] = {
|
||||
matchSpacedList(ast)
|
||||
}
|
||||
|
||||
def matchSpacedList(ast: AST): Option[List[AST]] = {
|
||||
ast match {
|
||||
case AST.App.Prefix(fn, arg) =>
|
||||
val fnRecurse = matchSpacedList(fn)
|
||||
|
||||
fnRecurse match {
|
||||
case Some(headItems) => Some(headItems :+ arg)
|
||||
case None => Some(List(fn, arg))
|
||||
}
|
||||
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object MethodDefinition {
|
||||
def unapply(ast: AST): Option[(List[AST], AST, AST)] = {
|
||||
ast match {
|
||||
case Binding(lhs, rhs) =>
|
||||
lhs match {
|
||||
case MethodReference(targetPath, name) =>
|
||||
Some((targetPath, name, rhs))
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ConsOrVar {
|
||||
def unapply(arg: AST): Option[AST.Ident] = arg match {
|
||||
case AST.Ident.Var.any(arg) => Some(arg)
|
||||
case AST.Ident.Cons.any(arg) => Some(arg)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
object OperatorDot {
|
||||
def unapply(ast: AST): Option[(AST, AST)] = ast match {
|
||||
case AST.App.Infix(left, AST.Ident.Opr("."), right) => Some((left, right))
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
object Path {
|
||||
|
||||
def unapply(ast: AST): Option[List[AST]] = {
|
||||
val path = matchPath(ast)
|
||||
|
||||
if (path.isEmpty) {
|
||||
None
|
||||
} else {
|
||||
Some(path)
|
||||
}
|
||||
}
|
||||
|
||||
def matchPath(ast: AST): List[AST] = {
|
||||
ast match {
|
||||
case OperatorDot(left, right) =>
|
||||
right match {
|
||||
case AST.Ident.any(right) => matchPath(left) :+ right
|
||||
case _ => List()
|
||||
}
|
||||
case AST.Ident.any(ast) => List(ast)
|
||||
case _ => List()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object MethodReference {
|
||||
def unapply(ast: AST): Option[(List[AST], AST)] = {
|
||||
ast match {
|
||||
case Path(segments) =>
|
||||
if (segments.length >= 2) {
|
||||
val consPath = segments.dropRight(1)
|
||||
val maybeVar = segments.last
|
||||
|
||||
val isValid = consPath.collect {
|
||||
case a @ AST.Ident.Cons(_) => a
|
||||
}.length == consPath.length
|
||||
|
||||
if (isValid) {
|
||||
maybeVar match {
|
||||
case AST.Ident.Var(_) => Some((consPath, maybeVar))
|
||||
case _ => None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object CaseBranch {
|
||||
// TODO [AA]
|
||||
}
|
||||
|
||||
object CaseExpression {
|
||||
val caseName = data.List1(AST.Ident.Var("case"), AST.Ident.Var("of"))
|
||||
|
||||
// scrutinee and branches
|
||||
def unapply(ast: AST): Option[(AST, List[AST])] = {
|
||||
ast match {
|
||||
case AST.Mixfix(identSegments, argSegments) =>
|
||||
if (identSegments == caseName) {
|
||||
println("==== MIXFIX ====")
|
||||
println(Debug.pretty(ast.toString))
|
||||
|
||||
None
|
||||
} else {
|
||||
None
|
||||
}
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -19,14 +19,12 @@ trait AstExpressionVisitor[+T] {
|
||||
|
||||
def visitFunction(
|
||||
arguments: java.util.List[AstArgDefinition],
|
||||
statements: java.util.List[AstExpression],
|
||||
retValue: AstExpression
|
||||
body: AstExpression
|
||||
): T
|
||||
|
||||
def visitCaseFunction(
|
||||
arguments: java.util.List[AstArgDefinition],
|
||||
statements: java.util.List[AstExpression],
|
||||
retValue: AstExpression
|
||||
body: AstExpression
|
||||
): T
|
||||
|
||||
def visitFunctionApplication(
|
||||
@ -46,17 +44,22 @@ trait AstExpressionVisitor[+T] {
|
||||
def visitDesuspend(target: AstExpression): T
|
||||
|
||||
def visitStringLiteral(string: String): T
|
||||
|
||||
def visitBlock(
|
||||
statements: java.util.List[AstExpression],
|
||||
retValue: AstExpression
|
||||
): T
|
||||
}
|
||||
|
||||
trait AstModuleScopeVisitor[+T] {
|
||||
trait AstModuleScopeVisitor[T] {
|
||||
|
||||
@throws(classOf[Exception])
|
||||
def visitModuleScope(
|
||||
imports: java.util.List[AstImport],
|
||||
typeDefs: java.util.List[AstTypeDef],
|
||||
bindings: java.util.List[AstMethodDef],
|
||||
expression: AstExpression
|
||||
): T
|
||||
expression: java.util.Optional[AstExpression]
|
||||
): java.util.Optional[T]
|
||||
}
|
||||
|
||||
sealed trait AstModuleSymbol
|
||||
@ -74,10 +77,10 @@ case class AstImport(name: String)
|
||||
case class AstModuleScope(
|
||||
imports: List[AstImport],
|
||||
bindings: List[AstModuleSymbol],
|
||||
expression: AstExpression
|
||||
expression: Option[AstExpression]
|
||||
) {
|
||||
|
||||
def visit[T](visitor: AstModuleScopeVisitor[T]): T = {
|
||||
def visit[T](visitor: AstModuleScopeVisitor[T]): Optional[T] = {
|
||||
val types = new java.util.ArrayList[AstTypeDef]()
|
||||
val defs = new java.util.ArrayList[AstMethodDef]()
|
||||
|
||||
@ -86,7 +89,12 @@ case class AstModuleScope(
|
||||
case typeDef: AstTypeDef => types.add(typeDef)
|
||||
}
|
||||
|
||||
visitor.visitModuleScope(imports.asJava, types, defs, expression)
|
||||
visitor.visitModuleScope(
|
||||
imports.asJava,
|
||||
types,
|
||||
defs,
|
||||
Optional.ofNullable(expression.orNull)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,24 +188,20 @@ case class AstApply(
|
||||
|
||||
case class AstFunction(
|
||||
arguments: List[AstArgDefinition],
|
||||
statements: List[AstExpression],
|
||||
ret: AstExpression
|
||||
body: AstExpression
|
||||
) extends AstExpression {
|
||||
override def visit[T](visitor: AstExpressionVisitor[T]): T =
|
||||
visitor.visitFunction(arguments.asJava, statements.asJava, ret)
|
||||
visitor.visitFunction(arguments.asJava, body)
|
||||
|
||||
def getArguments: java.util.List[AstArgDefinition] = arguments.asJava
|
||||
|
||||
def getStatements: java.util.List[AstExpression] = statements.asJava
|
||||
}
|
||||
|
||||
case class AstCaseFunction(
|
||||
arguments: List[AstArgDefinition],
|
||||
statements: List[AstExpression],
|
||||
ret: AstExpression
|
||||
body: AstExpression
|
||||
) extends AstExpression {
|
||||
override def visit[T](visitor: AstExpressionVisitor[T]): T =
|
||||
visitor.visitCaseFunction(arguments.asJava, statements.asJava, ret)
|
||||
visitor.visitCaseFunction(arguments.asJava, body)
|
||||
}
|
||||
|
||||
case class AstAssignment(name: String, body: AstExpression)
|
||||
@ -226,6 +230,12 @@ case class AstDesuspend(target: AstExpression) extends AstExpression {
|
||||
visitor.visitDesuspend(target)
|
||||
}
|
||||
|
||||
case class AstBlock(statements: List[AstExpression], retVal: AstExpression)
|
||||
extends AstExpression {
|
||||
override def visit[T](visitor: AstExpressionVisitor[T]): T =
|
||||
visitor.visitBlock(statements.asJava, retVal)
|
||||
}
|
||||
|
||||
class EnsoParserInternal extends JavaTokenParsers {
|
||||
|
||||
override def skipWhitespace: Boolean = true
|
||||
@ -310,17 +320,19 @@ class EnsoParserInternal extends JavaTokenParsers {
|
||||
|
||||
def function: Parser[AstFunction] =
|
||||
("{" ~> (inArgList ?) ~ ((statement <~ ";") *) ~ expression <~ "}") ^^ {
|
||||
case args ~ stmts ~ expr => AstFunction(args.getOrElse(Nil), stmts, expr)
|
||||
case args ~ stmts ~ expr =>
|
||||
AstFunction(args.getOrElse(Nil), AstBlock(stmts, expr))
|
||||
}
|
||||
|
||||
def caseFunction: Parser[AstCaseFunction] = function ^^ {
|
||||
case AstFunction(args, stmts, ret) => AstCaseFunction(args, stmts, ret)
|
||||
case AstFunction(args, AstBlock(stmts, ret)) =>
|
||||
AstCaseFunction(args, AstBlock(stmts, ret))
|
||||
}
|
||||
|
||||
def caseClause: Parser[AstCase] =
|
||||
(expression <~ "~") ~ (caseFunction <~ ";") ^^ {
|
||||
case cons ~ fun =>
|
||||
AstCase(cons, AstCaseFunction(fun.arguments, fun.statements, fun.ret))
|
||||
AstCase(cons, AstCaseFunction(fun.arguments, fun.body))
|
||||
}
|
||||
|
||||
def matchClause: Parser[AstMatch] =
|
||||
@ -338,12 +350,9 @@ class EnsoParserInternal extends JavaTokenParsers {
|
||||
def methodDef: Parser[AstMethodDef] =
|
||||
(ident <~ ".") ~ (ident <~ "=") ~ expression ^^ {
|
||||
case typeName ~ methodName ~ body =>
|
||||
val thisArg =
|
||||
AstArgDefinition(Constants.THIS_ARGUMENT_NAME, None, false)
|
||||
val fun = body match {
|
||||
case b: AstFunction =>
|
||||
b.copy(arguments = thisArg :: b.arguments)
|
||||
case _ => AstFunction(List(thisArg), List(), body)
|
||||
case b: AstFunction => b
|
||||
case _ => AstFunction(List(), body)
|
||||
}
|
||||
AstMethodDef(typeName, methodName, fun)
|
||||
}
|
||||
@ -356,7 +365,7 @@ class EnsoParserInternal extends JavaTokenParsers {
|
||||
def globalScope: Parser[AstModuleScope] =
|
||||
(importStmt *) ~ ((typeDef | methodDef) *) ~ expression ^^ {
|
||||
case imports ~ assignments ~ expr =>
|
||||
AstModuleScope(imports, assignments, expr)
|
||||
AstModuleScope(imports, assignments, Some(expr))
|
||||
}
|
||||
|
||||
def parseGlobalScope(code: String): AstModuleScope = {
|
||||
|
@ -6,16 +6,14 @@ class CurryingTest extends InterpreterTest {
|
||||
"Functions" should "allow partial application" in {
|
||||
val code =
|
||||
"""
|
||||
|@{
|
||||
| apply = { |v, f| @f [v] };
|
||||
| adder = { |a, b| a + b };
|
||||
| plusOne = @apply [f = @adder [1]];
|
||||
| result = @plusOne [10];
|
||||
|apply = v f -> f v
|
||||
|adder = a b -> a + b
|
||||
|plusOne = apply (f = adder 1)
|
||||
|result = plusOne 10
|
||||
|result
|
||||
|}
|
||||
|""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 11
|
||||
eval(code) shouldEqual 11
|
||||
}
|
||||
|
||||
"Functions" should "allow default arguments to be suspended" in {
|
||||
|
@ -6,36 +6,33 @@ class ErrorsTest extends InterpreterTest {
|
||||
"Panics" should "be thrown and stop evaluation" in {
|
||||
val code =
|
||||
"""
|
||||
|type Foo;
|
||||
|type Bar;
|
||||
|type Baz;
|
||||
|@{
|
||||
| @println [@IO, @Foo];
|
||||
| @throw [@Panic, @Bar];
|
||||
| @println [@IO, @Baz]
|
||||
|}
|
||||
|type Foo
|
||||
|type Bar
|
||||
|type Baz
|
||||
|
|
||||
|IO.println Foo
|
||||
|Panic.throw Bar
|
||||
|IO.println Baz
|
||||
|""".stripMargin
|
||||
|
||||
val exception = the[InterpreterException] thrownBy evalOld(code)
|
||||
val exception = the[InterpreterException] thrownBy eval(code)
|
||||
exception.isGuestException shouldEqual true
|
||||
exception.getGuestObject.toString shouldEqual "Bar<>"
|
||||
consumeOut shouldEqual List("Foo<>")
|
||||
exception.getGuestObject.toString shouldEqual "Bar"
|
||||
consumeOut shouldEqual List("Foo")
|
||||
}
|
||||
|
||||
"Panics" should "be recoverable and transformed into errors" in {
|
||||
val code =
|
||||
"""
|
||||
|type MyError;
|
||||
|type MyError
|
||||
|
|
||||
|@{
|
||||
| thrower = { |x| @throw [@Panic, x] };
|
||||
| caught = @recover [@Panic, @thrower [@MyError]];
|
||||
| @println [@IO, caught]
|
||||
|}
|
||||
|thrower = x -> Panic.throw x
|
||||
|caught = Panic.recover (thrower MyError)
|
||||
|IO.println caught
|
||||
|""".stripMargin
|
||||
|
||||
noException shouldBe thrownBy(evalOld(code))
|
||||
consumeOut shouldEqual List("Error:MyError<>")
|
||||
noException shouldBe thrownBy(eval(code))
|
||||
consumeOut shouldEqual List("Error:MyError")
|
||||
}
|
||||
|
||||
"Errors" should "propagate through pattern matches" in {
|
||||
@ -60,26 +57,22 @@ class ErrorsTest extends InterpreterTest {
|
||||
"Errors" should "be catchable by a user-provided special handling function" in {
|
||||
val code =
|
||||
"""
|
||||
|@{
|
||||
| intError = @throw [@Error, 1];
|
||||
| @catch [intError, { |x| x + 3 }]
|
||||
|}
|
||||
|intError = Error.throw 1
|
||||
|intError.catch (x -> x + 3)
|
||||
|""".stripMargin
|
||||
evalOld(code) shouldEqual 4
|
||||
eval(code) shouldEqual 4
|
||||
}
|
||||
|
||||
"Catch function" should "accept a constructor handler" in {
|
||||
val code =
|
||||
"""
|
||||
|type MyCons err;
|
||||
|type MyCons err
|
||||
|
|
||||
|@{
|
||||
| unitErr = @throw [@Error, @Unit];
|
||||
| @println [@IO, @catch [unitErr, MyCons]]
|
||||
|}
|
||||
|unitErr = Error.throw Unit
|
||||
|IO.println (unitErr.catch MyCons)
|
||||
|""".stripMargin
|
||||
evalOld(code)
|
||||
consumeOut shouldEqual List("MyCons<Unit<>>")
|
||||
eval(code)
|
||||
consumeOut shouldEqual List("MyCons<Unit>")
|
||||
}
|
||||
|
||||
"Catch function" should "accept a method handler" in {
|
||||
@ -103,7 +96,7 @@ class ErrorsTest extends InterpreterTest {
|
||||
}
|
||||
|
||||
"Catch function" should "act as identity for non-error values" in {
|
||||
val code = "@catch [10, {|x| x + 1}]"
|
||||
evalOld(code) shouldEqual 10
|
||||
val code = "10.catch (x -> x + 1)"
|
||||
eval(code) shouldEqual 10
|
||||
}
|
||||
}
|
||||
|
@ -6,10 +6,10 @@ class FunctionArgumentsTest extends InterpreterTest {
|
||||
"Functions" should "take arguments and use them in their bodies" in {
|
||||
val code =
|
||||
"""
|
||||
|{ |x| x * x }
|
||||
|x -> x * x
|
||||
|""".stripMargin
|
||||
|
||||
val function = evalOld(code)
|
||||
val function = eval(code)
|
||||
function.call(1) shouldEqual 1
|
||||
function.call(4) shouldEqual 16
|
||||
}
|
||||
@ -17,53 +17,51 @@ class FunctionArgumentsTest extends InterpreterTest {
|
||||
"Function arguments from outer scope" should "be visible in the inner scope" in {
|
||||
val code =
|
||||
"""
|
||||
|{ |a|
|
||||
| add = { |a, b| a + b };
|
||||
| adder = { |b| @add [a,b] };
|
||||
| res = @adder [2];
|
||||
| res
|
||||
|}
|
||||
|a ->
|
||||
| add = a b -> a + b
|
||||
| adder = b -> add a b
|
||||
| adder 2
|
||||
""".stripMargin
|
||||
|
||||
evalOld(code).call(3) shouldEqual 5
|
||||
eval(code).call(3) shouldEqual 5
|
||||
}
|
||||
|
||||
"Lambdas" should "be callable directly without assignment" in {
|
||||
val code =
|
||||
"""
|
||||
|(x y -> x * y) 5 6
|
||||
|""".stripMargin
|
||||
eval(code) shouldEqual 30
|
||||
}
|
||||
|
||||
"Recursion" should "work" in {
|
||||
val code =
|
||||
"""
|
||||
|@{
|
||||
| sumTo = { |x| @ifZero [x, 0, x + (@sumTo [x - 1])] };
|
||||
| @sumTo [10]
|
||||
|}
|
||||
|sumTo = x -> ifZero x 0 (x + (sumTo (x-1)))
|
||||
|sumTo 10
|
||||
""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 55
|
||||
eval(code) shouldEqual 55
|
||||
}
|
||||
|
||||
"Function calls" should "accept more arguments than needed and pass them to the result upon execution" in {
|
||||
val code =
|
||||
"""
|
||||
|@{
|
||||
| f = { |x| { |z| x + z } };
|
||||
|
|
||||
| @f [1, 2]
|
||||
|}
|
||||
|f = x -> z -> x + z
|
||||
|f 1 2
|
||||
|""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 3
|
||||
eval(code) shouldEqual 3
|
||||
}
|
||||
|
||||
"Function calls" should "allow oversaturation and execute until completion" in {
|
||||
val code =
|
||||
"""
|
||||
|@{
|
||||
| f = { | x, y | { | w | { | z | (x * y) + (w + z) } } };
|
||||
|
|
||||
| @f [3, 3, 10, 1]
|
||||
|}
|
||||
|f = x y -> w -> z -> x * y + w + z
|
||||
|f 3 3 10 1
|
||||
|""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 20
|
||||
eval(code) shouldEqual 20
|
||||
}
|
||||
|
||||
"Function calls" should "be able to return atoms that are evaluated with oversaturated args" in {
|
||||
@ -88,27 +86,22 @@ class FunctionArgumentsTest extends InterpreterTest {
|
||||
"""
|
||||
|Unit.myMethod = 1
|
||||
|
|
||||
|@{
|
||||
| f = { |x| myMethod };
|
||||
| t = @f [10, @Unit];
|
||||
|
|
||||
|f = x -> myMethod
|
||||
|t = f 10 Unit
|
||||
|t
|
||||
|}
|
||||
|""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 1
|
||||
eval(code) shouldEqual 1
|
||||
}
|
||||
|
||||
"Recursion closing over lexical scope" should "work properly" in {
|
||||
val code =
|
||||
"""
|
||||
|@{
|
||||
| summator = { |current| @ifZero [current, 0, @{@summator [current - 1]} ] };
|
||||
| res = @summator [0];
|
||||
|summator = current -> ifZero current 0 ((x -> summator (current - 1)) 0)
|
||||
|res = summator 0
|
||||
|res
|
||||
|}
|
||||
|""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 0
|
||||
eval(code) shouldEqual 0
|
||||
}
|
||||
}
|
||||
|
@ -9,71 +9,68 @@ class GlobalScopeTest extends InterpreterTest {
|
||||
"""
|
||||
|Unit.a = 10
|
||||
|
|
||||
|@a [@Unit]
|
||||
|a Unit
|
||||
""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 10
|
||||
eval(code) shouldEqual 10
|
||||
}
|
||||
|
||||
"Functions" should "use values from the global scope in their bodies" in {
|
||||
val code =
|
||||
"""
|
||||
|Unit.a = 10
|
||||
|Unit.addTen = { |b| (@a [@Unit]) + b }
|
||||
|Unit.addTen = b -> a Unit + b
|
||||
|
|
||||
|@addTen [@Unit, 5]
|
||||
|addTen Unit 5
|
||||
""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 15
|
||||
eval(code) shouldEqual 15
|
||||
}
|
||||
|
||||
"Functions" should "be able to call other functions in scope" in {
|
||||
val code =
|
||||
"""
|
||||
|Unit.adder = { |a, b| a + b }
|
||||
|Unit.adder = a b -> a + b
|
||||
|
|
||||
|@{ |multiply|
|
||||
| res = @adder [@Unit, 1, 2];
|
||||
| doubled = res * multiply;
|
||||
|fn = multiply ->
|
||||
| res = adder Unit 1 2
|
||||
| doubled = res * multiply
|
||||
| doubled
|
||||
|} [2]
|
||||
|fn 2
|
||||
""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 6
|
||||
eval(code) shouldEqual 6
|
||||
}
|
||||
|
||||
"Functions" should "be able to be passed as values when in scope" in {
|
||||
val code =
|
||||
"""
|
||||
|Unit.adder = { |a, b| a + b }
|
||||
|Unit.adder = a b -> a + b
|
||||
|
|
||||
|Unit.binaryFn = { |a, b, function|
|
||||
| result = @function [a, b];
|
||||
|Unit.binaryFn = a b function ->
|
||||
| result = function a b
|
||||
| result
|
||||
|}
|
||||
|
|
||||
|@binaryFn [@Unit, 1, 2, { |a, b| @adder [@Unit, a, b] }]
|
||||
|binaryFn Unit 1 2 (a b -> adder Unit a b)
|
||||
""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 3
|
||||
eval(code) shouldEqual 3
|
||||
}
|
||||
|
||||
"Functions" should "be able to mutually recurse in the global scope" in {
|
||||
val code =
|
||||
"""
|
||||
|Unit.decrementCall = { |number|
|
||||
| res = number - 1;
|
||||
| @fn1 [@Unit, res]
|
||||
|}
|
||||
|Unit.decrementCall = number ->
|
||||
| res = number - 1
|
||||
| Unit.fn1 res
|
||||
|
|
||||
|Unit.fn1 = { |number|
|
||||
| @ifZero [number % 3, number, @decrementCall [@Unit, number]]
|
||||
|}
|
||||
|Unit.fn1 = number ->
|
||||
| ifZero (number % 3) number (Unit.decrementCall number)
|
||||
|
|
||||
|@fn1 [@Unit, 5]
|
||||
|Unit.fn1 5
|
||||
""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 3
|
||||
eval(code) shouldEqual 3
|
||||
}
|
||||
|
||||
"Functions" should "be suspended within blocks" in {
|
||||
@ -81,11 +78,11 @@ class GlobalScopeTest extends InterpreterTest {
|
||||
"""
|
||||
|Unit.a = 10/0
|
||||
|
|
||||
|Unit.b = { @a [@Unit] }
|
||||
|Unit.b = Unit.a
|
||||
|b
|
||||
""".stripMargin
|
||||
|
||||
noException should be thrownBy evalOld(code)
|
||||
noException should be thrownBy eval(code)
|
||||
}
|
||||
|
||||
"Exceptions" should "be thrown when called" in {
|
||||
@ -93,11 +90,11 @@ class GlobalScopeTest extends InterpreterTest {
|
||||
"""
|
||||
|Unit.a = 10/0
|
||||
|
|
||||
|Unit.b = { @a [@Unit] }
|
||||
|@b [@Unit]
|
||||
|Unit.b = Unit.a
|
||||
|Unit.b
|
||||
""".stripMargin
|
||||
|
||||
an[InterpreterException] should be thrownBy evalOld(code)
|
||||
an[InterpreterException] should be thrownBy eval(code)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,36 +6,32 @@ class InteropTest extends InterpreterTest {
|
||||
"Interop library" should "support tail recursive functions" in {
|
||||
val code =
|
||||
"""
|
||||
|@{
|
||||
| recurFun = { |i| @ifZero [i, 0, @recurFun [i - 1]] };
|
||||
|recurFun = i -> ifZero i 0 (recurFun i-1)
|
||||
|recurFun
|
||||
|}
|
||||
|""".stripMargin
|
||||
|
||||
val recurFun = evalOld(code)
|
||||
val recurFun = eval(code)
|
||||
recurFun.call(15) shouldEqual 0
|
||||
}
|
||||
|
||||
"Interop library" should "support calling curried functions" in {
|
||||
val code =
|
||||
"""
|
||||
|@{
|
||||
| fun = { |x, y, z| (x + y) + z };
|
||||
| @fun [y = 1]
|
||||
|}
|
||||
|fun = x y z -> x + y + z
|
||||
|fun y=1
|
||||
|""".stripMargin
|
||||
|
||||
val curriedFun = evalOld(code)
|
||||
val curriedFun = eval(code)
|
||||
curriedFun.call(2, 3) shouldEqual 6
|
||||
}
|
||||
|
||||
"Interop library" should "support creating curried calls" in {
|
||||
val code =
|
||||
"""
|
||||
|{ |x, y, z| (x + y) + z }
|
||||
|x y z -> x + y + z
|
||||
|""".stripMargin
|
||||
|
||||
val fun = evalOld(code)
|
||||
val fun = eval(code)
|
||||
fun.call(1).call(2).call(3) shouldEqual 6
|
||||
}
|
||||
|
||||
@ -44,10 +40,10 @@ class InteropTest extends InterpreterTest {
|
||||
"""
|
||||
|Any.method = this
|
||||
|
|
||||
|{ |x| method }
|
||||
|x -> method
|
||||
|""".stripMargin
|
||||
|
||||
val fun = evalOld(code)
|
||||
val fun = eval(code)
|
||||
fun.call(1, 2) shouldEqual 2
|
||||
}
|
||||
}
|
||||
|
@ -1,64 +0,0 @@
|
||||
package org.enso.interpreter.test.semantic
|
||||
|
||||
import org.enso.interpreter.test.{InterpreterException, InterpreterTest}
|
||||
|
||||
class LexicalScopeTest extends InterpreterTest {
|
||||
"Scope capture from outer scope" should "work" in {
|
||||
val code =
|
||||
"""
|
||||
|@{
|
||||
| x = 10;
|
||||
| @{
|
||||
| y = x;
|
||||
| y + 5
|
||||
| }
|
||||
|}
|
||||
""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 15
|
||||
}
|
||||
|
||||
"Variable shadowing" should "work" in {
|
||||
val code =
|
||||
"""
|
||||
|@{
|
||||
| x = 10;
|
||||
| @{
|
||||
| x = 5;
|
||||
| x + 1
|
||||
| }
|
||||
|}
|
||||
""".stripMargin
|
||||
evalOld(code) shouldEqual 6
|
||||
}
|
||||
|
||||
"Variable redefinition in same scope" should "throw error" in {
|
||||
val code =
|
||||
"""
|
||||
|@{
|
||||
| x = 10;
|
||||
| @{
|
||||
| y = x;
|
||||
| y = 5;
|
||||
| y + 1
|
||||
| }
|
||||
|}
|
||||
""".stripMargin
|
||||
the[InterpreterException] thrownBy evalOld(code) should have message "Variable y was already defined in this scope."
|
||||
}
|
||||
|
||||
"Reference to an undefined variable" should "throw error" in {
|
||||
pending
|
||||
//TODO [AA] Pending, because we're not yet sure what the behavior should be in the presence
|
||||
// of dynamic dispatch. `y` in this code is actually equivalent to `x -> x.y`.
|
||||
val code =
|
||||
"""
|
||||
|@{
|
||||
| x = 10;
|
||||
| y
|
||||
|}
|
||||
""".stripMargin
|
||||
the[InterpreterException] thrownBy evalOld(code) should have message "Variable y is not defined."
|
||||
}
|
||||
|
||||
}
|
@ -6,11 +6,75 @@ class MethodsTest extends InterpreterTest {
|
||||
"Methods" should "be defined in the global scope and dispatched to" in {
|
||||
val code =
|
||||
"""
|
||||
|type Foo;
|
||||
|Foo.bar = { |number| number + 1 }
|
||||
|@bar [@Foo, 10]
|
||||
|type Foo
|
||||
|Foo.bar = number -> number + 1
|
||||
|bar Foo 10
|
||||
|""".stripMargin
|
||||
evalOld(code) shouldEqual 11
|
||||
eval(code) shouldEqual 11
|
||||
}
|
||||
|
||||
"Methods" should "be callable with dot operator" in {
|
||||
val code =
|
||||
"""
|
||||
|type Foo
|
||||
|Foo.bar = number -> number + 1
|
||||
|Foo.bar 10
|
||||
|""".stripMargin
|
||||
eval(code) shouldEqual 11
|
||||
}
|
||||
|
||||
"Methods" should "be chainable with dot operator" in {
|
||||
val code =
|
||||
"""
|
||||
|type Foo
|
||||
|type Bar
|
||||
|type Baz
|
||||
|
|
||||
|Foo.bar = Bar
|
||||
|Bar.baz = x -> Baz
|
||||
|Baz.spam = y -> y + 25
|
||||
|
|
||||
|Foo.bar.baz 54 . spam 2
|
||||
|""".stripMargin
|
||||
eval(code) shouldEqual 27
|
||||
}
|
||||
|
||||
"Dot operator" should "behave like parenthesised when non-spaced" in {
|
||||
val code =
|
||||
"""
|
||||
|type Foo
|
||||
|type Bar
|
||||
|
|
||||
|Foo.bar = a b -> a + b
|
||||
|Bar.constant = 10
|
||||
|
|
||||
|Foo.bar Bar.constant Bar.constant
|
||||
|
|
||||
|""".stripMargin
|
||||
eval(code) shouldEqual 20
|
||||
}
|
||||
|
||||
"Methods" should "be able to be defined without arguments" in {
|
||||
val code =
|
||||
"""
|
||||
|type Foo
|
||||
|Foo.bar = 1
|
||||
|bar Foo + 5
|
||||
|""".stripMargin
|
||||
eval(code) shouldEqual 6
|
||||
}
|
||||
|
||||
"Methods" should "be definable as blocks without arguments" in {
|
||||
val code =
|
||||
"""
|
||||
|Any.method =
|
||||
| x = this * this
|
||||
| y = x * 2
|
||||
| y + 1
|
||||
|
|
||||
|3.method
|
||||
|""".stripMargin
|
||||
eval(code) shouldEqual 19
|
||||
}
|
||||
|
||||
"Methods" should "be dispatched to the proper constructor" in {
|
||||
@ -30,19 +94,19 @@ class MethodsTest extends InterpreterTest {
|
||||
"Method call target" should "be passable by-name" in {
|
||||
val code =
|
||||
"""
|
||||
|Unit.testMethod = { |x, y, z| (x + y) + z }
|
||||
|@testMethod [x = 1, y = 2, this = @Unit, z = 3]
|
||||
|Unit.testMethod = x y z -> x + y + z
|
||||
|testMethod x=1 y=2 this=Unit z=3
|
||||
|""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 6
|
||||
eval(code) shouldEqual 6
|
||||
}
|
||||
|
||||
"Calling a non-existent method" should "throw an exception" in {
|
||||
val code =
|
||||
"""
|
||||
|@foo [7]
|
||||
|foo 7
|
||||
|""".stripMargin
|
||||
the[InterpreterException] thrownBy evalOld(code) should have message "Object Number does not define method foo."
|
||||
the[InterpreterException] thrownBy eval(code) should have message "Object Number does not define method foo."
|
||||
}
|
||||
|
||||
"Methods defined on Any type" should "be callable for any type" in {
|
||||
@ -72,4 +136,24 @@ class MethodsTest extends InterpreterTest {
|
||||
evalOld(code)
|
||||
consumeOut shouldEqual List("1", "2", "3", "0", "0", "0")
|
||||
}
|
||||
|
||||
"Test" should "test test" in {
|
||||
pending
|
||||
// val code =
|
||||
// """
|
||||
// |Nil.sum = 0
|
||||
// |Cons.sum = case this of
|
||||
// | Cons h t -> h + sum t
|
||||
// |
|
||||
// |myList = Cons (Cons (Cons 3 Nil) 2) 1
|
||||
// |
|
||||
// |""".stripMargin
|
||||
val code =
|
||||
"""
|
||||
|Cons.sum = case a of
|
||||
| b
|
||||
|""".stripMargin
|
||||
|
||||
eval(code) shouldEqual 6
|
||||
}
|
||||
}
|
||||
|
@ -7,178 +7,164 @@ class NamedArgumentsTest extends InterpreterTest {
|
||||
val code =
|
||||
"""
|
||||
|Unit.a = 10
|
||||
|Unit.addTen = { |b| (@a [@Unit]) + b }
|
||||
|Unit.addTen = b -> a Unit + b
|
||||
|
|
||||
|@addTen [@Unit, b = 10]
|
||||
|addTen Unit (b = 10)
|
||||
""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 20
|
||||
eval(code) shouldEqual 20
|
||||
}
|
||||
|
||||
"Functions" should "be able to have named arguments given out of order" in {
|
||||
val code =
|
||||
"""
|
||||
|Unit.subtract = { |a, b| a - b }
|
||||
|Unit.subtract = a b -> a - b
|
||||
|
|
||||
|@subtract [@Unit, b = 10, a = 5]
|
||||
|subtract Unit (b = 10) (a = 5)
|
||||
""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual -5
|
||||
eval(code) shouldEqual -5
|
||||
}
|
||||
|
||||
"Functions" should "be able to have scope values as named arguments" in {
|
||||
val code =
|
||||
"""
|
||||
|@{
|
||||
| a = 10;
|
||||
| addTen = { |num| num + a };
|
||||
| res = @addTen [num = a];
|
||||
|a = 10
|
||||
|addTen = num -> num + a
|
||||
|res = addTen (num = a)
|
||||
|res
|
||||
|}
|
||||
""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 20
|
||||
eval(code) shouldEqual 20
|
||||
}
|
||||
|
||||
"Functions" should "be able to be defined with default argument values" in {
|
||||
val code =
|
||||
"""
|
||||
|Unit.addNum = { |a, num = 10| a + num }
|
||||
|Unit.addNum = a (num = 10) -> a + num
|
||||
|
|
||||
|@addNum [@Unit, 5]
|
||||
|addNum Unit 5
|
||||
""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 15
|
||||
eval(code) shouldEqual 15
|
||||
}
|
||||
|
||||
"Default arguments" should "be able to default to complex expressions" in {
|
||||
val code =
|
||||
"""
|
||||
|Unit.add = { |a, b| a + b }
|
||||
|Unit.add = a b -> a + b
|
||||
|Unit.doThing = a (b = add Unit 1 2) -> a + b
|
||||
|
|
||||
|Unit.doThing = { |a, b = @add [@Unit, 1, 2]| a + b }
|
||||
|
|
||||
|@doThing [@Unit, 10]
|
||||
|doThing Unit 10
|
||||
|""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 13
|
||||
eval(code) shouldEqual 13
|
||||
}
|
||||
|
||||
"Default arguments" should "be able to close over their outer scope" in {
|
||||
val code =
|
||||
"""
|
||||
|@{
|
||||
| id = { |x| x };
|
||||
|
|
||||
| apply = { |val, fn = id| @fn [val] };
|
||||
|
|
||||
| res = @apply [val = 1];
|
||||
|id = x -> x
|
||||
|apply = val (fn = id) -> fn val
|
||||
|res = apply (val = 1)
|
||||
|res
|
||||
|}
|
||||
|""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 1
|
||||
eval(code) shouldEqual 1
|
||||
}
|
||||
|
||||
"Functions" should "use their default values when none is supplied" in {
|
||||
val code =
|
||||
"""
|
||||
|Unit.addTogether = { |a = 5, b = 6| a + b }
|
||||
|Unit.addTogether = (a = 5) (b = 6) -> a + b
|
||||
|
|
||||
|@addTogether [@Unit]
|
||||
|addTogether Unit
|
||||
""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 11
|
||||
eval(code) shouldEqual 11
|
||||
}
|
||||
|
||||
"Functions" should "override defaults by name" in {
|
||||
val code =
|
||||
"""
|
||||
|Unit.addNum = { |a, num = 10| a + num }
|
||||
|Unit.addNum = a (num = 10) -> a + num
|
||||
|
|
||||
|@addNum [@Unit, 1, num = 1]
|
||||
|addNum Unit 1 (num = 1)
|
||||
""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 2
|
||||
eval(code) shouldEqual 2
|
||||
}
|
||||
|
||||
"Functions" should "override defaults by position" in {
|
||||
val code =
|
||||
"""
|
||||
|Unit.addNum = { |a, num = 10| a + num }
|
||||
|Unit.addNum = a (num = 10) -> a + num
|
||||
|
|
||||
|@addNum [@Unit, 1, 2]
|
||||
|addNum Unit 1 2
|
||||
|""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 3
|
||||
eval(code) shouldEqual 3
|
||||
}
|
||||
|
||||
"Defaulted arguments" should "work in a recursive context" in {
|
||||
val code =
|
||||
"""
|
||||
|Unit.summer = { |sumTo|
|
||||
| summator = { |acc = 0, current|
|
||||
| @ifZero [current, acc, @summator [current = current - 1, acc = acc + current]]
|
||||
| };
|
||||
| res = @summator [current = sumTo];
|
||||
|Unit.summer = sumTo ->
|
||||
| summator = (acc = 0) current ->
|
||||
| ifZero current acc (summator (current = current - 1) (acc = acc + current))
|
||||
| res = summator (current = sumTo)
|
||||
| res
|
||||
|}
|
||||
|
|
||||
|@summer [@Unit, 100]
|
||||
|summer Unit 100
|
||||
""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 5050
|
||||
eval(code) shouldEqual 5050
|
||||
}
|
||||
|
||||
"Named Arguments" should "only be scoped to their definitions" in {
|
||||
val code =
|
||||
"""
|
||||
|@{
|
||||
| foo = { |x, y| x - y };
|
||||
| bar = { |y, x| x - y };
|
||||
|
|
||||
| baz = { |f| @f [x = 10, y = 11] };
|
||||
|
|
||||
| a = @baz [foo];
|
||||
| b = @baz [bar];
|
||||
|
|
||||
|foo = x y -> x - y
|
||||
|bar = y x -> x - y
|
||||
|baz = f -> f (x=10) (y=11)
|
||||
|a = baz foo
|
||||
|b = baz bar
|
||||
|a - b
|
||||
|}
|
||||
|""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 0
|
||||
eval(code) shouldEqual 0
|
||||
}
|
||||
|
||||
"Named arguments" should "be applied in a sequence compatible with Eta-expansions" in {
|
||||
pending
|
||||
val code =
|
||||
"""
|
||||
|Unit.foo = { |a, b, c| a + b }
|
||||
|@foo [@Unit, 20, a = 10]
|
||||
|Unit.foo = a b c -> a + b
|
||||
|foo Unit 20 (a = 10)
|
||||
|""".stripMargin
|
||||
}
|
||||
|
||||
"Default arguments" should "be able to depend on prior arguments" in {
|
||||
val code =
|
||||
"""
|
||||
|Unit.doubleOrAdd = { |a, b = a| a + b }
|
||||
|Unit.doubleOrAdd = a (b = a) -> a + b
|
||||
|
|
||||
|@doubleOrAdd [@Unit, 5]
|
||||
|doubleOrAdd Unit 5
|
||||
|""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 10
|
||||
eval(code) shouldEqual 10
|
||||
}
|
||||
|
||||
"Default arguments" should "not be able to depend on later arguments" in {
|
||||
//TODO: Currently throws something equivalent to "Can't add dynamic symbol to Long". Needs rethinking.
|
||||
val code =
|
||||
"""
|
||||
|Unit.badArgFn = { | a, b = c, c = a | (a + b) + c }
|
||||
|Unit.badArgFn = a (b = c) (c = a) -> a + b + c
|
||||
|
|
||||
|@badArgFn [@Unit, 3]
|
||||
|badArgFn Unit 3
|
||||
|""".stripMargin
|
||||
|
||||
an[InterpreterException] should be thrownBy evalOld(code)
|
||||
an[InterpreterException] should be thrownBy eval(code)
|
||||
}
|
||||
|
||||
"Constructors" should "be able to use named arguments" in {
|
||||
@ -227,13 +213,13 @@ class NamedArgumentsTest extends InterpreterTest {
|
||||
"Default arguments to constructors" should "be resolved dynamically" in {
|
||||
val code =
|
||||
"""
|
||||
|type Cons2 head (rest = Nil2);
|
||||
|type Nil2;
|
||||
|type Cons2 head (rest = Nil2)
|
||||
|type Nil2
|
||||
|
|
||||
|5
|
||||
|""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 5
|
||||
eval(code) shouldEqual 5
|
||||
}
|
||||
|
||||
"Constructors" should "be able to take and use default arguments" in {
|
||||
|
@ -22,4 +22,28 @@ class SimpleArithmeticTest extends InterpreterTest {
|
||||
"2 * 2 / 2" should "equal 2" in {
|
||||
eval("2 * 2 / 2") shouldEqual 2
|
||||
}
|
||||
|
||||
"Things" should "work" in {
|
||||
// val code =
|
||||
// """
|
||||
// |(Cons h t) -> h
|
||||
// |""".stripMargin
|
||||
// val code =
|
||||
// """
|
||||
// |Cons h (Cons t Nil)
|
||||
// |""".stripMargin
|
||||
// val code = // type signature
|
||||
// """
|
||||
// |a : Cons h t -> a
|
||||
// |""".stripMargin
|
||||
// val code = // lambda
|
||||
// """
|
||||
// |(a : Cons h t) -> a
|
||||
// |""".stripMargin
|
||||
// val code = // lambda
|
||||
// """
|
||||
// |(Cons h t : a) -> a
|
||||
// |""".stripMargin
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -6,39 +6,35 @@ class StateTest extends InterpreterTest {
|
||||
"State" should "be accessible from functions" in {
|
||||
val code =
|
||||
"""
|
||||
|@{
|
||||
| @put [@State, 10];
|
||||
| x = @get [@State];
|
||||
| @put [@State, x + 1];
|
||||
| @get [@State]
|
||||
|}
|
||||
|State.put 10
|
||||
|x = State.get
|
||||
|State.put x+1
|
||||
|State.get
|
||||
|""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 11
|
||||
eval(code) shouldEqual 11
|
||||
}
|
||||
|
||||
"State" should "be implicitly threaded through function executions" in {
|
||||
val code =
|
||||
"""
|
||||
|Unit.incState = {
|
||||
| x = @get [@State];
|
||||
| @put [@State, x + 1]
|
||||
|}
|
||||
|Unit.incState =
|
||||
| x = State.get
|
||||
| State.put x+1
|
||||
|
|
||||
|@{
|
||||
| @put [@State, 0];
|
||||
| @incState [@Unit];
|
||||
| @incState [@Unit];
|
||||
| @incState [@Unit];
|
||||
| @incState [@Unit];
|
||||
| @incState [@Unit];
|
||||
| @get [@State]
|
||||
|}
|
||||
|State.put 0
|
||||
|Unit.incState
|
||||
|Unit.incState
|
||||
|Unit.incState
|
||||
|Unit.incState
|
||||
|Unit.incState
|
||||
|State.get
|
||||
|""".stripMargin
|
||||
|
||||
evalOld(code) shouldEqual 5
|
||||
eval(code) shouldEqual 5
|
||||
}
|
||||
|
||||
// TODO [AA,MK]: New syntax must support suspended blocks like `myFun` here
|
||||
"State" should "be localized with State.run" in {
|
||||
val code =
|
||||
"""
|
||||
@ -60,25 +56,22 @@ class StateTest extends InterpreterTest {
|
||||
"State" should "work well with recursive code" in {
|
||||
val code =
|
||||
"""
|
||||
|@{
|
||||
| stateSum = { |n|
|
||||
| acc = @get [@State];
|
||||
| @println[@IO, acc];
|
||||
| @put [@State, acc + n];
|
||||
| @ifZero [n, @get [@State], @stateSum [n-1]]
|
||||
| };
|
||||
| @run [@State, 0, @stateSum [10]]
|
||||
|}
|
||||
|stateSum = n ->
|
||||
| acc = State.get
|
||||
| State.put acc+n
|
||||
| ifZero n State.get (stateSum n-1)
|
||||
|
|
||||
|State.run 0 (stateSum 10)
|
||||
|""".stripMargin
|
||||
evalOld(code) shouldEqual 55
|
||||
eval(code) shouldEqual 55
|
||||
}
|
||||
|
||||
"State" should "be initialized to a Unit by default" in {
|
||||
val code =
|
||||
"""
|
||||
|@println[@IO, @get[@State]]
|
||||
|IO.println State.get
|
||||
|""".stripMargin
|
||||
evalOld(code)
|
||||
eval(code)
|
||||
consumeOut shouldEqual List("Unit<>")
|
||||
}
|
||||
|
||||
@ -111,6 +104,7 @@ class StateTest extends InterpreterTest {
|
||||
| @recover[@Panic, @panicker];
|
||||
| @get[@State]
|
||||
|}
|
||||
|
|
||||
|""".stripMargin
|
||||
evalOld(code) shouldEqual (-5)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user