do allowed in arg/dict values

This commit is contained in:
Chris Wanstrath 2025-11-25 13:16:41 -08:00 committed by Chris Wanstrath
parent 9e4471ad38
commit 566beb87ef
3 changed files with 88 additions and 16 deletions

View File

@ -241,6 +241,22 @@ export class Parser {
// parse specific nodes // parse specific nodes
// //
// raw determines whether we just want the SyntaxNodes or we want to
// wrap them in a PositionalArg
arg(raw = false): SyntaxNode {
// 'do' is a special function arg - it doesn't need to be wrapped
// in parens. otherwise, args are regular value()s
const val = this.is($T.Keyword, 'do') ? this.do() : this.value()
if (raw) {
return val
} else {
const arg = new SyntaxNode('PositionalArg', val.from, val.to)
arg.add(val)
return arg
}
}
// [ 1 2 3 ] // [ 1 2 3 ]
array(): SyntaxNode { array(): SyntaxNode {
const open = this.expect($T.OpenBracket) const open = this.expect($T.OpenBracket)
@ -425,10 +441,7 @@ export class Parser {
continue continue
} }
if (this.is($T.NamedArgPrefix)) values.push(this.is($T.NamedArgPrefix) ? this.namedArg() : this.arg())
values.push(this.namedArg())
else
values.push(this.value())
} }
const close = this.expect($T.CloseBracket) const close = this.expect($T.CloseBracket)
@ -549,17 +562,8 @@ export class Parser {
const ident = fn ?? this.identifier() const ident = fn ?? this.identifier()
const args: SyntaxNode[] = [] const args: SyntaxNode[] = []
while (!this.isExprEnd() && !this.is($T.Operator, '|')) { while (!this.isExprEnd() && !this.is($T.Operator, '|'))
if (this.is($T.NamedArgPrefix)) { args.push(this.is($T.NamedArgPrefix) ? this.namedArg() : this.arg())
args.push(this.namedArg())
} else {
// 'do' is the only keyword allowed as a function argument
const val = this.is($T.Keyword, 'do') ? this.do() : this.value()
const arg = new SyntaxNode('PositionalArg', val.from, val.to)
arg.add(val)
args.push(arg)
}
}
const node = new SyntaxNode('FunctionCall', ident.from, (args.at(-1) || ident).to) const node = new SyntaxNode('FunctionCall', ident.from, (args.at(-1) || ident).to)
node.push(ident, ...args) node.push(ident, ...args)
@ -669,7 +673,7 @@ export class Parser {
// abc= true // abc= true
namedArg(): SyntaxNode { namedArg(): SyntaxNode {
const prefix = SyntaxNode.from(this.expect($T.NamedArgPrefix)) const prefix = SyntaxNode.from(this.expect($T.NamedArgPrefix))
const val = this.value() const val = this.arg(true)
const node = new SyntaxNode('NamedArg', prefix.from, val.to) const node = new SyntaxNode('NamedArg', prefix.from, val.to)
return node.push(prefix, val) return node.push(prefix, val)
} }

View File

@ -43,6 +43,58 @@ describe('calling functions', () => {
`) `)
}) })
test('call with function', () => {
expect(`tail do x: x end`).toMatchTree(`
FunctionCall
Identifier tail
PositionalArg
FunctionDef
Do do
Params
Identifier x
colon :
FunctionCallOrIdentifier
Identifier x
keyword end
`)
})
test('call with arg and function', () => {
expect(`tail true do x: x end`).toMatchTree(`
FunctionCall
Identifier tail
PositionalArg
Boolean true
PositionalArg
FunctionDef
Do do
Params
Identifier x
colon :
FunctionCallOrIdentifier
Identifier x
keyword end
`)
})
test('call with function in named arg', () => {
expect(`tail callback=do x: x end`).toMatchTree(`
FunctionCall
Identifier tail
NamedArg
NamedArgPrefix callback=
FunctionDef
Do do
Params
Identifier x
colon :
FunctionCallOrIdentifier
Identifier x
keyword end
`)
})
test('command with arg that is also a command', () => { test('command with arg that is also a command', () => {
expect('tail tail').toMatchTree(` expect('tail tail').toMatchTree(`
FunctionCall FunctionCall

View File

@ -336,6 +336,22 @@ describe('dict literals', () => {
`) `)
}) })
test('work with functions', () => {
expect(`[trap=do x: x end]`).toMatchTree(`
Dict
NamedArg
NamedArgPrefix trap=
FunctionDef
Do do
Params
Identifier x
colon :
FunctionCallOrIdentifier
Identifier x
keyword end
`)
})
test('can be nested', () => { test('can be nested', () => {
expect('[a=one b=[two [c=three]]]').toMatchTree(` expect('[a=one b=[two [c=three]]]').toMatchTree(`
Dict Dict