import { describe, test, expect } from 'bun:test' import '../../testSetup' describe('DotGet', () => { test('readme.txt is Word when readme not in scope', () => { expect('readme.txt').toMatchTree(`Word readme.txt`) }) test('readme.txt is Word when used in function', () => { expect('echo readme.txt').toMatchTree(` FunctionCall Identifier echo PositionalArg Word readme.txt`) }) test('obj.prop is DotGet when obj is assigned', () => { expect('obj = 5; obj.prop').toMatchTree(` Assign AssignableIdentifier obj Eq = Number 5 FunctionCallOrIdentifier DotGet IdentifierBeforeDot obj Identifier prop `) }) test('function parameters are in scope within function body', () => { expect('do config: config.path end').toMatchTree(` FunctionDef Do do Params Identifier config colon : FunctionCallOrIdentifier DotGet IdentifierBeforeDot config Identifier path keyword end `) }) test('parameters out of scope outside function', () => { expect('do x: x.prop end; x.prop').toMatchTree(` FunctionDef Do do Params Identifier x colon : FunctionCallOrIdentifier DotGet IdentifierBeforeDot x Identifier prop keyword end Word x.prop `) }) test('multiple parameters work correctly', () => { expect(`do x y: x.foo y.bar end`).toMatchTree(` FunctionDef Do do Params Identifier x Identifier y colon : FunctionCallOrIdentifier DotGet IdentifierBeforeDot x Identifier foo FunctionCallOrIdentifier DotGet IdentifierBeforeDot y Identifier bar keyword end `) }) test('nested functions with scope isolation', () => { expect(`do x: x.outer do y: y.inner end end`).toMatchTree(` FunctionDef Do do Params Identifier x colon : FunctionCallOrIdentifier DotGet IdentifierBeforeDot x Identifier outer FunctionDef Do do Params Identifier y colon : FunctionCallOrIdentifier DotGet IdentifierBeforeDot y Identifier inner keyword end keyword end `) }) test('dot get works as function argument', () => { expect('config = 42; echo config.path').toMatchTree(` Assign AssignableIdentifier config Eq = Number 42 FunctionCall Identifier echo PositionalArg DotGet IdentifierBeforeDot config Identifier path `) }) test('dot get works as bare function', () => { expect('io = dict print=echo; io.print').toMatchTree(` Assign AssignableIdentifier io Eq = FunctionCall Identifier dict NamedArg NamedArgPrefix print= Identifier echo FunctionCallOrIdentifier DotGet IdentifierBeforeDot io Identifier print `) }) test('dot get works as function w/ args', () => { expect('io = dict print=echo; io.print heya').toMatchTree(` Assign AssignableIdentifier io Eq = FunctionCall Identifier dict NamedArg NamedArgPrefix print= Identifier echo FunctionCall DotGet IdentifierBeforeDot io Identifier print PositionalArg Identifier heya `) }) test('dot get works as function in parens', () => { expect('io = dict print=echo; (io.print heya)').toMatchTree(` Assign AssignableIdentifier io Eq = FunctionCall Identifier dict NamedArg NamedArgPrefix print= Identifier echo ParenExpr FunctionCall DotGet IdentifierBeforeDot io Identifier print PositionalArg Identifier heya `) }) test('mixed file paths and dot get', () => { expect('config = 42; cat readme.txt; echo config.path').toMatchTree(` Assign AssignableIdentifier config Eq = Number 42 FunctionCall Identifier cat PositionalArg Word readme.txt FunctionCall Identifier echo PositionalArg DotGet IdentifierBeforeDot config Identifier path `) }) test("dot get doesn't work with spaces", () => { expect('obj . prop').toMatchTree(` FunctionCall Identifier obj PositionalArg Word . PositionalArg Identifier prop`) }) test('readme.1 is Word when readme not in scope', () => { expect('readme.1').toMatchTree(`Word readme.1`) }) test('readme.1 is Word when used in function', () => { expect('echo readme.1').toMatchTree(` FunctionCall Identifier echo PositionalArg Word readme.1`) }) test('obj.1 is DotGet when obj is assigned', () => { expect('obj = 5; obj.1').toMatchTree(` Assign AssignableIdentifier obj Eq = Number 5 FunctionCallOrIdentifier DotGet IdentifierBeforeDot obj Number 1 `) }) test('obj.1 arg is DotGet when obj is assigned', () => { expect('obj = 5; obj.1').toMatchTree(` Assign AssignableIdentifier obj Eq = Number 5 FunctionCallOrIdentifier DotGet IdentifierBeforeDot obj Number 1 `) }) test('dot get index works as function w/ args', () => { expect(`io = list (do x: echo x end); io.0 heya`).toMatchTree(` Assign AssignableIdentifier io Eq = FunctionCall Identifier list PositionalArg ParenExpr FunctionDef Do do Params Identifier x colon : FunctionCall Identifier echo PositionalArg Identifier x keyword end FunctionCall DotGet IdentifierBeforeDot io Number 0 PositionalArg Identifier heya `) }) test('can use the result of a parens expression as the property of dot get', () => { expect('obj = list 1 2 3; obj.(1 + 2)').toMatchTree(` Assign AssignableIdentifier obj Eq = FunctionCall Identifier list PositionalArg Number 1 PositionalArg Number 2 PositionalArg Number 3 FunctionCallOrIdentifier DotGet IdentifierBeforeDot obj ParenExpr BinOp Number 1 Plus + Number 2 `) }) // NOTE: these are parsed as DotGet(meta, DotGet(script, name)) because that's easiest, // but the compiler flattens them test('chained dot get: meta.script.name', () => { expect('meta = 42; meta.script.name').toMatchTree(` Assign AssignableIdentifier meta Eq = Number 42 FunctionCallOrIdentifier DotGet IdentifierBeforeDot meta DotGet IdentifierBeforeDot script Identifier name `) }) test('chained dot get: a.b.c.d', () => { expect('a = 1; a.b.c.d').toMatchTree(` Assign AssignableIdentifier a Eq = Number 1 FunctionCallOrIdentifier DotGet IdentifierBeforeDot a DotGet IdentifierBeforeDot b DotGet IdentifierBeforeDot c Identifier d `) }) test('chained dot get in function call', () => { expect('config = 1; echo config.db.host').toMatchTree(` Assign AssignableIdentifier config Eq = Number 1 FunctionCall Identifier echo PositionalArg DotGet IdentifierBeforeDot config DotGet IdentifierBeforeDot db Identifier host `) }) test('chained dot get with numeric index at end', () => { expect('obj = 1; obj.items.0').toMatchTree(` Assign AssignableIdentifier obj Eq = Number 1 FunctionCallOrIdentifier DotGet IdentifierBeforeDot obj DotGet IdentifierBeforeDot items Number 0 `) }) test('chained dot get with ParenExpr at end', () => { expect('obj = 1; obj.items.(i)').toMatchTree(` Assign AssignableIdentifier obj Eq = Number 1 FunctionCallOrIdentifier DotGet IdentifierBeforeDot obj DotGet IdentifierBeforeDot items ParenExpr FunctionCallOrIdentifier Identifier i `) }) test('not in scope remains Word with chained dots', () => { expect('readme.md.bak').toMatchTree(`Word readme.md.bak`) }) test('chained dot get in nested functions', () => { expect(`do cfg: do inner: cfg.db.host end end`).toMatchTree(` FunctionDef Do do Params Identifier cfg colon : FunctionDef Do do Params Identifier inner colon : FunctionCallOrIdentifier DotGet IdentifierBeforeDot cfg DotGet IdentifierBeforeDot db Identifier host keyword end keyword end `) }) test('mixed simple and chained dot get', () => { expect('obj = 1; obj.a; obj.b.c').toMatchTree(` Assign AssignableIdentifier obj Eq = Number 1 FunctionCallOrIdentifier DotGet IdentifierBeforeDot obj Identifier a FunctionCallOrIdentifier DotGet IdentifierBeforeDot obj DotGet IdentifierBeforeDot b Identifier c `) }) test.skip('chained numeric dot get: row.2.1.b', () => { expect('row = []; row.2.1').toMatchTree(` Assign AssignableIdentifier row Eq = Array [] FunctionCallOrIdentifier DotGet IdentifierBeforeDot row DotGet Number 2 DotGet Number 1 Identifier b `) }) })