Add lambdas, types, and methods support to new syntax (#358)

This commit is contained in:
Ara Adkins 2019-11-27 11:32:36 +00:00 committed by GitHub
parent 1e3e105232
commit 9a4332108f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 939 additions and 484 deletions

View File

@ -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.

View File

@ -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(

View File

@ -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
}
}
}
}

View File

@ -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,

View File

@ -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)

View File

@ -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 = {

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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]

View File

@ -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;

View File

@ -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;
}

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -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
}
}
}
}

View File

@ -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 = {

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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."
}
}

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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)
}