mirror of
https://github.com/enso-org/enso.git
synced 2024-11-27 06:32:30 +03:00
Complete AST to AstExpression translation (#374)
This commit is contained in:
parent
2b8af07148
commit
c3acc5c615
@ -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]] =
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
*/
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
||||
/**
|
||||
|
@ -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 {
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
@ -63,4 +63,9 @@ public class RunStateNode extends BuiltinRootNode {
|
||||
new ArgumentDefinition(
|
||||
2, "statefulComputation", ArgumentDefinition.ExecutionMode.PASS_THUNK));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "State.run";
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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. */
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
()
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,6 @@ class ErrorsTest extends InterpreterTest {
|
||||
|
|
||||
|IO.println matched
|
||||
|""".stripMargin
|
||||
eval(code)
|
||||
noException shouldBe thrownBy(eval(code))
|
||||
consumeOut shouldEqual List("Error:MyError")
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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\"")
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user