Require blocks in fewer places
Due to Python's ... interesting ... scoping rules, we want to avoid forcing block scopes in certain places. Instead, we will let arbitrary statements take their place. Of course, this could be a block, but it very well could be a multi-statement (essentially a block that doesn't imply a new lexical scope), or anything, really.
This commit is contained in:
parent
2c6ad1e331
commit
1299e4ade7
10 changed files with 81 additions and 70 deletions
|
@ -177,19 +177,19 @@ type Function interface {
|
|||
Definition
|
||||
GetParameters() *[]*LocalVariable
|
||||
GetReturnType() *TypeToken
|
||||
GetBody() *Block
|
||||
GetBody() Statement
|
||||
}
|
||||
|
||||
type FunctionNode struct {
|
||||
// note that this node intentionally omits any embedded base, to avoid diamond "inheritance".
|
||||
Parameters *[]*LocalVariable `json:"parameters,omitempty"`
|
||||
ReturnType *TypeToken `json:"returnType,omitempty"`
|
||||
Body *Block `json:"body,omitempty"`
|
||||
Body Statement `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
func (node *FunctionNode) GetParameters() *[]*LocalVariable { return node.Parameters }
|
||||
func (node *FunctionNode) GetReturnType() *TypeToken { return node.ReturnType }
|
||||
func (node *FunctionNode) GetBody() *Block { return node.Body }
|
||||
func (node *FunctionNode) GetBody() Statement { return node.Body }
|
||||
|
||||
// ModuleMethod is just a function with an accessibility modifier.
|
||||
type ModuleMethod struct {
|
||||
|
|
|
@ -31,6 +31,7 @@ const ImportKind NodeKind = "Import"
|
|||
|
||||
/* Blocks */
|
||||
|
||||
// Block is a grouping of statements that enjoy their own lexical scope.
|
||||
type Block struct {
|
||||
StatementNode
|
||||
Statements []Statement `json:"statements"`
|
||||
|
@ -57,9 +58,9 @@ const LocalVariableDeclarationKind NodeKind = "LocalVariableDeclaration"
|
|||
|
||||
type TryCatchFinally struct {
|
||||
StatementNode
|
||||
TryBlock *Block `json:"tryBlock"`
|
||||
CatchBlocks *[]*TryCatchBlock `json:"catchBlocks,omitempty"`
|
||||
FinallyBlock *Block `json:"finallyBlock"`
|
||||
TryClause Statement `json:"tryClause"`
|
||||
CatchClauses *[]*TryCatchClause `json:"catchClauses,omitempty"`
|
||||
FinallyClause Statement `json:"finallyClause"`
|
||||
}
|
||||
|
||||
var _ Node = (*TryCatchFinally)(nil)
|
||||
|
@ -67,13 +68,13 @@ var _ Statement = (*TryCatchFinally)(nil)
|
|||
|
||||
const TryCatchFinallyKind NodeKind = "TryCatchFinally"
|
||||
|
||||
type TryCatchBlock struct {
|
||||
type TryCatchClause struct {
|
||||
NodeValue
|
||||
Exception *LocalVariable `json:"exception,omitempty"`
|
||||
Block *Block `json:"block"`
|
||||
Body Statement `json:"body"`
|
||||
}
|
||||
|
||||
var _ Node = (*TryCatchBlock)(nil)
|
||||
var _ Node = (*TryCatchClause)(nil)
|
||||
|
||||
/* Branches */
|
||||
|
||||
|
@ -173,7 +174,7 @@ const ThrowStatementKind NodeKind = "ThrowStatement"
|
|||
type WhileStatement struct {
|
||||
StatementNode
|
||||
Condition *Expression `json:"condition,omitempty"` // a `bool` statement indicating whether to cotinue.
|
||||
Body *Block `json:"body"` // the body to execute provided the condition remains `true`.
|
||||
Body Statement `json:"body"` // the body to execute provided the condition remains `true`.
|
||||
}
|
||||
|
||||
var _ Node = (*WhileStatement)(nil)
|
||||
|
@ -187,7 +188,7 @@ type ForStatement struct {
|
|||
Init *Statement `json:"init,omitempty"` // an initialization statement.
|
||||
Condition *Expression `json:"condition,omitempty"` // a `bool` statement indicating whether to continue.
|
||||
Post *Statement `json:"post,omitempty"` // a statement to run after the body, before the next iteration.
|
||||
Body *Block `json:"body"` // the body to execute provided the condition remains `true`.
|
||||
Body Statement `json:"body"` // the body to execute provided the condition remains `true`.
|
||||
}
|
||||
|
||||
var _ Node = (*ForStatement)(nil)
|
||||
|
|
|
@ -91,18 +91,18 @@ func Walk(v Visitor, node Node) {
|
|||
case *LocalVariableDeclaration:
|
||||
Walk(v, n.Local)
|
||||
case *TryCatchFinally:
|
||||
Walk(v, n.TryBlock)
|
||||
if n.CatchBlocks != nil {
|
||||
for _, catch := range *n.CatchBlocks {
|
||||
Walk(v, n.TryClause)
|
||||
if n.CatchClauses != nil {
|
||||
for _, catch := range *n.CatchClauses {
|
||||
Walk(v, catch)
|
||||
}
|
||||
}
|
||||
if n.FinallyBlock != nil {
|
||||
Walk(v, n.FinallyBlock)
|
||||
if n.FinallyClause != nil {
|
||||
Walk(v, n.FinallyClause)
|
||||
}
|
||||
case *TryCatchBlock:
|
||||
case *TryCatchClause:
|
||||
Walk(v, n.Exception)
|
||||
Walk(v, n.Block)
|
||||
Walk(v, n.Body)
|
||||
case *IfStatement:
|
||||
Walk(v, n.Condition)
|
||||
Walk(v, n.Consequent)
|
||||
|
|
|
@ -659,7 +659,7 @@ func (e *evaluator) evalCall(node diag.Diagable, fnc symbols.Function,
|
|||
case *Intrinsic:
|
||||
uw = f.Invoke(e, this, args)
|
||||
default:
|
||||
uw = e.evalBlock(fnode.GetBody())
|
||||
uw = e.evalStatement(fnode.GetBody())
|
||||
}
|
||||
|
||||
// Check that the unwind is as expected. In particular:
|
||||
|
@ -801,13 +801,13 @@ func (e *evaluator) evalLocalVariableDeclaration(node *ast.LocalVariableDeclarat
|
|||
}
|
||||
|
||||
func (e *evaluator) evalTryCatchFinally(node *ast.TryCatchFinally) *rt.Unwind {
|
||||
// First, execute the TryBlock.
|
||||
uw := e.evalBlock(node.TryBlock)
|
||||
// First, execute the try part.
|
||||
uw := e.evalStatement(node.TryClause)
|
||||
if uw != nil && uw.Throw() {
|
||||
// The try block threw something; see if there is a handler that covers this.
|
||||
thrown := uw.Exception().Thrown
|
||||
if node.CatchBlocks != nil {
|
||||
for _, catch := range *node.CatchBlocks {
|
||||
if node.CatchClauses != nil {
|
||||
for _, catch := range *node.CatchClauses {
|
||||
ex := e.ctx.RequireVariable(catch.Exception).(*symbols.LocalVariable)
|
||||
exty := ex.Type()
|
||||
if types.CanConvert(thrown.Type(), exty) {
|
||||
|
@ -815,7 +815,7 @@ func (e *evaluator) evalTryCatchFinally(node *ast.TryCatchFinally) *rt.Unwind {
|
|||
// evaluate the block, and swap the rt.Unwind information ("handling" the in-flight exception).
|
||||
e.pushScope(nil)
|
||||
e.locals.SetValue(ex, thrown)
|
||||
uw = e.evalBlock(catch.Block)
|
||||
uw = e.evalStatement(catch.Body)
|
||||
e.popScope(false)
|
||||
break
|
||||
}
|
||||
|
@ -823,9 +823,9 @@ func (e *evaluator) evalTryCatchFinally(node *ast.TryCatchFinally) *rt.Unwind {
|
|||
}
|
||||
}
|
||||
|
||||
// No matter the rt.Unwind instructions, be sure to invoke the FinallyBlock.
|
||||
if node.FinallyBlock != nil {
|
||||
uwf := e.evalBlock(node.FinallyBlock)
|
||||
// No matter the rt.Unwind instructions, be sure to invoke the finally part.
|
||||
if node.FinallyClause != nil {
|
||||
uwf := e.evalStatement(node.FinallyClause)
|
||||
|
||||
// Any rt.Unwind information from the finally block overrides the try rt.Unwind that was in flight.
|
||||
if uwf != nil {
|
||||
|
@ -989,10 +989,6 @@ func (e *evaluator) evalWhileStatement(node *ast.WhileStatement) *rt.Unwind {
|
|||
}
|
||||
|
||||
func (e *evaluator) evalForStatement(node *ast.ForStatement) *rt.Unwind {
|
||||
// Enter into a new scope so that any new variables are inside of the for block.
|
||||
e.pushScope(nil)
|
||||
defer e.popScope(false)
|
||||
|
||||
// Now run the initialization code.
|
||||
if node.Init != nil {
|
||||
if uw := e.evalStatement(*node.Init); uw != nil {
|
||||
|
|
|
@ -102,7 +102,7 @@ export type ClassPropertyKind = "ClassProperty";
|
|||
export interface Function extends Definition {
|
||||
parameters?: LocalVariable[];
|
||||
returnType?: TypeToken;
|
||||
body?: statements.Block;
|
||||
body?: statements.Statement;
|
||||
}
|
||||
|
||||
// A module method is just a function defined at the module scope.
|
||||
|
|
|
@ -37,7 +37,7 @@ export type NodeKind =
|
|||
statements.BlockKind |
|
||||
statements.LocalVariableDeclarationKind |
|
||||
statements.TryCatchFinallyKind |
|
||||
statements.TryCatchBlockKind |
|
||||
statements.TryCatchClauseKind |
|
||||
statements.BreakStatementKind |
|
||||
statements.ContinueStatementKind |
|
||||
statements.IfStatementKind |
|
||||
|
|
|
@ -18,6 +18,7 @@ export type ImportKind = "Import";
|
|||
|
||||
/** Blocks **/
|
||||
|
||||
// Block is a grouping of statements that enjoy their own lexical scope.
|
||||
export interface Block extends Statement {
|
||||
kind: BlockKind;
|
||||
statements: Statement[];
|
||||
|
@ -37,21 +38,21 @@ export type LocalVariableDeclarationKind = "LocalVariableDeclaration";
|
|||
/** Try/Catch/Finally **/
|
||||
|
||||
export interface TryCatchFinally extends Statement {
|
||||
kind: TryCatchFinallyKind;
|
||||
tryBlock: Block;
|
||||
catchBlocks?: TryCatchBlock[];
|
||||
finallyBlock?: Block;
|
||||
kind: TryCatchFinallyKind;
|
||||
tryClause: Statement;
|
||||
catchClauses?: TryCatchClause[];
|
||||
finallyClause?: Statement;
|
||||
}
|
||||
export const tryCatchFinallyKind = "TryCatchFinally";
|
||||
export type TryCatchFinallyKind = "TryCatchFinally";
|
||||
|
||||
export interface TryCatchBlock extends Node {
|
||||
kind: TryCatchBlockKind;
|
||||
block: Block;
|
||||
export interface TryCatchClause extends Node {
|
||||
kind: TryCatchClauseKind;
|
||||
body: Statement;
|
||||
exception?: LocalVariable;
|
||||
}
|
||||
export const tryCatchBlockKind = "TryCatchBlock";
|
||||
export type TryCatchBlockKind = "TryCatchBlock";
|
||||
export const tryCatchClauseKind = "TryCatchClause";
|
||||
export type TryCatchClauseKind = "TryCatchClause";
|
||||
|
||||
/** Branches **/
|
||||
|
||||
|
|
|
@ -1089,7 +1089,7 @@ export class Transformer {
|
|||
let initializer: ast.ModuleMethod = {
|
||||
kind: ast.moduleMethodKind,
|
||||
name: ident(tokens.initializerFunction),
|
||||
body: {
|
||||
body: <ast.Block>{
|
||||
kind: ast.blockKind,
|
||||
statements: statements,
|
||||
},
|
||||
|
@ -1657,12 +1657,15 @@ export class Transformer {
|
|||
insertAt = 0; // add the initializers to the empty block.
|
||||
members[tokens.constructorFunction] = ctor;
|
||||
}
|
||||
|
||||
let bodyBlock: ast.Block;
|
||||
if (ctor.body) {
|
||||
bodyBlock = <ast.Block>ctor.body;
|
||||
if (extend) {
|
||||
// If there is a superclass, find the insertion point right *after* the explicit call to
|
||||
// `super()`, to achieve the expected initialization order.
|
||||
for (let i = 0; i < ctor.body.statements.length; i++) {
|
||||
if (this.isSuperCall(ctor.body.statements[i], extend.tok)) {
|
||||
for (let i = 0; i < bodyBlock.statements.length; i++) {
|
||||
if (this.isSuperCall(bodyBlock.statements[i], extend.tok)) {
|
||||
insertAt = i+1; // place the initializers right after this call.
|
||||
break;
|
||||
}
|
||||
|
@ -1674,14 +1677,15 @@ export class Transformer {
|
|||
}
|
||||
}
|
||||
else {
|
||||
ctor.body = this.withLocation(node, <ast.Block>{
|
||||
bodyBlock = this.withLocation(node, <ast.Block>{
|
||||
kind: ast.blockKind,
|
||||
statements: [],
|
||||
});
|
||||
ctor.body = bodyBlock;
|
||||
if (extend) {
|
||||
// Generate an automatic call to the base class. Omitting this is only legal if the base class
|
||||
// constructor has zero arguments, so we just generate a simple `super();` call.
|
||||
ctor.body.statements.push(
|
||||
bodyBlock.statements.push(
|
||||
this.copyLocation(ctor.body, this.createEmptySuperCall(extend.tok)));
|
||||
insertAt = 1; // insert the initializers immediately after this call.
|
||||
}
|
||||
|
@ -1690,10 +1694,10 @@ export class Transformer {
|
|||
}
|
||||
}
|
||||
|
||||
ctor.body.statements =
|
||||
ctor.body.statements.slice(0, insertAt).concat(
|
||||
bodyBlock.statements =
|
||||
bodyBlock.statements.slice(0, insertAt).concat(
|
||||
instancePropertyInitializers).concat(
|
||||
ctor.body.statements.slice(insertAt, ctor.body.statements.length));
|
||||
bodyBlock.statements.slice(insertAt, bodyBlock.statements.length));
|
||||
}
|
||||
|
||||
let mods: ts.ModifierFlags = ts.getCombinedModifierFlags(node);
|
||||
|
@ -2254,13 +2258,21 @@ export class Transformer {
|
|||
expression: await this.transformExpression(node.incrementor),
|
||||
});
|
||||
}
|
||||
return this.withLocation(node, <ast.ForStatement>{
|
||||
|
||||
let forStmt: ast.ForStatement = this.withLocation(node, <ast.ForStatement>{
|
||||
kind: ast.forStatementKind,
|
||||
init: init,
|
||||
condition: condition,
|
||||
post: post,
|
||||
body: await this.transformStatement(node.statement),
|
||||
});
|
||||
|
||||
// Place the for statement into a block so that any new variables introduced inside of the init are lexically
|
||||
// scoped to the for loop, rather than outside of it.
|
||||
return <ast.Block>{
|
||||
kind: ast.blockKind,
|
||||
statements: [ forStmt ],
|
||||
};
|
||||
}
|
||||
|
||||
private async transformForInitializer(node: ts.ForInitializer): Promise<ast.Statement> {
|
||||
|
|
|
@ -216,7 +216,7 @@ class Function(Definition):
|
|||
assert (parameters is None or
|
||||
(isinstance(parameters, list) and all(isinstance(node, LocalVariable) for node in parameters)))
|
||||
assert return_type is None or isinstance(return_type, TypeToken)
|
||||
assert body is None or isinstance(body, Block)
|
||||
assert body is None or isinstance(body, Statement)
|
||||
super(Function, self).__init__(kind, name, loc=loc)
|
||||
self.parameters = parameters
|
||||
self.return_type = return_type
|
||||
|
@ -229,7 +229,7 @@ class ModuleMethod(Function, ModuleMember):
|
|||
assert (parameters is None or
|
||||
(isinstance(parameters, list) and all(isinstance(node, LocalVariable) for node in parameters)))
|
||||
assert return_type is None or isinstance(return_type, TypeToken)
|
||||
assert body is None or isinstance(body, Block)
|
||||
assert body is None or isinstance(body, Statement)
|
||||
super(ModuleMethod, self).__init__("ModuleMethod", name, parameters, return_type, body, loc)
|
||||
|
||||
class ClassMethod(Function, ClassMember):
|
||||
|
@ -240,7 +240,7 @@ class ClassMethod(Function, ClassMember):
|
|||
assert (parameters is None or
|
||||
(isinstance(parameters, list) and all(isinstance(node, LocalVariable) for node in parameters)))
|
||||
assert return_type is None or isinstance(return_type, TypeToken)
|
||||
assert body is None or isinstance(body, Block)
|
||||
assert body is None or isinstance(body, Statement)
|
||||
assert access is None or access in tokens.accs
|
||||
assert static is None or isinstance(bool, static)
|
||||
assert sealed is None or isinstance(bool, sealed)
|
||||
|
@ -289,22 +289,22 @@ class LocalVariableDeclaration(Statement):
|
|||
# ...Try/Catch/Finally
|
||||
|
||||
class TryCatchFinally(Statement):
|
||||
def __init__(self, try_block, catch_blocks=None, finally_block=None, loc=None):
|
||||
assert isinstance(try_block, Block)
|
||||
assert (catch_blocks is None or
|
||||
(isinstance(catch_blocks, list) and all(isinstance(node, TryCatchBlock) for node in catch_blocks)))
|
||||
assert finally_block is None or isinstance(finally_block, Block)
|
||||
def __init__(self, try_clause, catch_clauses=None, finally_clause=None, loc=None):
|
||||
assert isinstance(try_clause, Statement)
|
||||
assert (catch_clauses is None or
|
||||
(isinstance(catch_blocks, list) and all(isinstance(node, TryCatchClause) for node in catch_clauses)))
|
||||
assert finally_clause is None or isinstance(finally_clause, Statement)
|
||||
super(TryCatchFinally, self).__init__("TryCatchFinally", loc)
|
||||
self.try_block = try_block
|
||||
self.catch_blocks = catch_blocks
|
||||
self.finally_block = finally_block
|
||||
self.try_clause = try_clause
|
||||
self.catch_clauses = catch_clauses
|
||||
self.finally_clause = finally_clause
|
||||
|
||||
class TryCatchBlock(Node):
|
||||
def __init__(self, block, exception=None, loc=None):
|
||||
assert isinstance(block, Block)
|
||||
class TryCatchClause(Node):
|
||||
def __init__(self, body, exception=None, loc=None):
|
||||
assert isinstance(body, Statement)
|
||||
assert exception is None or isinstance(exception, LocalVariable)
|
||||
super(TryCatchBlock, self).__init__("TryCatchBlock", loc)
|
||||
self.block = block
|
||||
super(TryCatchClause, self).__init__("TryCatchClause", loc)
|
||||
self.body = body
|
||||
self.exception = exception
|
||||
|
||||
# ...Branches
|
||||
|
|
|
@ -144,13 +144,13 @@ class Transformer:
|
|||
|
||||
# If any top-level statements spilled over, add them to the initializer.
|
||||
if len(initstmts) > 0:
|
||||
initbody = ast.Block(initstmts)
|
||||
initbody = ast.MultiStatement(initstmts)
|
||||
members[tokens.func_init] = ast.ModuleMethod(self.ident(tokens.func_init), body=initbody)
|
||||
|
||||
# All Python scripts are executable, so ensure that an entrypoint exists. It consists of an empty block
|
||||
# because it exists solely to trigger the module initializer routine (if it exists).
|
||||
members[tokens.func_entrypoint] = ast.ModuleMethod(
|
||||
self.ident(tokens.func_entrypoint), body=ast.Block(list()))
|
||||
self.ident(tokens.func_entrypoint), body=ast.MultiStatement(list()))
|
||||
|
||||
# For every property "declaration" encountered during the transformation, add a module property.
|
||||
for propname in self.ctx.globals:
|
||||
|
@ -249,7 +249,8 @@ class Transformer:
|
|||
if file and start:
|
||||
loc = ast.Location(file, start, end)
|
||||
|
||||
return ast.Block(stmts, loc)
|
||||
# Note that, to emulate Python's more "dynamic" scoping rules, we do not emit a true block.
|
||||
return ast.MultiStatement(stmts, loc)
|
||||
|
||||
def transform_Assert(self, node):
|
||||
self.not_yet_implemented(node) # test, msg
|
||||
|
|
Loading…
Reference in a new issue