Skip to content

Commit

Permalink
Remove if-expr and if-let-expr
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanjermakov committed Jul 27, 2024
1 parent 00fc2b8 commit c0ae9b7
Show file tree
Hide file tree
Showing 9 changed files with 5 additions and 238 deletions.
8 changes: 1 addition & 7 deletions nois.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ statement ::= var-def | fn-def | trait-def | impl-def | type-def | r
;
sub-expr ::= operand postfix-op*
;
operand ::= if-expr
| if-let-expr
| while-expr
operand ::= while-expr
| for-expr
| match-expr
| closure-expr
Expand Down Expand Up @@ -114,10 +112,6 @@ list-expr ::= O-BRACKET (expr (COMMA expr)*)? COMMA? C-BRACKET
;
type-annot ::= COLON type
;
if-expr ::= IF-KEYWORD expr block (ELSE-KEYWORD block)?
;
if-let-expr ::= IF-KEYWORD LET-KEYWORD pattern EQUALS expr block (ELSE-KEYWORD block)?
;
while-expr ::= WHILE-KEYWORD expr block
;
for-expr ::= FOR-KEYWORD pattern IN-KEYWORD expr block
Expand Down
2 changes: 0 additions & 2 deletions src/ast/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ export const astExprKinds = <const>[
'binary-expr',
'closure-expr',
'list-expr',
'if-let-expr',
'while-expr',
'for-expr',
'match-expr'
Expand Down Expand Up @@ -182,7 +181,6 @@ export const astKinds = <const>[
'type-bounds',
'fn-type',
'generic',
'if-expr',
'match-clause',
'pattern',
'con-pattern',
Expand Down
50 changes: 0 additions & 50 deletions src/ast/operand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { LexerToken } from '../lexer/lexer'
import { ParseNode, ParseTree, filterNonAstNodes } from '../parser'
import { nameLikeTokens } from '../parser/fns'
import { Context, Definition } from '../scope'
import { VirtualIdentifierMatch } from '../scope/vid'
import { Virtual } from '../semantic'
import { assert } from '../util/todo'
import { Expr, buildExpr } from './expr'
Expand All @@ -12,8 +11,6 @@ import { Block, buildBlock, buildStatement } from './statement'
import { Type, buildType } from './type'

export type Operand = (
| IfExpr
| IfLetExpr
| WhileExpr
| ForExpr
| MatchExpr
Expand All @@ -33,10 +30,6 @@ export type Operand = (
export const buildOperand = (node: ParseNode, ctx: Context): Operand => {
const n = filterNonAstNodes(node)[0]
switch (n.kind) {
case 'if-expr':
return buildIfExpr(n, ctx)
case 'if-let-expr':
return buildIfLetExpr(n, ctx)
case 'while-expr':
return buildWhileExpr(n, ctx)
case 'for-expr':
Expand Down Expand Up @@ -71,49 +64,6 @@ export const identifierFromOperand = (operand: Operand): Identifier | undefined
return undefined
}

export type IfExpr = BaseAstNode & {
kind: 'if-expr'
condition: Expr
thenBlock: Block
elseBlock?: Block
}

export const buildIfExpr = (node: ParseNode, ctx: Context): IfExpr => {
const nodes = filterNonAstNodes(node)
let idx = 0
// skip if-keyword
idx++
const condition = buildExpr(nodes[idx++], ctx)
const thenBlock = buildBlock(nodes[idx++], ctx)
// skip else keyword
idx++
const elseBlock = nodes.at(idx) ? buildBlock(nodes[idx++], ctx) : undefined
return { kind: 'if-expr', parseNode: node, condition, thenBlock, elseBlock }
}

export type IfLetExpr = BaseAstNode & {
kind: 'if-let-expr'
pattern: Pattern
expr: Expr
thenBlock: Block
elseBlock?: Block
}

export const buildIfLetExpr = (node: ParseNode, ctx: Context): IfLetExpr => {
const nodes = filterNonAstNodes(node)
let idx = 0
// skip if and let keywords
idx++
idx++
const pattern = buildPattern(nodes[idx++], ctx)
const expr = buildExpr(nodes[idx++], ctx)
const thenBlock = buildBlock(nodes[idx++], ctx)
// skip else keyword
idx++
const elseBlock = nodes.at(idx) ? buildBlock(nodes[idx++], ctx) : undefined
return { kind: 'if-let-expr', parseNode: node, pattern, expr, thenBlock, elseBlock }
}

export type WhileExpr = BaseAstNode & {
kind: 'while-expr'
condition: Expr
Expand Down
22 changes: 1 addition & 21 deletions src/codegen/js/expr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { operatorImplMap } from '../../semantic/op'
import { ConcreteGeneric } from '../../typecheck'
import { unreachable } from '../../util/todo'
import { EmitNode, EmitToken, emitToken, emitTree, jsError, jsVariable } from './node'
import { emitBlock, emitBlockStatements } from './statement'
import { emitBlockStatements } from './statement'

export type EmitExpr = {
emit: EmitNode
Expand Down Expand Up @@ -182,26 +182,6 @@ export const emitBinaryExpr = (binaryExpr: BinaryExpr, ctx: Context): EmitExpr =
export const emitOperand = (operand: Operand, ctx: Context): EmitExpr => {
const resultVar = nextVariable(ctx)
switch (operand.kind) {
case 'if-expr': {
const { emit: cEmit, resultVar: cVar } = emitExpr(operand.condition, ctx)
const thenBlock = emitBlock(operand.thenBlock, ctx, resultVar)
const elseBlock = operand.elseBlock
? emitTree([emitToken('else'), emitBlock(operand.elseBlock, ctx, resultVar)])
: emitToken('')
return {
emit: emitTree([
jsVariable(resultVar),
cEmit,
emitToken(`if(${extractValue(cVar)})`),
thenBlock,
elseBlock
]),
resultVar
}
}
case 'if-let-expr':
// TODO
return { emit: jsError('if-let'), resultVar }
case 'while-expr': {
const { emit: cEmit, resultVar: cVar } = emitExpr(operand.condition, ctx)
const { emit: cEndEmit, resultVar: cEndVar } = emitExpr(operand.condition, ctx)
Expand Down
39 changes: 2 additions & 37 deletions src/parser/fns/expr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,14 @@ export const parseSubExpr = (parser: Parser): void => {
}

/**
* operand ::= if-expr | match-expr | closure-expr | O-PAREN expr C-PAREN | list-expr | STRING | CHAR | number | TRUE
* operand ::= match-expr | closure-expr | O-PAREN expr C-PAREN | list-expr | STRING | CHAR | number | TRUE
* | FALSE | identifier | type
*/
export const parseOperand = (parser: Parser): void => {
const dynamicTokens: TokenKind[] = ['char', 'int', 'float', 'bool']

const mark = parser.open()
if (parser.at('if-keyword') && parser.nth(1) === 'let-keyword') {
parseIfLetExpr(parser)
} else if (parser.at('if-keyword')) {
parseIfExpr(parser)
} else if (parser.at('while-keyword')) {
if (parser.at('while-keyword')) {
parseWhileExpr(parser)
} else if (parser.at('for-keyword')) {
parseForExpr(parser)
Expand Down Expand Up @@ -94,37 +90,6 @@ export const parseListExpr = (parser: Parser): void => {
parser.close(mark, 'list-expr')
}

/**
* if-expr ::= IF-KEYWORD expr block (ELSE-KEYWORD block)?
*/
export const parseIfExpr = (parser: Parser): void => {
const mark = parser.open()
parser.expect('if-keyword')
parseExpr(parser)
parseBlock(parser)
if (parser.consume('else-keyword')) {
parseBlock(parser)
}
parser.close(mark, 'if-expr')
}

/**
* if-let-expr ::= IF-KEYWORD LET-KEYWORD pattern EQUALS expr block (ELSE-KEYWORD block)?
*/
export const parseIfLetExpr = (parser: Parser): void => {
const mark = parser.open()
parser.expect('if-keyword')
parser.expect('let-keyword')
parsePattern(parser)
parser.expect('equals')
parseExpr(parser)
parseBlock(parser)
if (parser.consume('else-keyword')) {
parseBlock(parser)
}
parser.close(mark, 'if-let-expr')
}

/**
* while-expr ::= WHILE-KEYWORD expr block
*/
Expand Down
2 changes: 0 additions & 2 deletions src/parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,6 @@ export const treeKinds = <const>[
'type-bounds',
'fn-type',
'fn-type-params',
'if-expr',
'if-let-expr',
'while-expr',
'for-expr',
'match-expr',
Expand Down
47 changes: 0 additions & 47 deletions src/parser/parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,53 +161,6 @@ describe('parser', () => {
})
})

describe('if-expr', () => {
it('general', () => {
const { tree, errors } = parse('if a { b } else { c }')
expect(errors).toEqual([])
// biome-ignore format: compact
expect(tree).toEqual(
{ module:
[ { statement:
[ { expr:
[ { 'sub-expr':
[ { operand:
[ { 'if-expr':
[ { 'if-keyword': 'if' },
{ expr: [ { 'sub-expr': [ { operand: [ { identifier: [ { name: 'a' } ] } ] } ] } ] },
{ block:
[ { 'o-brace': '{' },
{ statement: [ { expr: [ { 'sub-expr': [ { operand: [ { identifier: [ { name: 'b' } ] } ] } ] } ] } ] },
{ 'c-brace': '}' } ] },
{ 'else-keyword': 'else' },
{ block:
[ { 'o-brace': '{' },
{ statement: [ { expr: [ { 'sub-expr': [ { operand: [ { identifier: [ { name: 'c' } ] } ] } ] } ] } ] },
{ 'c-brace': '}' } ] } ] } ] } ] } ] } ] } ] }
)
})

it('mismatch paren', () => {
const { errors } = parse('if a { b) }')
expect(errors.length).toEqual(1)
expect(errors[0]).toEqual({
expected: [],
got: { kind: 'c-paren', span: { end: 9, start: 8 }, value: ')' },
message: 'expected statement'
})
})

it('duplicate else clause', () => {
const { errors } = parse('if a { b } else { c } else { d }')
expect(errors.length).toEqual(2)
expect(errors[0]).toEqual({
expected: [],
got: { kind: 'o-brace', span: { end: 28, start: 27 }, value: '{' },
message: 'expected statement'
})
})
})

describe('match', () => {
it('list-pattern', () => {
const { tree, errors } = parse('match a { [] {} [b, tail] {} }')
Expand Down
8 changes: 0 additions & 8 deletions src/phase/name-resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,6 @@ export const resolveName = (node: AstNode, ctx: Context): void => {
node.bounds.forEach(b => resolveName(b, ctx))
break
}
case 'if-expr': {
resolveName(node.condition, ctx)
resolveName(node.thenBlock, ctx)
if (node.elseBlock) {
resolveName(node.elseBlock, ctx)
}
break
}
case 'match-clause': {
withScope(ctx, () => {
node.patterns.forEach(p => resolveName(p, ctx))
Expand Down
65 changes: 1 addition & 64 deletions src/semantic/expr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Arg, AstNode } from '../ast'
import { BinaryExpr, Expr, UnaryExpr } from '../ast/expr'
import { MatchExpr } from '../ast/match'
import { CallOp } from '../ast/op'
import { ClosureExpr, ForExpr, Identifier, IfExpr, IfLetExpr, ListExpr, Name, Operand, WhileExpr } from '../ast/operand'
import { ClosureExpr, ForExpr, Identifier, ListExpr, Name, Operand, WhileExpr } from '../ast/operand'
import { FieldDef } from '../ast/type-def'
import { Context, Scope, addError, enterScope, fnDefScope, instanceScope, leaveScope } from '../scope'
import { bool, future, iter, iterable, show, string, unwrap } from '../scope/std'
Expand Down Expand Up @@ -75,12 +75,6 @@ export const checkOperand = (operand: Operand, ctx: Context): void => {
checkOperand(operand.operand, ctx)
operand.type = operand.operand.type
break
case 'if-expr':
checkIfExpr(operand, ctx)
break
case 'if-let-expr':
checkIfLetExpr(operand, ctx)
break
case 'while-expr':
checkWhileExpr(operand, ctx)
break
Expand Down Expand Up @@ -231,63 +225,6 @@ export const checkBinaryExpr = (binaryExpr: BinaryExpr, ctx: Context): void => {
}
}

export const checkIfExpr = (ifExpr: IfExpr, ctx: Context): void => {
const module = ctx.moduleStack.at(-1)!
const scope = module.scopeStack.at(-1)!

checkExpr(ifExpr.condition, ctx)
const condType = ifExpr.condition.type ?? unknownType
if (!isAssignable(condType, bool, ctx)) {
addError(ctx, typeError(ctx, ifExpr.condition, condType, bool))
}

checkIfExprCommon(ifExpr, scope, ctx)
}

export const checkIfLetExpr = (ifLetExpr: IfLetExpr, ctx: Context): void => {
const module = ctx.moduleStack.at(-1)!
const scopeOuter = module.scopeStack.at(-1)!
const scope: Scope = {
kind: 'block',
definitions: new Map(),
closures: [],
isLoop: false,
allBranchesReturned: false
}
enterScope(module, scope, ctx)

checkExpr(ifLetExpr.expr, ctx)
assert(!!ifLetExpr.expr.type)
// pattern definitions should only be available in `then` block
checkPattern(ifLetExpr.pattern, ifLetExpr.expr.type!, ctx)

checkIfExprCommon(ifLetExpr, scopeOuter, ctx)

leaveScope(module, ctx)
}

export const checkIfExprCommon = (ifExpr: IfExpr | IfLetExpr, scope: Scope, ctx: Context): void => {
const thenAbr = checkBlock(ifExpr.thenBlock, ctx)
if (ifExpr.elseBlock) {
const elseAbr = checkBlock(ifExpr.elseBlock, ctx)

if (scope.kind === 'block' && thenAbr && elseAbr) {
scope.allBranchesReturned = true
}

const thenType = ifExpr.thenBlock.type!
const elseType = ifExpr.elseBlock.type!
const combined = combine(thenType, elseType, ctx)
if (combined) {
ifExpr.type = combined
} else {
ifExpr.type = { kind: 'unknown-type', mismatchedBranches: { then: thenType, else: elseType } }
}
} else {
ifExpr.type = { kind: 'unknown-type', mismatchedBranches: { then: ifExpr.thenBlock.type! } }
}
}

export const checkWhileExpr = (whileExpr: WhileExpr, ctx: Context): void => {
const module = ctx.moduleStack.at(-1)!
const outerScope = module.scopeStack.at(-1)!
Expand Down

0 comments on commit c0ae9b7

Please sign in to comment.