Add 1st class switch support

One guiding principle for what makes it into the MuIL AST is that
the gap between source language and AST should not be too great; the
projection of switch statements from MuJS into MuIL clearly violated
that principle, particularly considering that the logic wasn't even
right due to the incorrect emulation of conditional breaks.

Instead of digging deeper into the hole, I've encoded switch logic
in the AST, and implemented support in the evaluator.
This commit is contained in:
joeduffy 2017-02-16 04:58:04 -08:00
parent 6dcdf9e884
commit 563fad29ec
9 changed files with 416 additions and 553 deletions

View file

@ -84,8 +84,7 @@ var _ Statement = (*ContinueStatement)(nil)
const ContinueStatementKind NodeKind = "ContinueStatement"
// IfStatement is the usual C-style `if`. To simplify the MuIL AST, this is the only conditional statement available.
// All higher-level conditional constructs such as `switch`, if`/`else if`/..., etc., must be desugared into it.
// IfStatement is the usual C-style `if`.
type IfStatement struct {
StatementNode
Condition Expression `json:"condition"` // a `bool` conditional expression.
@ -98,6 +97,29 @@ var _ Statement = (*IfStatement)(nil)
const IfStatementKind NodeKind = "IfStatement"
// SwitchStatement is like a typical C-style `switch`.
type SwitchStatement struct {
StatementNode
Expression Expression `json:"expression"` // the value being switched upon.
Cases []*SwitchCase `json:"cases"` // the list of switch cases to be matched, in order.
}
var _ Node = (*SwitchStatement)(nil)
var _ Statement = (*SwitchStatement)(nil)
const SwitchStatementKind NodeKind = "SwitchStatement"
// SwitchCase is a single case of a switch to be matched.
type SwitchCase struct {
NodeValue
Clause *Expression `json:"clause,omitempty"` // the optional switch clause; if nil, default.
Consequent Statement `json:"consequent"` // the statement to execute if there is a match.
}
var _ Node = (*SwitchCase)(nil)
const SwitchCaseKind NodeKind = "SwitchCase"
// LabeledStatement associates an identifier with a statement for purposes of labeled jumps.
type LabeledStatement struct {
StatementNode

View file

@ -104,6 +104,14 @@ func Walk(v Visitor, node Node) {
if n.Alternate != nil {
Walk(v, *n.Alternate)
}
case *SwitchStatement:
Walk(v, n.Expression)
for _, cas := range n.Cases {
if cas.Clause != nil {
Walk(v, *cas.Clause)
}
Walk(v, cas.Consequent)
}
case *LabeledStatement:
Walk(v, n.Statement)
case *ReturnStatement:

View file

@ -37,6 +37,8 @@ func decodeStatement(m mapper.Mapper, tree mapper.Object) (ast.Statement, error)
return decodeContinueStatement(m, tree)
case ast.IfStatementKind:
return decodeIfStatement(m, tree)
case ast.SwitchStatementKind:
return decodeSwitchStatement(m, tree)
case ast.LabeledStatementKind:
return decodeLabeledStatement(m, tree)
case ast.ReturnStatementKind:
@ -105,6 +107,14 @@ func decodeIfStatement(m mapper.Mapper, tree mapper.Object) (*ast.IfStatement, e
return &stmt, nil
}
func decodeSwitchStatement(m mapper.Mapper, tree mapper.Object) (*ast.SwitchStatement, error) {
var stmt ast.SwitchStatement
if err := m.Decode(tree, &stmt); err != nil {
return nil, err
}
return &stmt, nil
}
func decodeLabeledStatement(m mapper.Mapper, tree mapper.Object) (*ast.LabeledStatement, error) {
var stmt ast.LabeledStatement
if err := m.Decode(tree, &stmt); err != nil {

View file

@ -610,6 +610,8 @@ func (e *evaluator) evalStatement(node ast.Statement) *rt.Unwind {
return e.evalContinueStatement(n)
case *ast.IfStatement:
return e.evalIfStatement(n)
case *ast.SwitchStatement:
return e.evalSwitchStatement(n)
case *ast.LabeledStatement:
return e.evalLabeledStatement(n)
case *ast.ReturnStatement:
@ -727,6 +729,52 @@ func (e *evaluator) evalIfStatement(node *ast.IfStatement) *rt.Unwind {
return nil
}
func (e *evaluator) evalSwitchStatement(node *ast.SwitchStatement) *rt.Unwind {
// First evaluate the expression we are switching on.
expr, uw := e.evalExpression(node.Expression)
if uw != nil {
return uw
}
// Next try to find a match; do this by walking all cases, in order, and checking for strict equality.
fallen := false
for _, caseNode := range node.Cases {
match := false
if fallen {
// A fallthrough automatically executes the body without evaluating the clause.
match = true
} else if caseNode.Clause == nil {
// A default style clause always matches.
match = true
} else {
// Otherwise, evaluate the expression, and check for equality.
clause, uw := e.evalExpression(*caseNode.Clause)
if uw != nil {
return uw
}
match = e.evalBinaryOperatorEquals(expr, clause)
}
// If we got a match, execute the clause.
if match {
if uw = e.evalStatement(caseNode.Consequent); uw != nil {
if uw.Break() && uw.Label() == nil {
// A simple break from this case.
break
} else {
// Anything else, get out of dodge.
return uw
}
}
// If we didn't encounter a break, we will fall through to the next case.
fallen = true
}
}
return nil
}
func (e *evaluator) evalLabeledStatement(node *ast.LabeledStatement) *rt.Unwind {
// Evaluate the underlying statement; if it is breaking or continuing to this label, stop the rt.Unwind.
uw := e.evalStatement(node.Statement)
@ -1126,6 +1174,11 @@ func (e *evaluator) evalLoadLocation(node *ast.LoadLocationExpression, lval bool
obj = pv.Obj()
}
if glog.V(9) {
glog.V(9).Infof("Loaded location of type '%v' from symbol '%v': lval=%v current=%v",
ty, sym, lval, pv.Obj())
}
return location{
This: this,
Name: sym.Name(),

View file

@ -40,6 +40,8 @@ export type NodeKind =
statements.BreakStatementKind |
statements.ContinueStatementKind |
statements.IfStatementKind |
statements.SwitchStatementKind |
statements.SwitchCaseKind |
statements.LabeledStatementKind |
statements.ReturnStatementKind |
statements.ThrowStatementKind |

View file

@ -61,8 +61,7 @@ export interface ContinueStatement extends Statement {
export const continueStatementKind = "ContinueStatement";
export type ContinueStatementKind = "ContinueStatement";
// An `if` statement. To simplify the AST, this is the only conditional statement available. All higher-level
// conditional constructs such as `switch`, `if / else if / ...`, etc. must be desugared into it.
// An `if` statement.
export interface IfStatement extends Statement {
kind: IfStatementKind;
condition: Expression; // a `bool` condition expression.
@ -72,6 +71,23 @@ export interface IfStatement extends Statement {
export const ifStatementKind = "IfStatement";
export type IfStatementKind = "IfStatement";
// A `switch` statement.
export interface SwitchStatement extends Statement {
kind: SwitchStatementKind;
expression: Expression; // the value being switched upon.
cases: SwitchCase[]; // the list of switch cases to be matched, in order.
}
export const switchStatementKind = "SwitchStatement";
export type SwitchStatementKind = "SwitchStatement";
// A single case of a `switch` to be matched.
export interface SwitchCase extends Node {
clause?: Expression; // the optional switch clause; if undefined, default.
consequent: Statement; // the statement to execute if there is a match.
}
export const switchCaseKind = "SwitchCase";
export type SwitchCaseKind = "SwitchCase";
// A labeled statement associates an identifier with a statement for purposes of labeled jumps.
export interface LabeledStatement extends Statement {
kind: LabeledStatementKind;

View file

@ -2281,194 +2281,33 @@ export class Transformer {
});
}
// transformSwitchStatement transforms a switch into a series of if/else statements.
private async transformSwitchStatement(node: ts.SwitchStatement): Promise<ast.Statement> {
// A switch expands into a block.
let block = this.withLocation(node, <ast.Block>{
kind: ast.blockKind,
statements: [],
});
// Spill the expression an auto-generated local/ variable so that we don't execute it multiple times.
let exprLocal = this.generateTempLocal("expr");
block.statements.push(<ast.LocalVariableDeclaration>{
kind: ast.localVariableDeclarationKind,
local: <ast.LocalVariable>{
kind: ast.localVariableKind,
name: ident(exprLocal),
type: <ast.TypeToken>{
kind: ast.typeTokenKind,
tok: tokens.objectType, // we don't care about the type.
},
},
});
block.statements.push(<ast.ExpressionStatement>{
kind: ast.expressionStatementKind,
expression: <ast.BinaryOperatorExpression>{
kind: ast.binaryOperatorExpressionKind,
operator: binaryOperators.get(ts.SyntaxKind.EqualsToken),
left: this.createLoadLocal(exprLocal),
right: await this.transformExpression(node.expression),
},
});
// Next, generate a list of clauses, including handling fallthrough and defaults; there are three cases:
//
// 1) A clause that ends with a break; this gets its own "if" statement.
// 2) A clause that does dynamically hit a "break"; this automatically falls through to the next case.
// 3) A default clause; this is executed unconditionally if no prior cases matched.
//
// To illustrate all three cases, consider this example:
//
// switch (<expr>) {¬
// case a:¬
// foo();¬
// break;¬
// case b:¬
// bar();¬
// default:¬
// baz();¬
// break;¬
// }¬
//
// The generated code looks roughly like this:¬
//
// let _tmp_expr = <expr>; // spilled expression.
// let _tmp_match = false; // initialized to false.
// let _tmp_break = true; // initialized to true.
// if (!_tmp_break || (!_tmp_match && _tmp_expr === a)) {¬
// _tmp_match = true;
// foo();¬
// _tmp_break = true;
// }¬
// if (!_tmp_break || (!_tmp_match && _tmp_expr === b)) {¬
// _tmp_match = true;
// bar();¬
// baz();¬
// _tmp_break = false;
// }¬
// if (!_tmp_break || !tmp_match) {¬
// baz();¬
// }¬
//
// Notice how we track two special variables, _tmp_match and _tmp_break, to indicate whether the prior
// clause matched or led to a break, respectively. This informs whether we will dynamically fallthrough.
let matchLocal = this.generateTempLocal("match");
let breakLocal = this.generateTempLocal("break");
// Place all the clauses into a big block and declare and initialize those locals.
block.statements.push(<ast.LocalVariableDeclaration>{
kind: ast.localVariableDeclarationKind,
local: <ast.LocalVariable>{
kind: ast.localVariableKind,
name: ident(matchLocal),
type: <ast.TypeToken>{
kind: ast.typeTokenKind,
tok: tokens.boolType,
},
default: false, // no match by default.
},
});
block.statements.push(<ast.LocalVariableDeclaration>{
kind: ast.localVariableDeclarationKind,
local: <ast.LocalVariable>{
kind: ast.localVariableKind,
name: ident(breakLocal),
type: <ast.TypeToken>{
kind: ast.typeTokenKind,
tok: tokens.boolType,
},
default: true, // break enabled by default (to prevent immediate fallthrough).
},
});
// Now create a proper guard and clause for every switch case.
private async transformSwitchStatement(node: ts.SwitchStatement): Promise<ast.SwitchStatement> {
let expr: ast.Expression = await this.transformExpression(node.expression);
let cases: ast.SwitchCase[] = [];
for (let clause of node.caseBlock.clauses) {
// Produce the case body; this is what will execute if the case is taken.
let body: ast.Block = <ast.Block>{
kind: ast.blockKind,
let caseClause: ast.Expression | undefined;
if (clause.kind === ts.SyntaxKind.CaseClause) {
caseClause = await this.transformExpression((<ts.CaseClause>clause).expression);
}
let caseConsequent: ast.Block = <ast.Block>{
kind: ast.blockKind,
statements: [],
};
// First set _tmp_match to true.
body.statements.push(<ast.ExpressionStatement>{
kind: ast.expressionStatementKind,
expression: <ast.BinaryOperatorExpression>{
kind: ast.binaryOperatorExpressionKind,
operator: binaryOperators.get(ts.SyntaxKind.EqualsToken),
left: this.createLoadLocal(matchLocal),
right: <ast.BoolLiteral>{
kind: ast.boolLiteralKind,
value: true,
},
},
});
// Now just push each of the clause's transformed statements.
for (let stmt of clause.statements) {
let trans: ast.Statement = await this.transformStatement(stmt);
if (trans.kind === ast.breakStatementKind && !(<ast.BreakStatement>trans).label) {
// This is a break; transform it into setting the variable, and skip adding it.
body.statements.push(<ast.ExpressionStatement>{
kind: ast.expressionStatementKind,
expression: <ast.BinaryOperatorExpression>{
kind: ast.binaryOperatorExpressionKind,
operator: binaryOperators.get(ts.SyntaxKind.EqualsToken),
left: this.createLoadLocal(breakLocal),
right: <ast.BoolLiteral>{
kind: ast.boolLiteralKind,
value: true,
},
},
});
}
else {
body.statements.push(trans);
}
caseConsequent.statements.push(await this.transformStatement(stmt));
}
// Next, generate the if guard; this needs to be sensitive to whether this is the default.
// First, the `!_tmp_match` part, which is common to all variants.
let guard: ast.Expression = <ast.UnaryOperatorExpression>{
kind: ast.unaryOperatorExpressionKind,
operator: prefixUnaryOperators.get(ts.SyntaxKind.ExclamationToken),
operand: this.createLoadLocal(matchLocal),
};
// Now, if this isn't default, we need to check === with the desired expression. This turns the above guard
// into `(!_tmp_match && _tmp_expr === <caseExpr>)`.
if (clause.kind === ts.SyntaxKind.CaseClause) {
guard = <ast.BinaryOperatorExpression>{
kind: ast.binaryOperatorExpressionKind,
operator: binaryOperators.get(ts.SyntaxKind.AmpersandAmpersandToken),
left: guard,
right: <ast.BinaryOperatorExpression>{
kind: ast.binaryOperatorExpressionKind,
operator: binaryOperators.get(ts.SyntaxKind.EqualsEqualsEqualsToken),
left: this.createLoadLocal(exprLocal),
right: await this.transformExpression((<ts.CaseClause>clause).expression),
},
};
}
// Finally turn <guard> into `!tmp_break || <guard>`, which is the final form of the if guard.
guard = <ast.BinaryOperatorExpression>{
kind: ast.binaryOperatorExpressionKind,
operator: binaryOperators.get(ts.SyntaxKind.BarBarToken),
left: this.createLoadLocal(breakLocal),
right: guard,
};
// Now push the overall thing as an if statement.
block.statements.push(this.withLocation(clause, <ast.IfStatement>{
kind: ast.ifStatementKind,
condition: guard,
consequent: body,
cases.push(this.withLocation(clause, <ast.SwitchCase>{
kind: ast.switchCaseKind,
clause: caseClause,
consequent: caseConsequent,
}));
}
return block;
return this.withLocation(node, <ast.SwitchStatement>{
kind: ast.switchStatementKind,
expression: expr,
cases: cases,
});
}
private async transformThrowStatement(node: ts.ThrowStatement): Promise<ast.ThrowStatement> {

View file

@ -1,8 +1,5 @@
{
"name": "basic/switch",
"dependencies": {
"mujs": "*"
},
"modules": {
"index": {
"kind": "Module",
@ -195,171 +192,58 @@
}
},
{
"kind": "Block",
"statements": [
{
"kind": "LocalVariableDeclaration",
"local": {
"kind": "LocalVariable",
"name": {
"kind": "Identifier",
"ident": ".temp_expr_0"
"kind": "SwitchStatement",
"expression": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": "v",
"loc": {
"file": "index.ts",
"start": {
"line": 5,
"column": 13
},
"type": {
"kind": "TypeToken",
"tok": "object"
"end": {
"line": 5,
"column": 14
}
}
},
"loc": {
"file": "index.ts",
"start": {
"line": 5,
"column": 13
},
"end": {
"line": 5,
"column": 14
}
}
},
"cases": [
{
"kind": "ExpressionStatement",
"expression": {
"kind": "BinaryOperatorExpression",
"operator": "=",
"left": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": ".temp_expr_0"
}
},
"right": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": "v",
"loc": {
"file": "index.ts",
"start": {
"line": 5,
"column": 13
},
"end": {
"line": 5,
"column": 14
}
}
"kind": "SwitchCase",
"clause": {
"kind": "StringLiteral",
"raw": "a",
"value": "a",
"loc": {
"file": "index.ts",
"start": {
"line": 6,
"column": 14
},
"loc": {
"file": "index.ts",
"start": {
"line": 5,
"column": 13
},
"end": {
"line": 5,
"column": 14
}
}
}
}
},
{
"kind": "LocalVariableDeclaration",
"local": {
"kind": "LocalVariable",
"name": {
"kind": "Identifier",
"ident": ".temp_match_1"
},
"type": {
"kind": "TypeToken",
"tok": "bool"
},
"default": false
}
},
{
"kind": "LocalVariableDeclaration",
"local": {
"kind": "LocalVariable",
"name": {
"kind": "Identifier",
"ident": ".temp_break_2"
},
"type": {
"kind": "TypeToken",
"tok": "bool"
},
"default": true
}
},
{
"kind": "IfStatement",
"condition": {
"kind": "BinaryOperatorExpression",
"operator": "||",
"left": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": ".temp_break_2"
}
},
"right": {
"kind": "BinaryOperatorExpression",
"operator": "&&",
"left": {
"kind": "UnaryOperatorExpression",
"operator": "!",
"operand": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": ".temp_match_1"
}
}
},
"right": {
"kind": "BinaryOperatorExpression",
"operator": "==",
"left": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": ".temp_expr_0"
}
},
"right": {
"kind": "StringLiteral",
"raw": "a",
"value": "a",
"loc": {
"file": "index.ts",
"start": {
"line": 6,
"column": 14
},
"end": {
"line": 6,
"column": 17
}
}
}
"end": {
"line": 6,
"column": 17
}
}
},
"consequent": {
"kind": "Block",
"statements": [
{
"kind": "ExpressionStatement",
"expression": {
"kind": "BinaryOperatorExpression",
"operator": "=",
"left": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": ".temp_match_1"
}
},
"right": {
"kind": "BoolLiteral",
"value": true
}
}
},
{
"kind": "ExpressionStatement",
"expression": {
@ -435,20 +319,16 @@
}
},
{
"kind": "ExpressionStatement",
"expression": {
"kind": "BinaryOperatorExpression",
"operator": "=",
"left": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": ".temp_break_2"
}
"kind": "BreakStatement",
"loc": {
"file": "index.ts",
"start": {
"line": 8,
"column": 13
},
"right": {
"kind": "BoolLiteral",
"value": true
"end": {
"line": 8,
"column": 19
}
}
}
@ -467,81 +347,26 @@
}
},
{
"kind": "IfStatement",
"condition": {
"kind": "BinaryOperatorExpression",
"operator": "||",
"left": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": ".temp_break_2"
}
},
"right": {
"kind": "BinaryOperatorExpression",
"operator": "&&",
"left": {
"kind": "UnaryOperatorExpression",
"operator": "!",
"operand": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": ".temp_match_1"
}
}
"kind": "SwitchCase",
"clause": {
"kind": "StringLiteral",
"raw": "b",
"value": "b",
"loc": {
"file": "index.ts",
"start": {
"line": 9,
"column": 14
},
"right": {
"kind": "BinaryOperatorExpression",
"operator": "==",
"left": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": ".temp_expr_0"
}
},
"right": {
"kind": "StringLiteral",
"raw": "b",
"value": "b",
"loc": {
"file": "index.ts",
"start": {
"line": 9,
"column": 14
},
"end": {
"line": 9,
"column": 17
}
}
}
"end": {
"line": 9,
"column": 17
}
}
},
"consequent": {
"kind": "Block",
"statements": [
{
"kind": "ExpressionStatement",
"expression": {
"kind": "BinaryOperatorExpression",
"operator": "=",
"left": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": ".temp_match_1"
}
},
"right": {
"kind": "BoolLiteral",
"value": true
}
}
},
{
"kind": "ExpressionStatement",
"expression": {
@ -631,50 +456,10 @@
}
},
{
"kind": "IfStatement",
"condition": {
"kind": "BinaryOperatorExpression",
"operator": "||",
"left": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": ".temp_break_2"
}
},
"right": {
"kind": "UnaryOperatorExpression",
"operator": "!",
"operand": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": ".temp_match_1"
}
}
}
},
"kind": "SwitchCase",
"consequent": {
"kind": "Block",
"statements": [
{
"kind": "ExpressionStatement",
"expression": {
"kind": "BinaryOperatorExpression",
"operator": "=",
"left": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": ".temp_match_1"
}
},
"right": {
"kind": "BoolLiteral",
"value": true
}
}
},
{
"kind": "ExpressionStatement",
"expression": {
@ -750,20 +535,16 @@
}
},
{
"kind": "ExpressionStatement",
"expression": {
"kind": "BinaryOperatorExpression",
"operator": "=",
"left": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": ".temp_break_2"
}
"kind": "BreakStatement",
"loc": {
"file": "index.ts",
"start": {
"line": 13,
"column": 13
},
"right": {
"kind": "BoolLiteral",
"value": true
"end": {
"line": 13,
"column": 19
}
}
}
@ -1161,40 +942,84 @@
{
"kind": "ThrowStatement",
"expression": {
"kind": "NewExpression",
"type": {
"kind": "TypeToken",
"tok": "mujs:lib:Error",
"loc": {
"file": "index.ts",
"start": {
"line": 20,
"column": 15
},
"end": {
"line": 20,
"column": 20
}
}
},
"arguments": [
{
"kind": "BinaryOperatorExpression",
"operator": "+",
"left": {
"kind": "BinaryOperatorExpression",
"operator": "+",
"left": {
"kind": "StringLiteral",
"raw": "Expected 'a'",
"value": "Expected 'a'",
"raw": "Expected 'a'; got '",
"value": "Expected 'a'; got '",
"loc": {
"file": "index.ts",
"start": {
"line": 20,
"column": 21
"column": 11
},
"end": {
"line": 20,
"column": 35
"column": 32
}
}
},
"right": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": "basic/switch:index:a",
"loc": {
"file": "index.ts",
"start": {
"line": 20,
"column": 35
},
"end": {
"line": 20,
"column": 36
}
}
},
"loc": {
"file": "index.ts",
"start": {
"line": 20,
"column": 35
},
"end": {
"line": 20,
"column": 36
}
}
},
"loc": {
"file": "index.ts",
"start": {
"line": 20,
"column": 11
},
"end": {
"line": 20,
"column": 36
}
}
],
},
"right": {
"kind": "StringLiteral",
"raw": "'",
"value": "'",
"loc": {
"file": "index.ts",
"start": {
"line": 20,
"column": 39
},
"end": {
"line": 20,
"column": 42
}
}
},
"loc": {
"file": "index.ts",
"start": {
@ -1203,7 +1028,7 @@
},
"end": {
"line": 20,
"column": 36
"column": 42
}
}
},
@ -1215,7 +1040,7 @@
},
"end": {
"line": 20,
"column": 37
"column": 43
}
}
}
@ -1431,40 +1256,84 @@
{
"kind": "ThrowStatement",
"expression": {
"kind": "NewExpression",
"type": {
"kind": "TypeToken",
"tok": "mujs:lib:Error",
"loc": {
"file": "index.ts",
"start": {
"line": 25,
"column": 15
},
"end": {
"line": 25,
"column": 20
}
}
},
"arguments": [
{
"kind": "BinaryOperatorExpression",
"operator": "+",
"left": {
"kind": "BinaryOperatorExpression",
"operator": "+",
"left": {
"kind": "StringLiteral",
"raw": "Expected 'bd'",
"value": "Expected 'bd'",
"raw": "Expected 'bd'; got '",
"value": "Expected 'bd'; got '",
"loc": {
"file": "index.ts",
"start": {
"line": 25,
"column": 21
"column": 11
},
"end": {
"line": 25,
"column": 36
"column": 33
}
}
},
"right": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": "basic/switch:index:b",
"loc": {
"file": "index.ts",
"start": {
"line": 25,
"column": 36
},
"end": {
"line": 25,
"column": 37
}
}
},
"loc": {
"file": "index.ts",
"start": {
"line": 25,
"column": 36
},
"end": {
"line": 25,
"column": 37
}
}
},
"loc": {
"file": "index.ts",
"start": {
"line": 25,
"column": 11
},
"end": {
"line": 25,
"column": 37
}
}
],
},
"right": {
"kind": "StringLiteral",
"raw": "'",
"value": "'",
"loc": {
"file": "index.ts",
"start": {
"line": 25,
"column": 40
},
"end": {
"line": 25,
"column": 43
}
}
},
"loc": {
"file": "index.ts",
"start": {
@ -1473,7 +1342,7 @@
},
"end": {
"line": 25,
"column": 37
"column": 43
}
}
},
@ -1485,7 +1354,7 @@
},
"end": {
"line": 25,
"column": 38
"column": 44
}
}
}
@ -1701,40 +1570,84 @@
{
"kind": "ThrowStatement",
"expression": {
"kind": "NewExpression",
"type": {
"kind": "TypeToken",
"tok": "mujs:lib:Error",
"loc": {
"file": "index.ts",
"start": {
"line": 30,
"column": 15
},
"end": {
"line": 30,
"column": 20
}
}
},
"arguments": [
{
"kind": "BinaryOperatorExpression",
"operator": "+",
"left": {
"kind": "BinaryOperatorExpression",
"operator": "+",
"left": {
"kind": "StringLiteral",
"raw": "Expected 'd'",
"value": "Expected 'd'",
"raw": "Expected 'd'; got '",
"value": "Expected 'd'; got '",
"loc": {
"file": "index.ts",
"start": {
"line": 30,
"column": 21
"column": 11
},
"end": {
"line": 30,
"column": 35
"column": 32
}
}
},
"right": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": "basic/switch:index:d",
"loc": {
"file": "index.ts",
"start": {
"line": 30,
"column": 35
},
"end": {
"line": 30,
"column": 36
}
}
},
"loc": {
"file": "index.ts",
"start": {
"line": 30,
"column": 35
},
"end": {
"line": 30,
"column": 36
}
}
},
"loc": {
"file": "index.ts",
"start": {
"line": 30,
"column": 11
},
"end": {
"line": 30,
"column": 36
}
}
],
},
"right": {
"kind": "StringLiteral",
"raw": "'",
"value": "'",
"loc": {
"file": "index.ts",
"start": {
"line": 30,
"column": 39
},
"end": {
"line": 30,
"column": 42
}
}
},
"loc": {
"file": "index.ts",
"start": {
@ -1743,7 +1656,7 @@
},
"end": {
"line": 30,
"column": 36
"column": 42
}
}
},
@ -1755,7 +1668,7 @@
},
"end": {
"line": 30,
"column": 37
"column": 43
}
}
}

View file

@ -17,16 +17,16 @@ function sw(v: string): string {
let a = sw("a");
if (a !== "a") {
throw new Error("Expected 'a'");
throw "Expected 'a'; got '" + a + "'";
}
let b = sw("b");
if (b !== "bd") {
throw new Error("Expected 'bd'");
throw "Expected 'bd'; got '" + b + "'";
}
let d = sw("d");
if (d !== "d") {
throw new Error("Expected 'd'");
throw "Expected 'd'; got '" + d + "'";
}