Permit Statements in SequenceExpressions

The previous shape of SequenceExpression only permitted expressions
in the sequence.  This is pretty common in most ILs, however, it usually
leads to complicated manual spilling in the event that a statement is needed.
This is often necessary when, for example, a compiler is deeply nested in some
expression production, and then realizes the code expansion requires a
statement (e.g., maybe a new local variable must be declared, etc).

Instead of requiring complicated code-gen, this change permits SequenceExpression
to contain an arbitrary mixture of expression/statement prelude nodes, terminating
with a single, final Expression which yields the actual expression value.  The
runtime bears the burden of implementing this which, frankly, is pretty trivial.
This commit is contained in:
joeduffy 2017-05-04 10:54:07 -07:00
parent 748432299a
commit fde88b7cf4
7 changed files with 46 additions and 23 deletions

View file

@ -268,10 +268,12 @@ export interface ConditionalExpression extends Expression {
export const conditionalExpressionKind = "ConditionalExpression";
export type ConditionalExpressionKind = "ConditionalExpression";
// A sequence expression allows composition of multiple expressions into one. It evaluates to the last one.
// A sequence expression allows evaluation of multiple "prelude" statements and/or expressions as though they were a
// single expression. The overall sequence evaluates to the final "value" expression.
export interface SequenceExpression extends Expression {
kind: SequenceExpressionKind;
expressions: Expression[];
kind: SequenceExpressionKind;
prelude: (Expression | statements.Statement)[];
value: Expression;
}
export const sequenceExpressionKind = "SequenceExpression";
export type SequenceExpressionKind = "SequenceExpression";

View file

@ -757,9 +757,14 @@ class ConditionalExpression(Expression):
self.alternate = alternate # the expression to evaluate if `false`.
class SequenceExpression(Expression):
"""A expression allows composition of multiple expressions into one. It evaluates to the last one's value."""
def __init__(self, expressions, loc=None):
assert isinstance(expressions, list) and all(isinstance(expr, Expression) for expr in expressions)
"""A sequence expression allows evaluation of multiple prelude statements and/or expressions as though they were
a single expression. The overall sequence evaluates to the final value expression."""
def __init__(self, prelude, value, loc=None):
assert(
isinstance(prelude, list) and
(all(isinstance(expr, Expression) or isinstance(expr, Statement) for expr in expressions)))
assert isinstance(value, Expression)
super(SequenceExpression, self).__init__("SequenceExpression", loc)
self.expressions = expressions
self.prelude = prelude
self.value = value

View file

@ -398,10 +398,12 @@ var _ Expression = (*ConditionalExpression)(nil)
const ConditionalExpressionKind NodeKind = "ConditionalExpression"
// SequenceExpression allows composition of multiple expressions into one. It evaluates to the last one.
// SequenceExpression allows evaluation of multiple "prelude" statements and/or expressions as though they were a
// single expression. The overall sequence evaluates to the final "value" expression.
type SequenceExpression struct {
ExpressionNode
Expressions []Expression `json:"expressions"`
Prelude []Node `json:"prelude"`
Value Expression `json:"value"`
}
var _ Node = (*SequenceExpression)(nil)

View file

@ -224,9 +224,10 @@ func Walk(v Visitor, node Node) {
Walk(v, n.Consequent)
Walk(v, n.Alternate)
case *SequenceExpression:
for _, expr := range n.Expressions {
Walk(v, expr)
for _, node := range n.Prelude {
Walk(v, node)
}
Walk(v, n.Value)
case *NullLiteral, *BoolLiteral, *NumberLiteral, *StringLiteral:
// No children, nothing to do.

View file

@ -711,7 +711,16 @@ func (a *astBinder) checkConditionalExpression(node *ast.ConditionalExpression)
}
func (a *astBinder) checkSequenceExpression(node *ast.SequenceExpression) {
// Ensure that all prelude nodes are statements or expressions.
for _, prelnode := range node.Prelude {
switch prelnode.(type) {
case ast.Expression, ast.Statement:
// good
default:
a.b.Diag().Errorf(errors.ErrorSequencePreludeExprStmt.At(prelnode))
}
}
// The type of a sequence expression is just the type of the last expression in the sequence.
// TODO: check that there's at least one!
a.b.ctx.RegisterType(node, a.b.ctx.RequireType(node.Expressions[len(node.Expressions)-1]))
a.b.ctx.RegisterType(node, a.b.ctx.RequireType(node.Value))
}

View file

@ -41,4 +41,5 @@ var (
ErrorInvalidCast = newError(529, "Illegal cast from '%v' to '%v'; this can never succeed")
ErrorModuleAliasTargetNotFound = newError(530, "Module alias target '%v' was not found (from '%v')")
ErrorDerivedClassHasNoCtor = newError(531, "Class '%v' has no constructor, but its base class '%v' does")
ErrorSequencePreludeExprStmt = newError(532, "Sequence preludes must consist of expressions and/or statements")
)

View file

@ -2057,16 +2057,19 @@ func (e *evaluator) evalConditionalExpression(node *ast.ConditionalExpression) (
}
func (e *evaluator) evalSequenceExpression(node *ast.SequenceExpression) (*rt.Object, *rt.Unwind) {
// Simply walk through the sequence and return the last object.
var obj *rt.Object
contract.Assert(len(node.Expressions) > 0)
for _, expr := range node.Expressions {
var uw *rt.Unwind
if obj, uw = e.evalExpression(expr); uw != nil {
// If the rt.Unwind was non-nil, stop visiting the expressions and propagate it now.
return nil, uw
// Evaluate the sequence's prelude and then, afterwards, evaluate to its value. If an unwind happens anywhere
// during the prelude, we will abruptly terminate the sequence and return it.
for _, prelnode := range node.Prelude {
switch n := prelnode.(type) {
case ast.Expression:
if _, uw := e.evalExpression(n); uw != nil {
return nil, uw
}
case ast.Statement:
if uw := e.evalStatement(n); uw != nil {
return nil, uw
}
}
}
// Return the last expression's object.
return obj, nil
return e.evalExpression(node.Value)
}