Complete AST to AstExpression translation (#374)

This commit is contained in:
Ara Adkins 2019-12-06 19:22:20 +00:00 committed by GitHub
parent 2b8af07148
commit c3acc5c615
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 959 additions and 633 deletions

View File

@ -268,8 +268,8 @@ object Shape extends ShapeImplicit {
extends SegmentFmt[T]
with Phantom
final case class SegmentRawEscape[T](code: RawEscape)
extends SegmentRaw[T]
with Phantom
extends SegmentRaw[T]
with Phantom
///////////
/// App ///
@ -1720,16 +1720,30 @@ object AST {
object Segment {
val Escape = org.enso.syntax.text.ast.text.Escape
type Escape = org.enso.syntax.text.ast.text.Escape
type Escape = org.enso.syntax.text.ast.text.Escape
type RawEscape = org.enso.syntax.text.ast.text.RawEscape
//// Definition ////
val EscT = Shape.SegmentEscape
type EscT = Shape.SegmentEscape[AST]
val RawEscT = Shape.SegmentRawEscape
type RawEscT = Shape.SegmentRawEscape[AST]
val Fmt = Shape.SegmentFmt
type Fmt = Shape.SegmentFmt[AST]
val Raw = Shape.SegmentRaw
type Raw = Shape.SegmentRaw[AST]
object Esc {
def apply(code: Escape): EscT = Shape.SegmentEscape(code)
def unapply(shape: EscT): Option[Escape] =
Shape.SegmentEscape.unapply(shape)
}
object RawEsc {
def apply(code: RawEscape): RawEscT = Shape.SegmentRawEscape(code)
def unapply(shape: RawEscT): Option[RawEscape] =
Shape.SegmentRawEscape.unapply(shape)
}
object Expr {
def apply(t: Option[AST]): Fmt = Shape.SegmentExpr(t)
def unapply(shape: Shape.SegmentExpr[AST]): Option[Option[AST]] =

View File

@ -86,7 +86,7 @@ object Builtin {
}
val if_then = Definition(
Var("if") -> Pattern.Expr(),
Var("if") -> Pattern.Expr(allowBlocks = false),
Var("then") -> Pattern.Expr()
) { ctx =>
ctx.body match {
@ -101,7 +101,7 @@ object Builtin {
}
val if_then_else = Definition(
Var("if") -> Pattern.Expr(),
Var("if") -> Pattern.Expr(allowBlocks = false),
Var("then") -> Pattern.Expr(),
Var("else") -> Pattern.Expr()
) { ctx =>
@ -120,7 +120,7 @@ object Builtin {
}
val case_of = Definition(
Var("case") -> Pattern.Expr(),
Var("case") -> Pattern.Expr(allowBlocks = false),
Var("of") -> Pattern.Block()
) { ctx =>
ctx.body match {

View File

@ -84,28 +84,28 @@ object Pattern {
def apply(ast: AST): Tok = Tok(None, ast)
}
object Var {
def apply(): Var = Var(None)
def apply(): Var = Var(None)
def apply(spaced: Boolean): Var = Var(Some(spaced))
}
object Cons {
def apply(): Cons = Cons(None)
def apply(): Cons = Cons(None)
def apply(spaced: Boolean): Cons = Cons(Some(spaced))
}
object Opr {
def apply(): Opr = Opr(None, None)
def apply(spaced: Spaced): Opr = Opr(spaced, None)
def apply(): Opr = Opr(None, None)
def apply(spaced: Spaced): Opr = Opr(spaced, None)
def apply(spaced: Boolean): Opr = Opr(Some(spaced))
}
object Num {
def apply(): Num = Num(None)
def apply(): Num = Num(None)
def apply(spaced: Boolean): Num = Num(Some(spaced))
}
object Text {
def apply(): Text = Text(None)
def apply(): Text = Text(None)
def apply(spaced: Boolean): Text = Text(Some(spaced))
}
object Block {
def apply(): Block = Block(None)
def apply(): Block = Block(None)
def apply(spaced: Boolean): Block = Block(Some(spaced))
}
@ -118,14 +118,14 @@ object Pattern {
Num(spaced) |
Text(spaced) |
Macro(spaced) |
Invalid(spaced) |
(if (allowBlocks) {
Block(spaced)
Block(spaced) |
Invalid(spaced)
} else {
Nothing()
Invalid(spaced)
})
def Any(spaced: Boolean): Pattern = Any(Some(spaced))
def ErrTillEnd(msg: String): Pattern = Any().tillEnd.err(msg)
def Any(spaced: Boolean): Pattern = Any(Some(spaced))
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
@ -338,23 +338,23 @@ sealed trait Pattern {
////////////////////////////
def ::(that: Pattern): Pattern = Seq(that, this)
def !(that: Pattern): Pattern = Except(that, this)
def |(that: Pattern): Pattern = Or(this, that)
def |(msg: String): Pattern = this | Err(msg, Nothing())
def |?(tag: String): Pattern = Tag(tag, this)
def !(that: Pattern): Pattern = Except(that, this)
def |(that: Pattern): Pattern = Or(this, that)
def |(msg: String): Pattern = this | Err(msg, Nothing())
def |?(tag: String): Pattern = Tag(tag, this)
def or(that: Pattern): Pattern = Or(this, that)
def or(msg: String): Pattern = this | Err(msg, Nothing())
def err(msg: String): Pattern = Err(msg, this)
def but(pat: Pattern): Pattern = Except(pat, this)
def many: Pattern = Many(this)
def many1: Pattern = this :: this.many
def tag(tag: String): Pattern = Tag(tag, this)
def opt: Pattern = this | Nothing()
def build: Pattern = Build(this)
def or(that: Pattern): Pattern = Or(this, that)
def or(msg: String): Pattern = this | Err(msg, Nothing())
def err(msg: String): Pattern = Err(msg, this)
def but(pat: Pattern): Pattern = Except(pat, this)
def many: Pattern = Many(this)
def many1: Pattern = this :: this.many
def tag(tag: String): Pattern = Tag(tag, this)
def opt: Pattern = this | Nothing()
def build: Pattern = Build(this)
def till(end: Pattern): Pattern = this.but(end).many
def tillEnd: Pattern = this :: End() // fixme: rename
def fromBegin: Pattern = Begin() :: this
def tillEnd: Pattern = this :: End() // fixme: rename
def fromBegin: Pattern = Begin() :: this
def matchRevUnsafe(
stream: AST.Stream,

View File

@ -97,6 +97,7 @@ public class ArgDefinitionFactory implements AstArgDefinitionVisitor<ArgumentDef
ExpressionNode defaultedValue = defExpression;
// Note [Handling Suspended Defaults]
if (suspended && defExpression != null) {
RootNode defaultRootNode =
new ClosureRootNode(
@ -118,4 +119,14 @@ public class ArgDefinitionFactory implements AstArgDefinitionVisitor<ArgumentDef
: ArgumentDefinition.ExecutionMode.EXECUTE;
return new ArgumentDefinition(position, name, defaultedValue, executionMode);
}
/* Note [Handling Suspended Defaults]
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Suspended defaults need to be wrapped in a thunk to ensure that they behave properly with
* regards to the expected semantics of lazy arguments.
*
* Were they not wrapped in a thunk, they would be evaluated eagerly, and hence the point at
* which the default would be evaluated would differ from the point at which a passed-in argument
* would be evaluated.
*/
}

View File

@ -4,6 +4,7 @@ import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.ReportPolymorphism;
import com.oracle.truffle.api.frame.FrameUtil;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.source.SourceSection;
import org.enso.interpreter.Language;
import org.enso.interpreter.runtime.callable.function.Function;
@ -18,6 +19,7 @@ import org.enso.interpreter.runtime.state.Stateful;
* determined by the API provided by Truffle.
*/
@ReportPolymorphism
@NodeInfo(shortName = "Closure", description = "A root node for Enso closures")
public class ClosureRootNode extends EnsoRootNode {
@Child private ExpressionNode body;

View File

@ -4,6 +4,7 @@ import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.frame.FrameSlotKind;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.SourceSection;
import org.enso.interpreter.Language;
@ -12,6 +13,7 @@ import org.enso.interpreter.runtime.scope.LocalScope;
import org.enso.interpreter.runtime.scope.ModuleScope;
/** A common base class for all kinds of root node in Enso. */
@NodeInfo(shortName = "Root", description = "A root node for Enso computations")
public abstract class EnsoRootNode extends RootNode {
private final String name;
private final SourceSection sourceSection;

View File

@ -3,6 +3,7 @@ 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.NodeInfo;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
@ -24,6 +25,7 @@ import java.util.Optional;
* must have access to the interpreter, it must take place as part of the interpreter context. As a
* result, this node handles the transformations and re-writes
*/
@NodeInfo(shortName = "ProgramRoot", description = "The root of an Enso program's execution")
public class ProgramRootNode extends EnsoRootNode {
private final Source sourceCode;

View File

@ -22,7 +22,7 @@ import java.util.Arrays;
* <p>It handles computing the values of the arguments to the callable, and also the sorting of
* those arguments into the correct positional order for the callable being called.
*/
@NodeInfo(shortName = "@", description = "Executes function")
@NodeInfo(shortName = "App", description = "Executes function")
public class ApplicationNode extends ExpressionNode {
private @Children ExpressionNode[] argExpressions;

View File

@ -6,6 +6,7 @@ import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.nodes.IndirectCallNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.runtime.callable.CallerInfo;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.state.Stateful;
@ -15,6 +16,7 @@ import org.enso.interpreter.runtime.state.Stateful;
*
* <p>Where possible, it will make the call as a direct call, with potential for inlining.
*/
@NodeInfo(shortName = "ExecCall", description = "Optimises function calls")
public abstract class ExecuteCallNode extends Node {
/**

View File

@ -1,12 +1,9 @@
package org.enso.interpreter.node.callable;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.CachedContext;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.Language;
import org.enso.interpreter.runtime.Context;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
@ -22,6 +19,7 @@ import org.enso.interpreter.runtime.error.RuntimeError;
* <p>The dispatch algorithm works by matching the kind of value the method is requested for and
* delegating to the proper lookup method of {@link UnresolvedSymbol}.
*/
@NodeInfo(shortName = "MethodResolve", description = "Resolves method calls to concrete targets")
public abstract class MethodResolverNode extends Node {
/**

View File

@ -10,7 +10,7 @@ import org.enso.interpreter.runtime.callable.function.Function;
* Reads and evaluates the expression provided as a function argument. It handles the case where
* none is given and the default should be used instead.
*/
@NodeInfo(description = "Read function argument.")
@NodeInfo(shortName = "ReadArg", description = "Read function argument.")
public class ReadArgumentNode extends ExpressionNode {
private final int index;
@Child ExpressionNode defaultValue;

View File

@ -1,6 +1,7 @@
package org.enso.interpreter.node.callable.dispatch;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.runtime.callable.CallerInfo;
import org.enso.interpreter.runtime.state.Stateful;
@ -8,6 +9,7 @@ import org.enso.interpreter.runtime.state.Stateful;
* This node handles optimising calls. It performs detection based on the kind of call being made,
* and then executes the call in the most efficient manner possible for it.
*/
@NodeInfo(shortName = "CallOptimiser", description = "Optimises function calls")
public abstract class CallOptimiserNode extends Node {
/**

View File

@ -4,6 +4,7 @@ import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.frame.*;
import com.oracle.truffle.api.nodes.LoopNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.RepeatingNode;
import org.enso.interpreter.node.callable.ExecuteCallNode;
import org.enso.interpreter.node.callable.ExecuteCallNodeGen;
@ -21,6 +22,7 @@ import org.enso.interpreter.runtime.state.Stateful;
*
* @see TailCallException
*/
@NodeInfo(shortName = "LoopCall", description = "Handles tail-call elimination")
public class LoopingCallOptimiserNode extends CallOptimiserNode {
private final FrameDescriptor loopFrameDescriptor = new FrameDescriptor();
@Child private LoopNode loopNode;

View File

@ -1,6 +1,7 @@
package org.enso.interpreter.node.callable.dispatch;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.node.callable.ExecuteCallNode;
import org.enso.interpreter.node.callable.ExecuteCallNodeGen;
import org.enso.interpreter.runtime.callable.CallerInfo;
@ -13,6 +14,7 @@ import org.enso.interpreter.runtime.state.Stateful;
* LoopingCallOptimiserNode}. Thanks to this design, the (much more common) case of calling a
* function in a non-tail position does not force the overhead of loop.
*/
@NodeInfo(shortName = "SimpleCallOpt", description = "Handles non-tail-call execution")
public class SimpleCallOptimiserNode extends CallOptimiserNode {
@Child private ExecuteCallNode executeCallNode = ExecuteCallNodeGen.create();

View File

@ -9,7 +9,7 @@ import org.enso.interpreter.node.ExpressionNode;
* This node defines the body of a function for execution, as well as the protocol for executing the
* function body.
*/
@NodeInfo(shortName = "{}")
@NodeInfo(shortName = "Block")
public class BlockNode extends ExpressionNode {
@Children private final ExpressionNode[] statements;
@Child private ExpressionNode returnExpr;

View File

@ -3,6 +3,7 @@ package org.enso.interpreter.node.callable.function;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.node.ClosureRootNode;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
@ -13,6 +14,9 @@ import org.enso.interpreter.runtime.callable.function.FunctionSchema;
* This node is responsible for representing the definition of a function. It contains information
* about the function's arguments, as well as the target for calling said function.
*/
@NodeInfo(
shortName = "CreateFn",
description = "Represents the definition of a function at runtime")
public class CreateFunctionNode extends ExpressionNode {
private final RootCallTarget callTarget;
private final FunctionSchema schema;

View File

@ -2,9 +2,12 @@ package org.enso.interpreter.node.callable.thunk;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.callable.argument.Thunk;
/** This node is responsible for wrapping a call target in a {@link Thunk} at execution time. */
@NodeInfo(shortName = "CreateThunk", description = "Wraps a call target in a thunk at runtime")
public class CreateThunkNode extends ExpressionNode {
private final RootCallTarget callTarget;
@ -12,11 +15,24 @@ public class CreateThunkNode extends ExpressionNode {
this.callTarget = callTarget;
}
/**
* Executes the node, creating a {@link Thunk} that wraps the internal {@link
* com.oracle.truffle.api.CallTarget}.
*
* @param frame the stack frame for execution
* @return the thunk
*/
@Override
public Object executeGeneric(VirtualFrame frame) {
return new Thunk(this.callTarget, frame.materialize());
}
/**
* Creates a new {@link CreateThunkNode}.
*
* @param callTarget the call target to wrap into a {@link Thunk}.
* @return the node
*/
public static CreateThunkNode build(RootCallTarget callTarget) {
return new CreateThunkNode(callTarget);
}

View File

@ -5,12 +5,14 @@ import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.FrameUtil;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.node.callable.argument.ThunkExecutorNode;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.state.Stateful;
/** Node responsible for handling user-requested thunks forcing. */
@NodeInfo(shortName = "Force", description = "Forces execution of a thunk at runtime")
@NodeChild(value = "target", type = ExpressionNode.class)
public abstract class ForceNode extends ExpressionNode {
@Specialization

View File

@ -1,9 +1,11 @@
package org.enso.interpreter.node.controlflow;
import com.oracle.truffle.api.nodes.ControlFlowException;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.runtime.state.Stateful;
/** This exception is used to signal when a certain branch in a case expression has been taken. */
@NodeInfo(shortName = "BranchSelect", description = "Signals that a case branch has been selected")
public class BranchSelectedException extends ControlFlowException {
private final Stateful result;

View File

@ -1,12 +1,14 @@
package org.enso.interpreter.node.controlflow;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.function.Function;
/** An abstract representation of a case expression. */
@NodeInfo(shortName = "CaseOf", description = "Represents a case expression at runtime")
public abstract class CaseNode extends BaseNode {
/**
* Executes the case expression with an atom scrutinee.

View File

@ -2,6 +2,7 @@ package org.enso.interpreter.node.controlflow;
import com.oracle.truffle.api.frame.FrameUtil;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import com.oracle.truffle.api.profiles.ConditionProfile;
import org.enso.interpreter.node.ExpressionNode;
@ -13,6 +14,7 @@ import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.type.TypesGen;
/** An implementation of the case expression specialised to working on constructors. */
@NodeInfo(shortName = "ConsCaseNode")
public class ConstructorCaseNode extends CaseNode {
@Child private ExpressionNode matcher;
@Child private ExpressionNode branch;

View File

@ -1,6 +1,7 @@
package org.enso.interpreter.node.controlflow;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.error.InexhaustivePatternMatchException;
@ -9,6 +10,9 @@ import org.enso.interpreter.runtime.error.InexhaustivePatternMatchException;
* This node is used when there is no explicit default case provided by the user for a pattern
* match. It is used to signal inexhaustivity.
*/
@NodeInfo(
shortName = "DefaultFallback",
description = "A fallback branch for a case expression when none is explicitly provided")
public class DefaultFallbackNode extends CaseNode {
/**

View File

@ -2,6 +2,7 @@ package org.enso.interpreter.node.controlflow;
import com.oracle.truffle.api.frame.FrameUtil;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.node.callable.ExecuteCallNode;
@ -13,6 +14,7 @@ import org.enso.interpreter.runtime.callable.function.Function;
* This node represents an explicit catch-call case in a pattern match, as provided by the user. It
* executes the catch-all case code.
*/
@NodeInfo(shortName = "Fallback", description = "An explicit fallback branch in a case expression")
public class FallbackNode extends CaseNode {
@Child private ExpressionNode functionNode;
@Child private ExecuteCallNode executeCallNode = ExecuteCallNodeGen.create();

View File

@ -2,6 +2,7 @@ package org.enso.interpreter.node.expression.atom;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
@ -9,6 +10,7 @@ import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
* A node instantiating a constant {@link AtomConstructor} with values computed based on the
* children nodes.
*/
@NodeInfo(shortName = "Instantiate", description = "Instantiates a constant Atom constructor")
public class InstantiateNode extends ExpressionNode {
private final AtomConstructor constructor;
private @Children ExpressionNode[] arguments;

View File

@ -7,7 +7,7 @@ import org.enso.interpreter.Language;
import org.enso.interpreter.runtime.state.Stateful;
/** Root node for use by all the builtin functions. */
@NodeInfo(description = "Root node for builtin functions.")
@NodeInfo(shortName = "BuiltinRoot", description = "Root node for builtin functions.")
public abstract class BuiltinRootNode extends RootNode {
protected BuiltinRootNode(Language language) {
super(language);
@ -22,4 +22,12 @@ public abstract class BuiltinRootNode extends RootNode {
*/
@Override
public abstract Stateful execute(VirtualFrame frame);
/**
* Gets the source-level name of this node.
*
* @return the source-level name of the node
*/
@Override
public abstract String getName();
}

View File

@ -63,4 +63,14 @@ public class IfZeroNode extends BuiltinRootNode {
new ArgumentDefinition(1, "ifTrue", ArgumentDefinition.ExecutionMode.PASS_THUNK),
new ArgumentDefinition(2, "ifFalse", ArgumentDefinition.ExecutionMode.PASS_THUNK));
}
/**
* Gets the source-level name of this node.
*
* @return the source-level name of the node
*/
@Override
public String getName() {
return "Number.ifZero";
}
}

View File

@ -47,4 +47,14 @@ public class DebugBreakpointNode extends BuiltinRootNode {
new ArgumentDefinition(
0, Constants.THIS_ARGUMENT_NAME, ArgumentDefinition.ExecutionMode.EXECUTE));
}
/**
* Gets the source-level name of this node.
*
* @return the source-level name of the node
*/
@Override
public String getName() {
return "Debug.breakpoint";
}
}

View File

@ -52,4 +52,14 @@ public class DebugEvalNode extends BuiltinRootNode {
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "expression", ArgumentDefinition.ExecutionMode.EXECUTE));
}
/**
* Gets the source-level name of this node.
*
* @return the source-level name of the node
*/
@Override
public String getName() {
return "Debug.eval";
}
}

View File

@ -67,4 +67,14 @@ public class CatchErrorNode extends BuiltinRootNode {
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "handler", ArgumentDefinition.ExecutionMode.EXECUTE));
}
/**
* Gets the source-level name of this node.
*
* @return the source-level name of the node
*/
@Override
public String getName() {
return "Error.catch";
}
}

View File

@ -61,4 +61,14 @@ public class CatchPanicNode extends BuiltinRootNode {
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "value", ArgumentDefinition.ExecutionMode.PASS_THUNK));
}
/**
* Gets the source-level name of this node.
*
* @return the source-level name of the node
*/
@Override
public String getName() {
return "Panic.catch";
}
}

View File

@ -49,4 +49,14 @@ public class ThrowErrorNode extends BuiltinRootNode {
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "payload", ArgumentDefinition.ExecutionMode.EXECUTE));
}
/**
* Gets the source-level name of this node.
*
* @return the source-level name of the node
*/
@Override
public String getName() {
return "Error.throw";
}
}

View File

@ -1,6 +1,7 @@
package org.enso.interpreter.node.expression.builtin.function;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.profiles.ConditionProfile;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.callable.InvokeCallableNode;
@ -12,6 +13,13 @@ import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import org.enso.interpreter.runtime.state.Stateful;
import org.enso.interpreter.runtime.type.TypesGen;
/**
* This node implements the built-in functionality for the explicit {@code call} operator on
* functions.
*
* <p>It is a standard builtin node, and hence conforms to the interface for these.
*/
@NodeInfo(shortName = "Function.call", description = "Allows function calls to be made explicitly")
public class ExplicitCallFunctionNode extends BuiltinRootNode {
private @Child InvokeCallableNode invokeCallableNode;
private final ConditionProfile isFunctionProfile = ConditionProfile.createCountingProfile();
@ -26,6 +34,12 @@ public class ExplicitCallFunctionNode extends BuiltinRootNode {
this.invokeCallableNode.markTail();
}
/**
* Forces execution of a function.
*
* @param frame current execution frame
* @return the value of executing the function.
*/
@Override
public Stateful execute(VirtualFrame frame) {
Object[] arguments = Function.ArgumentsHelper.getPositionalArguments(frame.getArguments());
@ -39,10 +53,24 @@ public class ExplicitCallFunctionNode extends BuiltinRootNode {
}
}
/**
* Creates a {@link Function} object that forces the execution of the object it is applied to.
*
* <p>This behaves in a curried manner, so for some function {@code f} you can call it with
* arguments where necessary (e.g. {@code f.call a b}.
*
* @param language the current {@link Language} instance
* @return a {@link Function} object wrapping the behavior of this node
*/
public static Function makeFunction(Language language) {
return Function.fromBuiltinRootNode(
new ExplicitCallFunctionNode(language),
FunctionSchema.CallStrategy.DIRECT_WHEN_TAIL,
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE));
}
@Override
public String getName() {
return "Function.call";
}
}

View File

@ -49,4 +49,14 @@ public abstract class PrintNode extends BuiltinRootNode {
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "value", ArgumentDefinition.ExecutionMode.EXECUTE));
}
/**
* Gets the source-level name of this node.
*
* @return the source-level name of the node
*/
@Override
public String getName() {
return "IO.println";
}
}

View File

@ -40,4 +40,14 @@ public class GetStateNode extends BuiltinRootNode {
FunctionSchema.CallStrategy.ALWAYS_DIRECT,
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE));
}
/**
* Gets the source-level name of this node.
*
* @return the source-level name of the node
*/
@Override
public String getName() {
return "State.get";
}
}

View File

@ -42,4 +42,14 @@ public class PutStateNode extends BuiltinRootNode {
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "newState", ArgumentDefinition.ExecutionMode.EXECUTE));
}
/**
* Gets the source-level name of this node.
*
* @return the source-level name of the node
*/
@Override
public String getName() {
return "State.put";
}
}

View File

@ -63,4 +63,9 @@ public class RunStateNode extends BuiltinRootNode {
new ArgumentDefinition(
2, "statefulComputation", ArgumentDefinition.ExecutionMode.PASS_THUNK));
}
@Override
public String getName() {
return "State.run";
}
}

View File

@ -1,10 +1,12 @@
package org.enso.interpreter.node.expression.constant;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
/** Represents a type constructor definition. */
@NodeInfo(shortName = "Cons", description = "Represents a constructor definition")
public class ConstructorNode extends ExpressionNode {
private final AtomConstructor constructor;

View File

@ -1,10 +1,12 @@
package org.enso.interpreter.node.expression.constant;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
/** Simple constant node that always results in the same {@link UnresolvedSymbol}. */
@NodeInfo(shortName = "DynamicSym")
public class DynamicSymbolNode extends ExpressionNode {
private final UnresolvedSymbol unresolvedSymbol;

View File

@ -16,7 +16,7 @@ import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.state.Stateful;
/** A base node serving as an instrumentable marker. */
@NodeInfo(description = "Instrumentation marker node.")
@NodeInfo(shortName = "Breakpoint", description = "Instrumentation marker node.")
@GenerateWrapper
public abstract class BreakpointNode extends Node implements InstrumentableNode {

View File

@ -7,7 +7,7 @@ import org.enso.interpreter.node.callable.CaptureCallerInfoNode;
import org.enso.interpreter.runtime.callable.CallerInfo;
/** Node capturing the runtime execution scope of its child. */
@NodeInfo(description = "Captures the child's execution scope.")
@NodeInfo(shortName = "ScopeCapture", description = "Captures the child's execution scope.")
public class CaptureResultScopeNode extends ExpressionNode {
/** Value object wrapping the expression return value and the execution scope. */

View File

@ -18,7 +18,7 @@ import org.enso.interpreter.runtime.scope.ModuleScope;
import org.enso.interpreter.runtime.state.Stateful;
/** Node running Enso expressions passed to it as strings. */
@NodeInfo(description = "Evaluates code passed to it as string")
@NodeInfo(shortName = "Eval", description = "Evaluates code passed to it as string")
public abstract class EvalNode extends BaseNode {
private final boolean shouldCaptureResultScope;
@ -61,7 +61,8 @@ public abstract class EvalNode extends BaseNode {
lookupContextReference(Language.class)
.get()
.compiler()
.runInline(expression, language, localScope, moduleScope).getOrElse(null);
.runInline(expression, language, localScope, moduleScope)
.getOrElse(null);
if (expr == null) {
throw new RuntimeException("Invalid code passed to `eval`: " + expression);
}

View File

@ -164,12 +164,18 @@ class Compiler(
* representation.
*
* @param sourceAST the parser AST input
* @return an IR representation with a 1:1 mapping to the parser AST
* constructs
* @return an IR representation of the program represented by `sourceAST`
*/
def translate(sourceAST: AST): AstModuleScope =
AstToAstExpression.translate(sourceAST)
/**
* Lowers the input AST to the compiler's high-level intermediate
* representation.
*
* @param sourceAST the parser AST representing the program source
* @return an IR representation of the program represented by `sourceAST`
*/
def translateInline(sourceAST: AST): Option[AstExpression] =
AstToAstExpression.translateInline(sourceAST)
}

View File

@ -0,0 +1,12 @@
package org.enso.compiler.core
/** [[Core]] is the sophisticated internal representation supported by the
* compiler.
*
* It is a structure designed to be amenable to program analysis and
* transformation and features:
* - High performance on a mutable graph structure.
* - High levels of type-safety to reduce the incidence of bugs.
* - Mutable links to represent program structure.
*/
object Core {}

View File

@ -1,186 +0,0 @@
package org.enso.compiler.core
import org.enso.compiler.core.IR.Literal.Text
import org.enso.syntax.text.AST
import org.enso.syntax.text.ast.text.Escape
// TODO [AA] REMOVE THIS ENTIRE FILE ONCE WE HAVE NEW CORE
/**
* This is the compiler's high-level intermediate representation.
*
* [[IR]] is a close match for the program structure of the source language,
* allowing it to be used for a number of high-level operations, including
* desugaring and analysis passes that rely on the structure of the source
* program to operate.
*/
sealed trait IR
object IR {
/**
* An expression is any language construct that returns a value, even if that
* value is `Unit`.
*/
sealed trait Expression extends IR
/**
* A module is the top-level construct of an Enso program.
*
* Modules currently have a one-to-one correspondence with the file scope,
* but this design may change in the future.
*
* @param elements all constructs contained within the module
*/
final case class Module(elements: List[IR]) extends Expression
/**
* An identifier is a name given to an Enso language construct.
*
* Each kind of identifier has different rules as to what constitutes
* validity, but these rules are enforced by the parser and so need not be
* checked at IR construction time.
*/
sealed trait Identifier extends Expression
object Identifier {
final case class Blank() extends Identifier
final case class Variable(name: String) extends Identifier
final case class Constructor(name: String) extends Identifier
final case class Operator(name: String) extends Identifier
final case class Module(name: String) extends Identifier
}
/**
* A binding is any top-level construct that creates a source-level primitive
* entity.
*/
sealed trait Binding extends Expression
object Binding {
final case class Import(modulePath: List[Identifier.Constructor])
extends Binding
final case class Type() extends Binding
final case class RawType(
constructor: Identifier.Constructor,
arguments: List[IR],
body: Option[IR]
) extends Binding
final case class Function() extends Binding
final case class Lambda() extends Binding
final case class Assignment() extends Binding
}
/**
* An application is any IR entity that applies a function to zero or more
* arguments.
*/
sealed trait Application extends Expression
object Application {
final case class Prefix(fn: IR, arg: IR) extends Application
final case class Infix(
left: IR,
fn: Identifier.Operator,
right: IR
) extends Application
final case class Mixfix(
segments: List[Identifier],
args: List[IR]
) extends Application
/**
* Operator sections are a syntactic construct that provide a short-hand
* for partial application of operators.
*/
sealed trait Section extends Application
object Section {
final case class Left(arg: IR, operator: Identifier.Operator)
extends Section
final case class Right(operator: Identifier.Operator, arg: IR)
extends Section
final case class Sides(operator: Identifier.Operator) extends Section
}
}
/** Literals are constant values provided as part of the program's source. */
sealed trait Literal extends Expression
object Literal {
final case class Number(number: String, base: Option[String])
extends Literal
/**
* Text literals in Enso come in two main types.
*
* Raw text literals are uninterpolated, and are passed through as they are
* provided in the program's source.
*
* Format text literals are literals that can contain Enso source-code
* expressions spliced into the literal. These expressions can be as simple
* as variable references, but are allowed to be arbitrary programs.
*/
sealed trait Text extends Literal
object Text {
final case class Raw(body: List[Text.Line]) extends Text
final case class Format(body: List[Text.Line]) extends Text
final case class Line(lineSegments: List[Segment])
sealed trait Segment extends Text
object Segment {
final case class Plain(text: String) extends Segment
final case class Expression(expr: Option[IR]) extends Segment
final case class EscapeCode(escape: Escape) extends Segment
}
}
}
/**
* Control flow constructs allow encoding non-linear programs.
*
* Enso technically only has the `case ... of` statement as its sole control
* flow construct. However, performance reasons force us to encode `if then`
* and `if then else` as independent constructs rather than as part of the
* standard library, so they are represented here.
*/
sealed trait Control extends Expression
object Control {
final case class Case() extends Expression
final case class IfThen() extends Expression
final case class IfThenElse() extends Expression
}
/** Constructs that purely represent program structure. */
sealed trait Block extends Expression
object Block {
final case class Enso(lines: List[IR]) extends Block
final case class Foreign(language: String, code: List[String]) extends Block
}
/** Constructs that represent various kinds of invalid programs. */
sealed trait Error extends IR
object Error {
final case class UnexpectedToken(msg: String, unexpectedIR: List[IR])
extends Error
final case class UnrecognisedSymbol(symbol: String) extends Error
final case class EmptyGroup() extends Error
final case class UnhandledAST(ast: AST) extends Error
final case class InvalidSuffix(identifier: IR, suffix: String) extends Error
final case class UnclosedText(lines: List[Text.Line]) extends Error
}
/** Comments in the program source. */
final case class Comment(lines: List[String]) extends IR
/** A representation of type signatures */
final case class Signature() extends IR
// UTILITY FUNCTIONS ========================================================
/**
* Checks whether a given IR node represents an invalid portion of a program.
*
* @param ir the node to analyse
* @return `true` if `ir` represents an invalid portion of the program,
* otherwise `false`
*/
def isErrorNode(ir: IR): Boolean = ir match {
case _: Error => true
}
}

View File

@ -3,36 +3,47 @@ package org.enso.compiler.generate
import cats.Foldable
import cats.implicits._
import org.enso.compiler.core
import org.enso.compiler.core.{IR, _}
import org.enso.compiler.core._
import org.enso.compiler.exception.UnhandledEntity
import org.enso.syntax.text.{AST, Location}
// 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 [AA} Things that will need to be done at later stages:
// - Retain information about groups for diagnostics and desugarings
// TODO [Generic]
// - Groups
// - Translate if-then-else to a function and test it by definining
// `if_then_else` on the type
// - Type signatures
// FIXME [AA] All places where we currently throw a `RuntimeException` should
// generate informative and useful nodes in core.
/**
* This is a representation of the raw conversion from the Parser [[AST AST]]
* to the internal [[IR IR]] used by the static transformation passes.
* This file contains the functionality that translates from the parser's
* [[AST]] type to the internal representation used by the compiler.
*
* This representation is currently [[AstExpression]], but this will change as
* [[Core]] becomes implemented. Most function docs will refer to [[Core]]
* now, as this will become true soon.
*/
object AstToAstExpression {
/** Translates an inline expression from the parser into an internal rep.
/** Translates a program represented in the parser [[AST]] to the compiler's
* [[Core]] internal representation.
*
* The restrictions that this must be only an expression are artificial and
* currently enforced only due to limitations of the interpreter for now.
* @param inputAST the [[AST]] representing the program to translate
* @return the [[Core]] representation of `inputAST`
*/
def translate(inputAST: AST): AstModuleScope = {
inputAST match {
case AST.Module.any(inputAST) => translateModule(inputAST)
case _ => {
throw new UnhandledEntity(inputAST, "translate")
}
}
}
/** Translates an inline program expression represented in the parser [[AST]]
* into the compiler's [[Core]] representation.
*
* @param inputAST
* @return
* Inline expressions must _only_ be expressions, and may not contain any
* type of definition.
*
* @param inputAST the [[AST]] representing the expression to translate.
* @return the [[Core]] representation of `inputAST` if it is valid,
* otherwise [[None]]
*/
def translateInline(inputAST: AST): Option[AstExpression] = {
inputAST match {
@ -66,7 +77,11 @@ object AstToAstExpression {
}
}
// TODO [AA] Fix the types
/** Translate a top-level Enso module into [[Core]].
*
* @param module the [[AST]] representation of the module to translate
* @return the [[Core]] representation of `module`
*/
def translateModule(module: AST.Module): AstModuleScope = {
module match {
case AST.Module(blocks) => {
@ -110,26 +125,12 @@ object AstToAstExpression {
}
}
/**
* Transforms the input [[AST]] into the compiler's high-level intermediate
* representation.
/** Translates a module-level definition from its [[AST]] representation into
* [[Core]].
*
* @param inputAST the AST to transform
* @return a representation of the program construct represented by
* `inputAST` in the compiler's [[IR IR]]
* @param inputAST the definition to be translated
* @return the [[Core]] representation of `inputAST`
*/
def translate(inputAST: AST): AstModuleScope = {
// println(Debug.pretty(inputAST.toString))
// println("=========================================")
inputAST match {
case AST.Module.any(inputAST) => translateModule(inputAST)
case _ => {
throw new UnhandledEntity(inputAST, "translate")
}
}
}
def translateModuleSymbol(inputAST: AST): AstModuleSymbol = {
inputAST match {
case AST.Def(consName, args, body) =>
@ -153,6 +154,80 @@ object AstToAstExpression {
}
}
/** Translates an arbitrary program expression from [[AST]] into [[Core]].
*
* @param inputAST the expresion to be translated
* @return the [[Core]] representation of `inputAST`
*/
def translateExpression(inputAST: AST): AstExpression = {
inputAST match {
case AstView
.SuspendedBlock(name, block @ AstView.Block(lines, lastLine)) =>
AstAssignment(
inputAST.location,
name.name,
AstBlock(
block.location,
lines.map(translateExpression),
translateExpression(lastLine),
suspended = true
)
)
case AstView.Assignment(name, expr) =>
translateBinding(inputAST.location, name, expr)
case AstView.MethodCall(target, name, args) =>
AstApply(
inputAST.location,
translateExpression(name),
(target :: args).map(translateCallArgument),
false
)
case AstView.CaseExpression(scrutinee, branches) =>
val actualScrutinee = translateExpression(scrutinee)
val nonFallbackBranches =
branches
.takeWhile(AstView.FallbackCaseBranch.unapply(_).isEmpty)
.map(translateCaseBranch)
val potentialFallback =
branches
.drop(nonFallbackBranches.length)
.headOption
.map(translateFallbackBranch)
AstMatch(
inputAST.location,
actualScrutinee,
nonFallbackBranches,
potentialFallback
)
case AST.App.any(inputAST) => translateApplicationLike(inputAST)
case AST.Mixfix.any(inputAST) => translateApplicationLike(inputAST)
case AST.Literal.any(inputAST) => translateLiteral(inputAST)
case AST.Group.any(inputAST) =>
translateGroup(inputAST, translateExpression)
case AST.Ident.any(inputAST) => translateIdent(inputAST)
case AstView.Block(lines, retLine) =>
AstBlock(
inputAST.location,
lines.map(translateExpression),
translateExpression(retLine)
)
case AST.Comment.any(inputAST) => translateComment(inputAST)
case AST.Invalid.any(inputAST) => translateInvalid(inputAST)
case AST.Foreign(_, _, _) =>
throw new RuntimeException(
"Enso does not yet support foreign language blocks"
)
case _ =>
throw new UnhandledEntity(inputAST, "translateExpression")
}
}
/** Translates a program literal from its [[AST]] representation into
* [[Core]].
*
* @param literal the literal to translate
* @return the [[Core]] representation of `literal`
*/
def translateLiteral(literal: AST.Literal): AstExpression = {
literal match {
case AST.Literal.Number(base, number) => {
@ -166,7 +241,8 @@ object AstToAstExpression {
literal.shape match {
case AST.Literal.Text.Line.Raw(segments) =>
val fullString = segments.collect {
case AST.Literal.Text.Segment.Plain(str) => str
case AST.Literal.Text.Segment.Plain(str) => str
case AST.Literal.Text.Segment.RawEsc(code) => code.repr
}.mkString
AstStringLiteral(literal.location, fullString)
@ -175,14 +251,14 @@ object AstToAstExpression {
.map(
t =>
t.text.collect {
case AST.Literal.Text.Segment.Plain(str) => str
case AST.Literal.Text.Segment.Plain(str) => str
case AST.Literal.Text.Segment.RawEsc(code) => code.repr
}.mkString
)
.mkString("\n")
AstStringLiteral(literal.location, fullString)
case AST.Literal.Text.Block.Fmt(_, _, _) =>
// TODO [AA] Add support for format strings
throw new RuntimeException("Format strings not yet supported")
case AST.Literal.Text.Line.Fmt(_) =>
throw new RuntimeException("Format strings not yet supported")
@ -193,12 +269,19 @@ object AstToAstExpression {
}
}
/** Translates an argument definition from [[AST]] into [[Core]].
*
* @param arg the argument to translate
* @param isSuspended `true` if the argument is suspended, otherwise `false`
* @return the [[Core]] representation of `arg`
*/
@scala.annotation.tailrec
def translateArgumentDefinition(
arg: AST,
isSuspended: Boolean = false
): AstArgDefinition = {
arg match {
case AstView.LazyAssignedArgument(name, value) =>
case AstView.LazyAssignedArgumentDefinition(name, value) =>
AstArgDefinition(
name.name,
Some(translateExpression(value)),
@ -219,30 +302,28 @@ object AstToAstExpression {
}
}
/** Translates a call-site function argument from its [[AST]] representation
* into [[Core]].
*
* @param arg the argument to translate
* @return the [[Core]] representation of `arg`
*/
def translateCallArgument(arg: AST): AstCallArg = arg match {
case AstView.AssignedArgument(left, right) =>
AstNamedCallArg(left.name, translateExpression(right))
case _ => AstUnnamedCallArg(translateExpression(arg))
}
def translateMethodCall(
location: Option[Location],
target: AST,
ident: AST.Ident,
args: List[AST]
): AstExpression = {
AstApply(
location,
translateExpression(ident),
(target :: args).map(translateCallArgument),
false
)
}
def translateCallable(application: AST): AstExpression = {
application match {
/** Translates an arbitrary expression that takes the form of a syntactic
* application from its [[AST]] representation into [[Core]].
*
* @param callable the callable to translate
* @return the [[Core]] representation of `callable`
*/
def translateApplicationLike(callable: AST): AstExpression = {
callable match {
case AstView.ForcedTerm(term) =>
AstForce(application.location, translateExpression(term))
AstForce(callable.location, translateExpression(term))
case AstView.Application(name, args) =>
val validArguments = args.filter {
case AstView.SuspendDefaultsOperator(_) => false
@ -256,7 +337,7 @@ object AstToAstExpression {
val hasDefaultsSuspended = suspendPositions.contains(args.length - 1)
AstApply(
application.location,
callable.location,
translateExpression(name),
validArguments.map(translateCallArgument),
hasDefaultsSuspended
@ -264,14 +345,14 @@ object AstToAstExpression {
case AstView.Lambda(args, body) =>
val realArgs = args.map(translateArgumentDefinition(_))
val realBody = translateExpression(body)
AstFunction(application.location, realArgs, realBody)
AstFunction(callable.location, realArgs, realBody)
case AST.App.Infix(left, fn, right) =>
// FIXME [AA] We should accept all ops when translating to core
// TODO [AA] We should accept all ops when translating to core
val validInfixOps = List("+", "/", "-", "*", "%")
if (validInfixOps.contains(fn.name)) {
AstArithOp(
application.location,
callable.location,
fn.name,
translateExpression(left),
translateExpression(right)
@ -281,26 +362,68 @@ 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
case _ => throw new UnhandledEntity(application, "translateCallable")
case AST.App.Prefix(_, _) =>
throw new RuntimeException(
"Enso does not support arbitrary prefix expressions"
)
case AST.App.Section.any(_) =>
throw new RuntimeException(
"Enso does not yet support operator sections"
)
case AST.Mixfix(nameSegments, args) =>
val realNameSegments = nameSegments.collect {
case AST.Ident.Var.any(v) => v
}
if (realNameSegments.length != nameSegments.length) {
throw new RuntimeException("Badly named mixfix function.")
}
val functionName =
AST.Ident.Var(realNameSegments.map(_.name).mkString("_"))
AstApply(
callable.location,
translateExpression(functionName),
args.map(translateCallArgument).toList,
false
)
case _ => throw new UnhandledEntity(callable, "translateCallable")
}
}
/** Translates an arbitrary program identifier from its [[AST]] representation
* into [[Core]].
*
* @param identifier the identifier to translate
* @return the [[Core]] representation of `identifier`
*/
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(identifier.location, name)
case AST.Ident.Cons(name) => AstVariable(identifier.location, name)
// case AST.Ident.Opr.any(identifier) => processIdentOperator(identifier)
// case AST.Ident.Mod(name) => IR.Identifier.Module(name)
case AST.Ident.Blank(_) =>
throw new RuntimeException("Blanks not yet properly supported")
case AST.Ident.Opr.any(_) =>
throw new RuntimeException("Operators not generically supported yet")
case AST.Ident.Mod(_) =>
throw new RuntimeException(
"Enso does not support arbitrary module identifiers yet"
)
case _ =>
throw new UnhandledEntity(identifier, "translateIdent")
}
}
def translateAssignment(
/** Translates an arbitrary binding operation from its [[AST]] representation
* into [[Core]].
*
* @param location the source location of the binding
* @param name the name of the binding being assigned to
* @param expr the expression being assigned to `name`
* @return the [[Core]] representation of `expr` being bound to `name`
*/
def translateBinding(
location: Option[Location],
name: AST,
expr: AST
@ -313,6 +436,12 @@ object AstToAstExpression {
}
}
/** Translates the branch of a case expression from its [[AST]] representation
* into [[Core]].
*
* @param branch the case branch to translate
* @return the [[Core]] representation of `branch`
*/
def translateCaseBranch(branch: AST): AstCase = {
branch match {
case AstView.ConsCaseBranch(cons, args, body) =>
@ -330,6 +459,12 @@ object AstToAstExpression {
}
}
/** Translates the fallback branch of a case expression from its [[AST]]
* representation into [[Core]].
*
* @param branch the fallback branch to translate
* @return the [[Core]] representation of `branch`
*/
def translateFallbackBranch(branch: AST): AstCaseFunction = {
branch match {
case AstView.FallbackCaseBranch(body) =>
@ -338,296 +473,85 @@ object AstToAstExpression {
}
}
def translateExpression(inputAST: AST): AstExpression = {
inputAST match {
case AstView
.SuspendedBlock(name, block @ AstView.Block(lines, lastLine)) =>
AstAssignment(
inputAST.location,
name.name,
AstBlock(
block.location,
lines.map(translateExpression),
translateExpression(lastLine),
suspended = true
)
)
case AstView.Assignment(name, expr) =>
translateAssignment(inputAST.location, name, expr)
case AstView.MethodCall(target, name, args) =>
translateMethodCall(inputAST.location, target, name, args)
case AstView.CaseExpression(scrutinee, branches) =>
val actualScrutinee = translateExpression(scrutinee)
val nonFallbackBranches =
branches
.takeWhile(AstView.FallbackCaseBranch.unapply(_).isEmpty)
.map(translateCaseBranch)
val potentialFallback =
branches
.drop(nonFallbackBranches.length)
.headOption
.map(translateFallbackBranch)
AstMatch(
inputAST.location,
actualScrutinee,
nonFallbackBranches,
potentialFallback
)
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(
inputAST.location,
lines.map(translateExpression),
translateExpression(retLine)
)
case _ =>
throw new UnhandledEntity(inputAST, "translateExpression")
}
// inputAST match {
// case AST.Comment.any(inputAST) => processComment(inputAST)
// case AST.Ident.any(inputAST) => processIdent(inputAST)
// case AST.Import.any(inputAST) => processBinding(inputAST)
// case AST.Invalid.any(inputAST) => processInvalid(inputAST)
// case AST.Mixfix.any(inputAST) => processApplication(inputAST)
// case AST.Def.any(inputAST) => processBinding(inputAST)
// case AST.Foreign.any(inputAST) => processBlock(inputAST)
// case _ =>
// IR.Error.UnhandledAST(inputAST)
// }
}
def translateGroup(group: AST.Group): AstExpression = {
/** Translates an arbitrary grouped piece of syntax from its [[AST]]
* representation into [[Core]].
*
* It is currently an error to have an empty group.
*
* @param group the group to translate
* @param translator the function to apply to the group's contents
* @tparam T the result type of translating the expression contained in
* `group`
* @return the [[Core]] representation of the contents of `group`
*/
def translateGroup[T](group: AST.Group, translator: AST => T): T = {
group.body match {
case Some(ast) => translateExpression(ast)
case Some(ast) => translator(ast)
case None => {
// FIXME [AA] This should generate an error node in core
throw new RuntimeException("Empty group")
}
}
}
/** Translates an import statement from its [[AST]] representation into
* [[Core]].
*
* @param imp the import to translate
* @return the [[Core]] representation of `imp`
*/
def translateImport(imp: AST.Import): AstImport = {
AstImport(imp.path.map(t => t.name).reduceLeft((l, r) => l + "." + r))
}
/**
* Transforms invalid entities from the parser AST.
/** Translates an arbitrary invalid expression from the [[AST]] representation
* of the program into its [[Core]] representation.
*
* @param invalid the invalid entity
* @return a representation of `invalid` in the compiler's [[IR IR]]
* @param invalid the invalid entity to translate
* @return the [[Core]] representation of `invalid`
*/
def processInvalid(invalid: AST.Invalid): IR.Error = {
???
// invalid match {
// case AST.Invalid.Unexpected(str, unexpectedTokens) =>
// IR.Error.UnexpectedToken(str, unexpectedTokens.map(t => process(t.el)))
// case AST.Invalid.Unrecognized(str) => IR.Error.UnrecognisedSymbol(str)
// case AST.Ident.InvalidSuffix(identifier, suffix) =>
// IR.Error.InvalidSuffix(processIdent(identifier), suffix)
// case AST.Literal.Text.Unclosed(AST.Literal.Text.Line.Raw(text)) =>
// IR.Error.UnclosedText(List(processLine(text)))
// case AST.Literal.Text.Unclosed(AST.Literal.Text.Line.Fmt(text)) =>
// IR.Error.UnclosedText(List(processLine(text)))
// case _ =>
// throw new RuntimeException(
// "Fatal: Unhandled entity in processInvalid = " + invalid
// )
// }
def translateInvalid(invalid: AST.Invalid): AstExpression = {
invalid match {
case AST.Invalid.Unexpected(_, _) =>
throw new RuntimeException(
"Enso does not yet support unexpected blocks properly"
)
case AST.Invalid.Unrecognized(_) =>
throw new RuntimeException(
"Enso does not yet support unrecognised tokens properly"
)
case AST.Ident.InvalidSuffix(_, _) =>
throw new RuntimeException(
"Enso does not yet support invalid suffixes properly"
)
case AST.Literal.Text.Unclosed(_) =>
throw new RuntimeException(
"Enso does not yet support unclosed text literals properly"
)
case _ =>
throw new RuntimeException(
"Fatal: Unhandled entity in processInvalid = " + invalid
)
}
}
/**
* Transforms identifiers from the parser AST.
/** Translates a comment from its [[AST]] representation into its [[Core]]
* representation.
*
* @param identifier the identifier
* @return a representation of `identifier` in the compiler's [[IR IR]]
*/
def processIdent(identifier: AST.Ident): IR.Identifier = {
???
// identifier match {
// case AST.Ident.Blank(_) => IR.Identifier.Blank()
// case AST.Ident.Var(name) => IR.Identifier.Variable(name)
// case AST.Ident.Cons.any(identifier) => processIdentConstructor(identifier)
// case AST.Ident.Opr.any(identifier) => processIdentOperator(identifier)
// case AST.Ident.Mod(name) => IR.Identifier.Module(name)
// case _ =>
// throw new RuntimeException(
// "Fatal: Unhandled entity in processIdent = " + identifier
// )
// }
}
/**
* Transforms an operator identifier from the parser AST.
*
* @param operator the operator to transform
* @return a representation of `operator` in the compiler's [[IR IR]]
*/
def processIdentOperator(
operator: AST.Ident.Opr
): IR.Identifier.Operator = {
???
// IR.Identifier.Operator(operator.name)
}
/**
* Transforms a constructor identifier from the parser AST.
*
* @param constructor the constructor name to transform
* @return a representation of `constructor` in the compiler's [[IR IR]]
*/
def processIdentConstructor(
constructor: AST.Ident.Cons
): IR.Identifier.Constructor = {
???
// IR.Identifier.Constructor(constructor.name)
}
/**
* Transforms a line of a text literal from the parser AST.
*
* @param line the literal line to transform
* @return a representation of `line` in the compiler's [[IR IR]]
*/
def processLine(
line: List[AST.Literal.Text.Segment[AST]]
): IR.Literal.Text.Line = {
???
// IR.Literal.Text.Line(line.map(processTextSegment))
}
/**
* Transforms a segment of text from the parser AST.
*
* @param segment the text segment to transform
* @return a representation of `segment` in the compiler's [[IR IR]]
*/
def processTextSegment(
segment: AST.Literal.Text.Segment[AST]
): IR.Literal.Text.Segment = {
???
// segment match {
// case AST.Literal.Text.Segment._Plain(str) =>
// IR.Literal.Text.Segment.Plain(str)
// case AST.Literal.Text.Segment._Expr(expr) =>
// IR.Literal.Text.Segment.Expression(expr.map(process))
// case AST.Literal.Text.Segment._Escape(code) =>
// IR.Literal.Text.Segment.EscapeCode(code)
// case _ => throw new UnhandledEntity(segment, "processTextSegment")
// }
}
/**
* Transforms a function application from the parser AST.
*
* @param application the function application to transform
* @return a representation of `application` in the compiler's [[IR IR]]
*/
def processApplication(application: AST): IR.Application = {
???
// application match {
// case AST.App.Prefix(fn, arg) =>
// IR.Application.Prefix(process(fn), process(arg))
// case AST.App.Infix(leftArg, fn, rightArg) =>
// IR.Application.Infix(
// process(leftArg),
// processIdentOperator(fn),
// process(rightArg)
// )
// case AST.App.Section.Left(arg, fn) =>
// IR.Application.Section.Left(process(arg), processIdentOperator(fn))
// case AST.App.Section.Right(fn, arg) =>
// IR.Application.Section.Right(processIdentOperator(fn), process(arg))
// case AST.App.Section.Sides(fn) =>
// IR.Application.Section.Sides(processIdentOperator(fn))
// case AST.Mixfix(fnSegments, args) =>
// IR.Application
// .Mixfix(fnSegments.toList.map(processIdent), args.toList.map(process))
// case _ =>
// throw new UnhandledEntity(application, "processApplication")
// }
}
/**
* Transforms a source code block from the parser AST.
*
* This handles both blocks of Enso-native code, and blocks of foreign
* language code.
*
* @param block the block to transform
* @return a representation of `block` in the compiler's [[IR IR]]
*/
def processBlock(block: AST): IR.Block = {
???
// block match {
// case AST.Block(_, _, firstLine, lines) =>
// IR.Block
// .Enso(
// process(firstLine.elem) ::
// lines.filter(t => t.elem.isDefined).map(t => process(t.elem.get))
// )
// case AST.Foreign(_, language, code) => IR.Block.Foreign(language, code)
// case _ => throw new UnhandledEntity(block, "processBlock")
// }
}
/**
* Transforms a module top-level from the parser AST.
*
* @param module the module to transform
* @return a representation of `module` in the compiler's [[IR IR]]
*/
def processModule(module: AST.Module): IR.Module = {
???
// module match {
// case AST.Module(lines) =>
// IR.Module(
// lines.filter(t => t.elem.isDefined).map(t => process(t.elem.get))
// )
// case _ => throw new UnhandledEntity(module, "processModule")
// }
}
/**
* Transforms a comment from the parser AST.
* Currently this only supports documentation comments, and not standarc
* types of comments as they can't currently be represented.
*
* @param comment the comment to transform
* @return a representation of `comment` in the compiler's [[IR IR]]
* @return the [[Core]] representation of `comment`
*/
def processComment(comment: AST): IR.Comment = {
???
// comment match {
// case AST.Comment(lines) => IR.Comment(lines)
// case _ => throw new UnhandledEntity(comment, "processComment")
// }
}
/**
* Transforms a binding from the parser AST.
*
* Bindings are any constructs that give some Enso language construct a name.
* This includes type definitions, imports, assignments, and so on.
*
* @param binding the binding to transform
* @return a representation of `binding` in the compiler's [[IR IR]]
*/
def processBinding(binding: AST): IR.Binding = {
???
// binding match {
// case AST.Def(constructor, arguments, optBody) =>
// IR.Binding.RawType(
// processIdentConstructor(constructor),
// arguments.map(process),
// optBody.map(process)
// )
// case AST.Import(components) => {
// IR.Binding.Import(
// components.toList.map(t => processIdentConstructor(t))
// )
// }
// case _ => throw new UnhandledEntity(binding, "processBinding")
// }
def translateComment(comment: AST): AstExpression = {
comment match {
case AST.Comment(_) =>
throw new RuntimeException(
"Enso does not yet support comments properly"
)
case AST.Documented(_, _, ast) => translateExpression(ast)
case _ =>
throw new UnhandledEntity(comment, "processComment")
}
}
}

View File

@ -1,29 +1,27 @@
package org.enso.compiler.generate
import org.enso.data
import org.enso.syntax.text.{AST, Debug}
import org.enso.syntax.text.AST
// TODO [AA] Handle arbitrary parens
/** This object contains view patterns that allow matching on the parser AST for
* more sophisticated constructs.
/** 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.
* return [[Some]] when more complex conditions are met. These view patterns
* return the [[AST]] representations of the relevant segments in order to
* allow location information to easily be provided to the translation
* mechanism.
*/
object AstView {
object SuspendedBlock {
def unapply(ast: AST): Option[(AST.Ident.Var, AST.Block)] = {
ast match {
case Assignment(name, AST.Block.any(block)) =>
Some((name, block))
case _ => None
}
}
}
object Block {
/** Matches an arbitrary block in the program source.
*
* @param ast the structure to try and match on
* @return a list of expressions in the block, and the final expression
* separately
*/
def unapply(ast: AST): Option[(List[AST], AST)] = ast match {
case AST.Block(_, _, firstLine, lines) =>
val actualLines = firstLine.elem :: lines.flatMap(_.elem)
@ -36,9 +34,36 @@ object AstView {
}
}
object SuspendedBlock {
/** Matches an arbitrary suspended block in the program source.
*
* A suspended block is one that is bound to a name but takes no arguments.
*
* @param ast the structure to try and match on
* @return the name to which the block is assigned, and the block itself
*/
def unapply(ast: AST): Option[(AST.Ident.Var, AST.Block)] = {
ast match {
case Assignment(name, AST.Block.any(block)) =>
Some((name, block))
case _ => None
}
}
}
object Binding {
val bindingOpSym = AST.Ident.Opr("=")
/** Matches an arbitrary binding in the program source.
*
* A binding is any expression of the form `<expr> = <expr>`, and this
* matcher asserts no additional properties on the structure it matches.
*
* @param ast the structure to try and match on
* @return the expression on the left of the binding operator, and the
* expression on the right side of the binding operator
*/
def unapply(ast: AST): Option[(AST, AST)] = {
ast match {
case AST.App.Infix.any(ast) =>
@ -59,6 +84,14 @@ object AstView {
object Assignment {
val assignmentOpSym = AST.Ident.Opr("=")
/** Matches an assignment.
*
* An assignment is a [[Binding]] where the left-hand side is a variable
* name.
*
* @param ast the structure to try and match on
* @return the variable name assigned to, and the expression being assigned
*/
def unapply(ast: AST): Option[(AST.Ident.Var, AST)] = {
ast match {
case Binding(AST.Ident.Var.any(left), right) => Some((left, right))
@ -70,6 +103,16 @@ object AstView {
object Lambda {
val lambdaOpSym = AST.Ident.Opr("->")
/** Matches a lambda expression in the program source.
*
* A lambda expression is of the form `<args> -> <expression>` where
* `<args>` is a space-separated list of valid argument definitions, and
* `<expression>` is an arbitrary program expression.
*
* @param ast the structure to try and match on
* @return a list of the arguments defined for the lambda, and the body of
* the lambda
*/
def unapply(ast: AST): Option[(List[AST], AST)] = {
ast match {
case AST.App.Infix.any(ast) =>
@ -91,6 +134,16 @@ object AstView {
}
object ForcedTerm {
/** Matches a forced term.
*
* A forced term is one of the form `~t`, where `t` is an arbitrary program
* expression. This is temporary syntax and will be removed once we have
* the ability to insert these analytically.
*
* @param ast the structure to try and match on
* @return the term being forced
*/
def unapply(ast: AST): Option[AST] = {
ast match {
case MaybeParensed(
@ -103,6 +156,16 @@ object AstView {
}
object LazyArgument {
/** Matches on a lazy argument definition or usage.
*
* A lazy argument is one of the form `~t` where `t` is a valid parameter
* name. This is temporary syntax and will be removed once we have the
* ability to insert these analyticallyl
*
* @param ast the structure to try and match on
* @return the term being forced
*/
def unapply(ast: AST): Option[AST] = ast match {
case MaybeParensed(
AST.App.Section.Right(AST.Ident.Opr("~"), FunctionParam(arg))
@ -113,17 +176,29 @@ object AstView {
}
object FunctionParam {
/** Matches a definition-site function parameter.
*
* @param ast the structure to try and match on
* @return the parameter definition
*/
def unapply(ast: AST): Option[AST] = ast match {
case LazyAssignedArgument(_, _) => Some(ast)
case AssignedArgument(_, _) => Some(ast)
case DefinitionArgument(_) => Some(ast)
case PatternMatch(_, _) => Some(ast)
case LazyArgument(_) => Some(ast)
case _ => None
case LazyAssignedArgumentDefinition(_, _) => Some(ast)
case AssignedArgument(_, _) => Some(ast)
case DefinitionArgument(_) => Some(ast)
case PatternMatch(_, _) => Some(ast)
case LazyArgument(_) => Some(ast)
case _ => None
}
}
object LambdaParamList {
/** Matches on the parameter list of a lambda.
*
* @param ast the structure to try and match on
* @return a list of the arguments for which the lambda is defined
*/
def unapply(ast: AST): Option[List[AST]] = {
ast match {
case SpacedList(args) =>
@ -140,14 +215,30 @@ object AstView {
}
}
// TODO [AA] a matcher for type signatured definitions
object MaybeTyped {
def unapply(ast: AST): Option[(AST, AST)] = {
None
/** Matches on terms that _may_ have a type signature.
*
* Such terms take the form of `<term> : <type>`, where both `<term>` and
* `<type>` can be arbitrary program expressions.
*
* @param ast the structure to try and match on
* @return the term and the type ascribed to it
*/
def unapply(ast: AST): Option[(AST, AST)] = ast match {
case AST.App.Infix(entity, AST.Ident.Opr(":"), signature) =>
Some((entity, signature))
case _ => None
}
}
object MaybeParensed {
/** Matches on terms that _may_ be surrounded by parentheses.
*
* @param ast the structure to try and match on
* @return the term contained in the parentheses
*/
def unapply(ast: AST): Option[AST] = {
ast match {
case AST.Group(mExpr) => mExpr.flatMap(unapply)
@ -156,13 +247,30 @@ object AstView {
}
}
/** Used for named and defaulted argument syntactic forms. */
object AssignedArgument {
/** Matches on the structure of an 'assigned argument'.
*
* Such an argument has the structure `<var> = <expression>` where `<var>`
* must be a valid variable name, and `<expression>` is an arbitrary Enso
* expression.
*
* @param ast the structure to try and match on
* @return the variable name and the expression being bound to it
*/
def unapply(ast: AST): Option[(AST.Ident.Var, AST)] =
MaybeParensed.unapply(ast).flatMap(Assignment.unapply)
}
object LazyAssignedArgument {
object LazyAssignedArgumentDefinition {
/** Matches on the definition of a lazy argument for a function that also
* has a default value.
*
* @param ast the structure to try and match on
* @return the name of the argument being declared and the expression of
* the default value being bound to it
*/
def unapply(ast: AST): Option[(AST.Ident.Var, AST)] = {
ast match {
case MaybeParensed(
@ -178,16 +286,29 @@ object AstView {
}
object DefinitionArgument {
/** Matches on a definition argument, which is a standard variable
* identifier.
*
* @param ast the structure to try and match on
* @return the name of the argument
*/
def unapply(ast: AST): Option[AST.Ident.Var] = ast match {
case AST.Ident.Var.any(ast) => Some(ast)
case _ => None
case MaybeParensed(AST.Ident.Var.any(ast)) => Some(ast)
case _ => None
}
}
/** Used for arguments declared as lazy. */
object SuspendedArgument {}
object Application {
/** Matches an arbitrary function application. This includes both method
* calls and standard function applications as they are syntactically
* unified.
*
* @param ast the structure to try and match on
* @return the name of the function, and a list of its arguments (including
* the `self` argument if using method-call syntax)
*/
def unapply(ast: AST): Option[(AST, List[AST])] =
SpacedList.unapply(ast).flatMap {
case fun :: args =>
@ -201,6 +322,17 @@ object AstView {
}
object MethodCall {
/** Matches on a method call.
*
* A method call has the form `<obj>.<fn-name> <args...>` where `<obj>` is
* an arbitrary expression, `<fn-name>` is the name of the function being
* called, and `<args>` are the arguments to the call.
*
* @param ast the structure to try and match on
* @return the `self` expression, the function name, and the arguments to
* the function
*/
def unapply(ast: AST): Option[(AST, AST.Ident, List[AST])] = ast match {
case OperatorDot(target, Application(ConsOrVar(ident), args)) =>
Some((target, ident, args))
@ -211,6 +343,12 @@ object AstView {
}
object SuspendDefaultsOperator {
/** Matches on a usage of the `...` 'suspend defaults' operator.
*
* @param ast the structure to try and match on
* @return the 'suspend defaults' operator
*/
def unapply(ast: AST): Option[AST] = {
ast match {
case AST.Ident.Opr("...") => Some(ast)
@ -221,16 +359,17 @@ object AstView {
object SpacedList {
/** Also matches lists with a ... left section
/** Matches an arbitrary space-separated list in the AST, possibly including
* a usage of the `...` operator.
*
* @param ast
* @return the constructor, and a list of its arguments
* @param ast the structure to try and match on
* @return the elements of the list
*/
def unapply(ast: AST): Option[List[AST]] = {
matchSpacedList(ast)
}
def matchSpacedList(ast: AST): Option[List[AST]] = {
private[this] def matchSpacedList(ast: AST): Option[List[AST]] = {
ast match {
case MaybeParensed(AST.App.Prefix(fn, arg)) =>
val fnRecurse = matchSpacedList(fn)
@ -258,6 +397,17 @@ object AstView {
}
object MethodDefinition {
/** Matches on the definition of a method.
*
* These take the form of `<type>.<fn-name> = <expression>` where `<type>`
* is the name of a type, `<fn-name>` is the name of a function, and
* `<expression>` is an arbitrary program expression.
*
* @param ast the structure to try and match on
* @return the path segments of the type reference, the function name, and
* the bound expression
*/
def unapply(ast: AST): Option[(List[AST], AST, AST)] = {
ast match {
case Binding(lhs, rhs) =>
@ -274,6 +424,13 @@ object AstView {
}
object ConsOrVar {
/** Matches any expression that is either the name of a constructor or a
* variable.
*
* @param arg the structure to try and match on
* @return the identifier matched on
*/
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)
@ -282,6 +439,13 @@ object AstView {
}
object OperatorDot {
/** Matches on an arbitrary usage of operator `.` with no restrictions on
* the operands.
*
* @param ast the structure to try and match on
* @return the left- and right-hand sides of the operator
*/
def unapply(ast: AST): Option[(AST, AST)] = ast match {
case AST.App.Infix(left, AST.Ident.Opr("."), right) => Some((left, right))
case _ =>
@ -289,10 +453,15 @@ object AstView {
}
}
object Path {
object DotChain {
/** Matches an arbitrary chain of [[OperatorDot]] expressions.
*
* @param ast the structure to try and match on
* @return the segments making up the chain
*/
def unapply(ast: AST): Option[List[AST]] = {
val path = matchPath(ast)
val path = matchDotChain(ast)
if (path.isEmpty) {
None
@ -301,23 +470,28 @@ object AstView {
}
}
def matchPath(ast: AST): List[AST] = {
private[this] def matchDotChain(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()
case OperatorDot(left, right) => matchDotChain(left) :+ right
case AST.Ident.any(ast) => List(ast)
case _ => List()
}
}
}
object MethodReference {
/** Matches on a method reference.
*
* A method reference is a [[DotChain]] where all but the last element are
* the names of constructors.
*
* @param ast the structure to try and match on
* @return the constructor segments and the final segment
*/
def unapply(ast: AST): Option[(List[AST], AST)] = {
ast match {
case Path(segments) =>
case DotChain(segments) =>
if (segments.length >= 2) {
val consPath = segments.dropRight(1)
val maybeVar = segments.last
@ -345,7 +519,24 @@ object AstView {
object CaseExpression {
val caseName = data.List1(AST.Ident.Var("case"), AST.Ident.Var("of"))
// scrutinee and branches
/** Matches on a case expression.
*
* A case expression is of the following form:
*
* {{{
* case <scrutinee> of
* <matcher> -> <expression>
* <...>
* }}}
*
* where:
* - `<scrutinee>` is an arbitrary non-block program expression
* - `<matcher>` is a [[PatternMatch]]
* - `<expression>` is an arbirary program expression
*
* @param ast the structure to try and match on
* @return the scrutinee and a list of the case branches
*/
def unapply(ast: AST): Option[(AST, List[AST])] = {
ast match {
case AST.Mixfix(identSegments, argSegments) =>
@ -381,6 +572,18 @@ object AstView {
}
object ConsCaseBranch {
/** Matches a case branch that performas a pattern match on a consctructor.
*
* A constructor case branch is of the form `<cons> <args..> -> <expr>`
* where `<cons>` is the name of a constructor, `<args..>` is the list of
* arguments to that constructor, and `<expr>` is the expression to execute
* on a successful match.
*
* @param ast the structure to try and match on
* @return the constructor name, the constructor arguments, and the
* expression to be executed
*/
def unapply(ast: AST): Option[(AST, List[AST], AST)] = {
CaseBranch.unapply(ast).flatMap {
case (cons, args, ast) => cons.map((_, args, ast))
@ -389,6 +592,15 @@ object AstView {
}
object FallbackCaseBranch {
/** Matches on a fallback case branch.
*
* A fallback case branch is of the form `_ -> <expression>`, where
* `<expression>` is an arbitrary Enso expression.
*
* @param ast the structure to try and match on
* @return the expression of the fallback branch
*/
def unapply(ast: AST): Option[AST] = {
CaseBranch.unapply(ast).flatMap {
case (cons, args, ast) =>
@ -398,15 +610,27 @@ object AstView {
}
object CaseBranch {
// matcher, arguments, body
/** Matches on an arbitrary pattern match case branch.
*
* A case branch has the form `<matcher> -> <expression>`, where
* `<matcher>` is an expression that can match on the scrutinee, and
* `<expression>` is an arbitrary expression to execute on a successful
* match.
*
* @param ast the structure to try and match on
* @return the matcher expression, its arguments (if they exist), and the
* body of the case branch
*/
def unapply(ast: AST): Option[(Option[AST], List[AST], AST)] = {
ast match {
case AST.App.Infix(left, AST.Ident.Opr("->"), right) =>
left match {
case PatternMatch(cons, args) => Some((Some(cons), args, right))
case AST.Ident.Blank.any(_) => Some((None, List(), right))
case DefinitionArgument(v) => Some((None, List(v), right))
case _ => None
case MaybeParensed(AST.Ident.Blank.any(_)) =>
Some((None, List(), right))
case DefinitionArgument(v) => Some((None, List(v), right))
case _ => None
}
case _ => None
}
@ -415,9 +639,17 @@ object AstView {
object PatternMatch {
// Cons, args
/** Matches an arbitrary pattern match on a constructor.
*
* A pattern match is of the form `<cons> <args..>` where `<cons>` is the
* name of a constructor, and `<args>` are pattern match parameters.
*
* @param ast the structure to try and match on
* @return the name of the constructor, and a list containing its arguments
*/
def unapply(ast: AST): Option[(AST.Ident.Cons, List[AST])] = {
ast match {
case SpacedList(AST.Ident.Cons.any(cons) :: xs) =>
case MaybeParensed(SpacedList(AST.Ident.Cons.any(cons) :: xs)) =>
val realArgs: List[AST] = xs.collect { case a @ MatchParam(_) => a }
if (realArgs.length == xs.length) {
@ -432,6 +664,12 @@ object AstView {
}
object MatchParam {
/** Matches a valid parameter to a pattern match.
*
* @param ast the structure to try and match on
* @return the argument
*/
def unapply(ast: AST): Option[AST] = ast match {
case DefinitionArgument(_) => Some(ast)
case PatternMatch(_, _) => Some(ast)
@ -439,5 +677,4 @@ object AstView {
case _ => None
}
}
}

View File

@ -12,7 +12,7 @@ class CodeLocationsTest extends InterpreterTest {
def debugPrintCodeLocations(code: String): Unit = {
var off = 0
code.lines.toList.foreach { line =>
val chars = line.toList.map { c =>
val chars: List[Any] = line.toList.map { c =>
s" ${if (c == ' ') '_' else c} "
} :+ ''
val ixes = off.until(off + chars.length).map { i =>
@ -32,6 +32,7 @@ class CodeLocationsTest extends InterpreterTest {
instrumenter.assertNodeExists(4, 7, classOf[MultiplyOperatorNode])
instrumenter.assertNodeExists(4, 2, classOf[IntegerLiteralNode])
eval(code)
()
}
"Code locations" should "be correct with parenthesized expressions" in
@ -40,6 +41,7 @@ class CodeLocationsTest extends InterpreterTest {
instrumenter.assertNodeExists(0, 13, classOf[MultiplyOperatorNode])
instrumenter.assertNodeExists(1, 6, classOf[AddOperatorNode])
eval(code)
()
}
"Code Locations" should "be correct in applications and method calls" in
@ -48,6 +50,7 @@ class CodeLocationsTest extends InterpreterTest {
instrumenter.assertNodeExists(0, 27, classOf[ApplicationNode])
instrumenter.assertNodeExists(16, 8, classOf[ApplicationNode])
eval(code)
()
}
"Code Locations" should "be correct in assignments and variable reads" in
@ -64,6 +67,7 @@ class CodeLocationsTest extends InterpreterTest {
instrumenter.assertNodeExists(23, 1, classOf[ReadLocalTargetNode])
instrumenter.assertNodeExists(36, 1, classOf[ReadLocalTargetNode])
eval(code)
()
}
"Code Locations" should "be correct for deeply nested functions" in
@ -84,6 +88,7 @@ class CodeLocationsTest extends InterpreterTest {
instrumenter.assertNodeExists(77, 7, classOf[ApplicationNode])
instrumenter.assertNodeExists(87, 9, classOf[ApplicationNode])
eval(code)
()
}
"Code Locations" should "be correct inside pattern matches" in
@ -109,6 +114,7 @@ class CodeLocationsTest extends InterpreterTest {
instrumenter.assertNodeExists(98, 9, classOf[AssignmentNode])
instrumenter.assertNodeExists(121, 5, classOf[MultiplyOperatorNode])
eval(code)
()
}
"Code locations" should "be correct for lambdas" in
@ -125,6 +131,7 @@ class CodeLocationsTest extends InterpreterTest {
instrumenter.assertNodeExists(5, 12, classOf[CreateFunctionNode])
instrumenter.assertNodeExists(22, 27, classOf[CreateFunctionNode])
eval(code)
()
}
"Code locations" should "be correct for defaulted arguments" in
@ -139,6 +146,7 @@ class CodeLocationsTest extends InterpreterTest {
instrumenter.assertNodeExists(35, 3, classOf[ReadLocalTargetNode])
instrumenter.assertNodeExists(39, 1, classOf[ReadLocalTargetNode])
eval(code)
()
}
"Code locations" should "be correct for lazy arguments" in
@ -151,5 +159,6 @@ class CodeLocationsTest extends InterpreterTest {
|""".stripMargin
instrumenter.assertNodeExists(22, 2, classOf[ForceNode])
eval(code)
()
}
}

View File

@ -47,7 +47,6 @@ class ErrorsTest extends InterpreterTest {
|
|IO.println matched
|""".stripMargin
eval(code)
noException shouldBe thrownBy(eval(code))
consumeOut shouldEqual List("Error:MyError")
}

View File

@ -0,0 +1,71 @@
package org.enso.interpreter.test.semantic
import org.enso.interpreter.test.InterpreterTest
class GroupingTest extends InterpreterTest {
val condition = "be able to be grouped"
"Arbitrary lambdas" should condition in {
val code =
"""
|(x -> x)
|""".stripMargin
eval(code).call(5) shouldEqual 5
}
"RHS of an assignment" should condition in {
val code =
"""
|fn = (x -> x)
|
|fn 10
|""".stripMargin
eval(code) shouldEqual 10
}
"Forced terms and lazy arguments" should condition in {
val code =
"""
|ifTest = c (~ifT) ~ifF -> ifZero c ~ifT (~ifF)
|sum = c acc -> ifTest c acc (sum c-1 acc+c)
|sum 10000 0
|""".stripMargin
eval(code) shouldEqual 50005000
}
"Arbitrary arguments" should condition in {
val code =
"""
|(x) -> x
|""".stripMargin
eval(code).call(5) shouldEqual 5
}
"Pattern matches" should condition in {
val code =
"""
|fn = x -> case x of
| (Cons h t) -> h + fn t
| (_) -> 0
|
|fn (Cons 7 Nil)
|""".stripMargin
eval(code) shouldEqual 7
}
"Method calls" should condition in {
val code =
"""
|type Foo
|Foo.bar = number -> number + 1
|(Foo.bar) 10
|""".stripMargin
eval(code) shouldEqual 11
}
}

View File

@ -0,0 +1,35 @@
package org.enso.interpreter.test.semantic
import org.enso.interpreter.test.InterpreterTest
class MixfixFunctionsTest extends InterpreterTest {
val subject = "Mixfix functions"
subject should "be able to be defined as a method" in {
val code =
"""
|type Foo a
|
|Foo.if_then = x -> case this of
| Foo a -> a + x
|
|if Foo 2 then 8
|""".stripMargin
eval(code) shouldEqual 10
}
subject should "easily support multiple arguments" in {
val code =
"""
|type Foo a b
|
|Foo.if_then_else = a b -> case this of
| Foo x y -> x + y + a + b
|
|if (Foo 1 2) then 3 else 4
|""".stripMargin
eval(code) shouldEqual 10
}
}

View File

@ -27,4 +27,14 @@ class TextTest extends InterpreterTest {
eval(code)
consumeOut shouldEqual List("Foo", "Bar", " Baz")
}
"Raw text literals" should "support escape sequences" in {
val code =
"""
|IO.println "\"Grzegorz Brzeczyszczykiewicz\""
|""".stripMargin
eval(code)
consumeOut shouldEqual List("\"Grzegorz Brzeczyszczykiewicz\"")
}
}