shrimp/src/parser/tests/control-flow.test.ts
2025-11-13 22:34:04 -08:00

378 lines
7.6 KiB
TypeScript

import { expect, describe, test } from 'bun:test'
import '../shrimp.grammar' // Importing this so changes cause it to retest!
describe('if/else if/else', () => {
test('parses single line if', () => {
expect(`if y == 1: 'cool' end`).toMatchTree(`
IfExpr
keyword if
ConditionalOp
Identifier y
EqEq ==
Number 1
colon :
Block
String
StringFragment cool
keyword end
`)
expect('a = if x: 2 end').toMatchTree(`
Assign
AssignableIdentifier a
Eq =
IfExpr
keyword if
FunctionCallOrIdentifier
Identifier x
colon :
Block
Number 2
keyword end
`)
})
test('parses multiline if', () => {
expect(`
if x < 9:
yes
end`).toMatchTree(`
IfExpr
keyword if
ConditionalOp
Identifier x
Lt <
Number 9
colon :
Block
FunctionCallOrIdentifier
Identifier yes
keyword end
`)
})
test('parses multiline if with else', () => {
expect(`if with-else:
x
else:
y
end`).toMatchTree(`
IfExpr
keyword if
FunctionCallOrIdentifier
Identifier with-else
colon :
Block
FunctionCallOrIdentifier
Identifier x
ElseExpr
keyword else
colon :
Block
FunctionCallOrIdentifier
Identifier y
keyword end
`)
})
test('parses multiline if with else if', () => {
expect(`if with-else-if:
x
else if another-condition:
y
end`).toMatchTree(`
IfExpr
keyword if
FunctionCallOrIdentifier
Identifier with-else-if
colon :
Block
FunctionCallOrIdentifier
Identifier x
ElseIfExpr
keyword else
keyword if
FunctionCallOrIdentifier
Identifier another-condition
colon :
Block
FunctionCallOrIdentifier
Identifier y
keyword end
`)
})
test('parses multiline if with multiple else if and else', () => {
expect(`if with-else-if-else:
x
else if another-condition:
y
else if yet-another-condition:
z
else:
oh-no
end`).toMatchTree(`
IfExpr
keyword if
FunctionCallOrIdentifier
Identifier with-else-if-else
colon :
Block
FunctionCallOrIdentifier
Identifier x
ElseIfExpr
keyword else
keyword if
FunctionCallOrIdentifier
Identifier another-condition
colon :
Block
FunctionCallOrIdentifier
Identifier y
ElseIfExpr
keyword else
keyword if
FunctionCallOrIdentifier
Identifier yet-another-condition
colon :
Block
FunctionCallOrIdentifier
Identifier z
ElseExpr
keyword else
colon :
Block
FunctionCallOrIdentifier
Identifier oh-no
keyword end
`)
})
test('does not parse identifiers that start with if', () => {
expect('iffy = if true: 2 end').toMatchTree(`
Assign
AssignableIdentifier iffy
Eq =
IfExpr
keyword if
Boolean true
colon :
Block
Number 2
keyword end
`)
})
test('parses function calls in if tests', () => {
expect(`if var? 'abc': true end`).toMatchTree(`
IfExpr
keyword if
FunctionCall
Identifier var?
PositionalArg
String
StringFragment abc
colon :
Block
Boolean true
keyword end
`)
})
test("parses paren'd function calls in if tests", () => {
expect(`if (var? 'abc'): true end`).toMatchTree(`
IfExpr
keyword if
ParenExpr
FunctionCall
Identifier var?
PositionalArg
String
StringFragment abc
colon :
Block
Boolean true
keyword end
`)
})
test('parses function calls in else-if tests', () => {
expect(`if false: true else if var? 'abc': true end`).toMatchTree(`
IfExpr
keyword if
Boolean false
colon :
Block
Boolean true
ElseIfExpr
keyword else
keyword if
FunctionCall
Identifier var?
PositionalArg
String
StringFragment abc
colon :
Block
Boolean true
keyword end
`)
})
test("parses paren'd function calls in else-if tests", () => {
expect(`if false: true else if (var? 'abc'): true end`).toMatchTree(`
IfExpr
keyword if
Boolean false
colon :
Block
Boolean true
ElseIfExpr
keyword else
keyword if
ParenExpr
FunctionCall
Identifier var?
PositionalArg
String
StringFragment abc
colon :
Block
Boolean true
keyword end
`)
})
test('allows if/else in parens', () => {
expect(`eh? = (if true: true end)`).toMatchTree(`
Assign
AssignableIdentifier eh?
Eq =
ParenExpr
IfExpr
keyword if
Boolean true
colon :
Block
Boolean true
keyword end
`)
})
})
describe('while', () => {
test('infinite loop', () => {
expect(`while true: true end`).toMatchTree(`
WhileExpr
keyword while
Boolean true
colon :
Block
Boolean true
keyword end`)
})
test('basic expression', () => {
expect(`while a > 0: true end`).toMatchTree(`
WhileExpr
keyword while
ConditionalOp
Identifier a
Gt >
Number 0
colon :
Block
Boolean true
keyword end`)
})
test('compound expression', () => {
expect(`while a > 0 and b < 100 and c < 1000: true end`).toMatchTree(`
WhileExpr
keyword while
ConditionalOp
ConditionalOp
ConditionalOp
Identifier a
Gt >
Number 0
And and
ConditionalOp
Identifier b
Lt <
Number 100
And and
ConditionalOp
Identifier c
Lt <
Number 1000
colon :
Block
Boolean true
keyword end`)
})
test('multiline infinite loop', () => {
expect(`
while true:
true
end`).toMatchTree(`
WhileExpr
keyword while
Boolean true
colon :
Block
Boolean true
keyword end`)
})
test('multiline basic expression', () => {
expect(`
while a > 0:
true
end`).toMatchTree(`
WhileExpr
keyword while
ConditionalOp
Identifier a
Gt >
Number 0
colon :
Block
Boolean true
keyword end`)
})
test('multiline compound expression', () => {
expect(`
while a > 0 and b < 100 and c < 1000:
true
end`).toMatchTree(`
WhileExpr
keyword while
ConditionalOp
ConditionalOp
ConditionalOp
Identifier a
Gt >
Number 0
And and
ConditionalOp
Identifier b
Lt <
Number 100
And and
ConditionalOp
Identifier c
Lt <
Number 1000
colon :
Block
Boolean true
keyword end`)
})
})