376 lines
7.3 KiB
TypeScript
376 lines
7.3 KiB
TypeScript
import { expect, describe, test } from 'bun:test'
|
|
import { afterEach } from 'bun:test'
|
|
import { resetCommandSource, setCommandSource } from '#editor/commands'
|
|
import { beforeEach } from 'bun:test'
|
|
import './shrimp.grammar' // Importing this so changes cause it to retest!
|
|
|
|
describe('calling functions', () => {
|
|
beforeEach(() => {
|
|
setCommandSource(() => [
|
|
{
|
|
command: 'echo',
|
|
args: [{ name: 'path', type: 'string' }],
|
|
execute: (p: any) => p,
|
|
},
|
|
])
|
|
})
|
|
|
|
afterEach(() => {
|
|
resetCommandSource()
|
|
})
|
|
|
|
test('call with no args', () => {
|
|
expect('tail').toMatchTree(`
|
|
FunctionCallOrIdentifier
|
|
Identifier tail
|
|
`)
|
|
})
|
|
|
|
test('call with arg', () => {
|
|
expect('tail path').toMatchTree(`
|
|
FunctionCall
|
|
Identifier tail
|
|
PositionalArg
|
|
Identifier path
|
|
`)
|
|
})
|
|
|
|
test('call with arg and named arg', () => {
|
|
expect('tail path lines=30').toMatchTree(`
|
|
FunctionCall
|
|
Identifier tail
|
|
PositionalArg
|
|
Identifier path
|
|
NamedArg
|
|
NamedArgPrefix lines=
|
|
Number 30
|
|
`)
|
|
})
|
|
|
|
test('command with arg that is also a command', () => {
|
|
expect('tail tail').toMatchTree(`
|
|
FunctionCall
|
|
Identifier tail
|
|
PositionalArg
|
|
Identifier tail
|
|
`)
|
|
|
|
expect('tai').toMatchTree(`
|
|
FunctionCallOrIdentifier
|
|
Identifier tai
|
|
`)
|
|
})
|
|
|
|
test('Incomplete namedArg', () => {
|
|
expect('tail lines=').toMatchTree(`
|
|
FunctionCall
|
|
Identifier tail
|
|
NamedArg
|
|
NamedArgPrefix lines=
|
|
⚠
|
|
⚠ `)
|
|
})
|
|
})
|
|
|
|
describe('Identifier', () => {
|
|
test('parses identifiers with emojis and dashes', () => {
|
|
expect('moo-😊-34').toMatchTree(`
|
|
FunctionCallOrIdentifier
|
|
Identifier moo-😊-34`)
|
|
})
|
|
})
|
|
|
|
describe('Parentheses', () => {
|
|
test('parses expressions with parentheses correctly', () => {
|
|
expect('(2 + 3)').toMatchTree(`
|
|
ParenExpr
|
|
BinOp
|
|
Number 2
|
|
operator +
|
|
Number 3`)
|
|
})
|
|
|
|
test('allows parens in function calls', () => {
|
|
expect('echo (3 + 3)').toMatchTree(`
|
|
FunctionCall
|
|
Identifier echo
|
|
PositionalArg
|
|
ParenExpr
|
|
BinOp
|
|
Number 3
|
|
operator +
|
|
Number 3`)
|
|
})
|
|
|
|
test('a word can be contained in parens', () => {
|
|
expect('(basename ./cool)').toMatchTree(`
|
|
ParenExpr
|
|
FunctionCall
|
|
Identifier basename
|
|
PositionalArg
|
|
Word ./cool
|
|
`)
|
|
})
|
|
|
|
test('nested parentheses', () => {
|
|
expect('(2 + (1 * 4))').toMatchTree(`
|
|
ParenExpr
|
|
BinOp
|
|
Number 2
|
|
operator +
|
|
ParenExpr
|
|
BinOp
|
|
Number 1
|
|
operator *
|
|
Number 4`)
|
|
})
|
|
|
|
test('Function in parentheses', () => {
|
|
expect('4 + (echo 3)').toMatchTree(`
|
|
BinOp
|
|
Number 4
|
|
operator +
|
|
ParenExpr
|
|
FunctionCall
|
|
Identifier echo
|
|
PositionalArg
|
|
Number 3`)
|
|
})
|
|
})
|
|
|
|
describe('BinOp', () => {
|
|
test('addition tests', () => {
|
|
expect('2 + 3').toMatchTree(`
|
|
BinOp
|
|
Number 2
|
|
operator +
|
|
Number 3
|
|
`)
|
|
})
|
|
|
|
test('subtraction tests', () => {
|
|
expect('5 - 2').toMatchTree(`
|
|
BinOp
|
|
Number 5
|
|
operator -
|
|
Number 2
|
|
`)
|
|
})
|
|
|
|
test('multiplication tests', () => {
|
|
expect('4 * 3').toMatchTree(`
|
|
BinOp
|
|
Number 4
|
|
operator *
|
|
Number 3
|
|
`)
|
|
})
|
|
|
|
test('division tests', () => {
|
|
expect('8 / 2').toMatchTree(`
|
|
BinOp
|
|
Number 8
|
|
operator /
|
|
Number 2
|
|
`)
|
|
})
|
|
|
|
test('mixed operations with precedence', () => {
|
|
expect('2 + 3 * 4 - 5 / 1').toMatchTree(`
|
|
BinOp
|
|
BinOp
|
|
Number 2
|
|
operator +
|
|
BinOp
|
|
Number 3
|
|
operator *
|
|
Number 4
|
|
operator -
|
|
BinOp
|
|
Number 5
|
|
operator /
|
|
Number 1
|
|
`)
|
|
})
|
|
})
|
|
|
|
describe('Fn', () => {
|
|
test('parses function no parameters', () => {
|
|
expect('fn: 1').toMatchTree(`
|
|
FunctionDef
|
|
fn fn
|
|
Params
|
|
: :
|
|
Number 1`)
|
|
})
|
|
|
|
test('parses function with single parameter', () => {
|
|
expect('fn x: x + 1').toMatchTree(`
|
|
FunctionDef
|
|
fn fn
|
|
Params
|
|
Identifier x
|
|
: :
|
|
BinOp
|
|
Identifier x
|
|
operator +
|
|
Number 1`)
|
|
})
|
|
|
|
test('parses function with multiple parameters', () => {
|
|
expect('fn x y: x * y').toMatchTree(`
|
|
FunctionDef
|
|
fn fn
|
|
Params
|
|
Identifier x
|
|
Identifier y
|
|
: :
|
|
BinOp
|
|
Identifier x
|
|
operator *
|
|
Identifier y`)
|
|
})
|
|
|
|
test('parses multiline function with multiple statements', () => {
|
|
expect(`fn x y:
|
|
x * y
|
|
x + 9
|
|
end`).toMatchTree(`
|
|
FunctionDef
|
|
fn fn
|
|
Params
|
|
Identifier x
|
|
Identifier y
|
|
: :
|
|
BinOp
|
|
Identifier x
|
|
operator *
|
|
Identifier y
|
|
BinOp
|
|
Identifier x
|
|
operator +
|
|
Number 9
|
|
end end`)
|
|
})
|
|
})
|
|
|
|
describe('ambiguity', () => {
|
|
test('parses ambiguous expressions correctly', () => {
|
|
expect('a + -3').toMatchTree(`
|
|
BinOp
|
|
Identifier a
|
|
operator +
|
|
Number -3
|
|
`)
|
|
})
|
|
|
|
test('parses ambiguous expressions correctly', () => {
|
|
expect('a-var + a-thing').toMatchTree(`
|
|
BinOp
|
|
Identifier a-var
|
|
operator +
|
|
Identifier a-thing
|
|
`)
|
|
})
|
|
})
|
|
|
|
describe('newlines', () => {
|
|
test('parses multiple statements separated by newlines', () => {
|
|
expect(`x = 5
|
|
y = 2`).toMatchTree(`
|
|
Assign
|
|
Identifier x
|
|
= =
|
|
Number 5
|
|
Assign
|
|
Identifier y
|
|
= =
|
|
Number 2`)
|
|
})
|
|
|
|
test('parses statements separated by semicolons', () => {
|
|
expect(`x = 5; y = 2`).toMatchTree(`
|
|
Assign
|
|
Identifier x
|
|
= =
|
|
Number 5
|
|
Assign
|
|
Identifier y
|
|
= =
|
|
Number 2`)
|
|
})
|
|
|
|
test('parses statement with word and a semicolon', () => {
|
|
expect(`a = hello; 2`).toMatchTree(`
|
|
Assign
|
|
Identifier a
|
|
= =
|
|
FunctionCallOrIdentifier
|
|
Identifier hello
|
|
Number 2`)
|
|
})
|
|
})
|
|
|
|
describe('Assign', () => {
|
|
test('parses simple assignment', () => {
|
|
expect('x = 5').toMatchTree(`
|
|
Assign
|
|
Identifier x
|
|
= =
|
|
Number 5`)
|
|
})
|
|
|
|
test('parses assignment with addition', () => {
|
|
expect('x = 5 + 3').toMatchTree(`
|
|
Assign
|
|
Identifier x
|
|
= =
|
|
BinOp
|
|
Number 5
|
|
operator +
|
|
Number 3`)
|
|
})
|
|
|
|
test('parses assignment with functions', () => {
|
|
expect('add = fn a b: a + b').toMatchTree(`
|
|
Assign
|
|
Identifier add
|
|
= =
|
|
FunctionDef
|
|
fn fn
|
|
Params
|
|
Identifier a
|
|
Identifier b
|
|
: :
|
|
BinOp
|
|
Identifier a
|
|
operator +
|
|
Identifier b`)
|
|
})
|
|
})
|
|
|
|
describe('whitespace', () => {
|
|
test('trims leading and trailing whitespace in expected tree', () => {
|
|
expect(`
|
|
3
|
|
|
|
|
|
fn x y:
|
|
x
|
|
end
|
|
|
|
`).toMatchTree(`
|
|
Number 3
|
|
|
|
FunctionDef
|
|
fn fn
|
|
Params
|
|
Identifier x
|
|
Identifier y
|
|
: :
|
|
Identifier x
|
|
end end
|
|
`)
|
|
})
|
|
})
|