From abd7d2e43b4f4dc7147e5123adc981a7cab16c9d Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Sun, 26 Oct 2025 16:28:56 -0700 Subject: [PATCH] DotGet function calls --- src/parser/shrimp.grammar | 5 +- src/parser/shrimp.terms.ts | 34 +++++----- src/parser/shrimp.ts | 18 +++--- src/parser/tests/basics.test.ts | 7 ++- src/parser/tests/dot-get.test.ts | 105 ++++++++++++++++++++++++------- 5 files changed, 116 insertions(+), 53 deletions(-) diff --git a/src/parser/shrimp.grammar b/src/parser/shrimp.grammar index 741dad0..8695018 100644 --- a/src/parser/shrimp.grammar +++ b/src/parser/shrimp.grammar @@ -46,7 +46,6 @@ item { consumeToTerminator { PipeExpr | ambiguousFunctionCall | - DotGet | IfExpr | FunctionDef | Assign | @@ -63,7 +62,7 @@ pipeOperand { } FunctionCallOrIdentifier { - Identifier + DotGet | Identifier } ambiguousFunctionCall { @@ -71,7 +70,7 @@ ambiguousFunctionCall { } FunctionCall { - Identifier arg+ + (DotGet | Identifier) arg+ } arg { diff --git a/src/parser/shrimp.terms.ts b/src/parser/shrimp.terms.ts index 75f88be..e7fce78 100644 --- a/src/parser/shrimp.terms.ts +++ b/src/parser/shrimp.terms.ts @@ -19,24 +19,24 @@ export const Program = 17, PipeExpr = 18, FunctionCall = 19, - PositionalArg = 20, - ParenExpr = 21, - FunctionCallOrIdentifier = 22, - BinOp = 23, - ConditionalOp = 24, - FunctionDef = 25, + DotGet = 20, + PositionalArg = 21, + ParenExpr = 22, + FunctionCallOrIdentifier = 23, + BinOp = 24, + ConditionalOp = 25, + FunctionDef = 26, keyword = 50, - Params = 27, - colon = 28, - String = 30, - StringFragment = 31, - Interpolation = 32, - EscapeSeq = 33, - Number = 34, - Boolean = 35, - Regex = 36, - Null = 37, - DotGet = 38, + Params = 28, + colon = 29, + String = 31, + StringFragment = 32, + Interpolation = 33, + EscapeSeq = 34, + Number = 35, + Boolean = 36, + Regex = 37, + Null = 38, Underscore = 39, NamedArg = 40, NamedArgPrefix = 41, diff --git a/src/parser/shrimp.ts b/src/parser/shrimp.ts index 1db89f9..3f0a131 100644 --- a/src/parser/shrimp.ts +++ b/src/parser/shrimp.ts @@ -4,24 +4,24 @@ import {operatorTokenizer} from "./operatorTokenizer" import {tokenizer} from "./tokenizer" import {trackScope} from "./scopeTracker" import {highlighting} from "./highlight" -const spec_Identifier = {__proto__:null,do:52, end:58, null:74, if:88, elseif:96, else:100} +const spec_Identifier = {__proto__:null,do:54, end:60, null:76, if:88, elseif:96, else:100} export const parser = LRParser.deserialize({ version: 14, - states: ".jQVQbOOO#XQcO'#CrO$RQRO'#CsO$aQcO'#DmO$xQbO'#DsOOQ`'#Cu'#CuO%QQbO'#CqO%rOSO'#CzOOQa'#Dq'#DqO&QOpO'#DSO&VQcO'#DpOOQ`'#Dn'#DnO&nQbO'#DmO&|QbO'#EQOOQ`'#DX'#DXO'kQRO'#DaOOQ`'#Dm'#DmO'pQQO'#DlOOQ`'#Dl'#DlOOQ`'#Db'#DbQVQbOOOOQa'#Dp'#DpOOQ`'#Cp'#CpO'xQbO'#DUOOQ`'#Do'#DoOOQ`'#Dc'#DcO(SQbO,59ZO&|QbO,59_O&|QbO,59_OOQ`'#Dd'#DdO(pQbO'#CwO(xQQO,5:_O)iQRO'#CsO)yQRO,59]O*[QRO,59]O*VQQO,59]O+VQQO,59]O+_QbO'#C|O+gQWO'#C}OOOO'#Dy'#DyOOOO'#Df'#DfO+{OSO,59fOOQa,59f,59fO,ZO`O,59nO,`QbO'#DgO,eQbO,59YO,vQRO,5:lO,}QQO,5:lO-SQbO,59{OOQ`,5:W,5:WOOQ`-E7`-E7`OOQ`,59p,59pOOQ`-E7a-E7aOOQa1G.y1G.yO-^QcO1G.yOOQ`-E7b-E7bO-xQbO1G/yO&|QbO,59`O&|QbO,59`OOQa1G.w1G.wOOOO,59h,59hOOOO,59i,59iOOOO-E7d-E7dOOQa1G/Q1G/QOOQa1G/Y1G/YO!QQbO'#CrOOQ`,5:R,5:ROOQ`-E7e-E7eO.VQbO1G0WOOQ`1G/g1G/gO.dQbO7+%eO.iQbO7+%fOOQO1G.z1G.zO.vQRO1G.zOOQ`'#DZ'#DZOOQ`7+%r7+%rO/QQbO7+%sOOQ`<lAN>lO&|QbO'#D]OOQ`'#Dh'#DhO0_QbOAN>yO0jQQO'#D_OOQ`AN>yAN>yO0oQbOAN>yO0tQRO,59wO0{QQO,59wOOQ`-E7f-E7fOOQ`G24eG24eO1QQbOG24eO1VQQO,59yO1[QQO1G/cOOQ`LD*PLD*PO.iQbO1G/eO/QQbO7+$}OOQ`7+%P7+%POOQ`<mAN>mO&|QbO'#D]OOQ`'#Dh'#DhO0bQbOAN>yO0mQQO'#D_OOQ`AN>yAN>yO0rQbOAN>yO0wQRO,59wO1OQQO,59wOOQ`-E7f-E7fOOQ`G24eG24eO1TQbOG24eO1YQQO,59yO1_QQO1G/cOOQ`LD*PLD*PO.lQbO1G/eO/TQbO7+$}OOQ`7+%P7+%POOQ`< spec_Identifier[value] || -1}], - tokenPrec: 753 + tokenPrec: 756 }) diff --git a/src/parser/tests/basics.test.ts b/src/parser/tests/basics.test.ts index 3299ff3..1158bb1 100644 --- a/src/parser/tests/basics.test.ts +++ b/src/parser/tests/basics.test.ts @@ -319,9 +319,10 @@ describe('DotGet whitespace sensitivity', () => { AssignableIdentifier basename Eq = Number 5 - DotGet - IdentifierBeforeDot basename - Identifier prop`) + FunctionCallOrIdentifier + DotGet + IdentifierBeforeDot basename + Identifier prop`) }) test('space before dot - NOT DotGet, parses as division', () => { diff --git a/src/parser/tests/dot-get.test.ts b/src/parser/tests/dot-get.test.ts index fd1aacb..f2461bc 100644 --- a/src/parser/tests/dot-get.test.ts +++ b/src/parser/tests/dot-get.test.ts @@ -20,9 +20,10 @@ describe('DotGet', () => { AssignableIdentifier obj Eq = Number 5 - DotGet - IdentifierBeforeDot obj - Identifier prop + FunctionCallOrIdentifier + DotGet + IdentifierBeforeDot obj + Identifier prop `) }) @@ -33,9 +34,10 @@ describe('DotGet', () => { Params AssignableIdentifier config colon : - DotGet - IdentifierBeforeDot config - Identifier path + FunctionCallOrIdentifier + DotGet + IdentifierBeforeDot config + Identifier path keyword end `) }) @@ -47,9 +49,10 @@ describe('DotGet', () => { Params AssignableIdentifier x colon : - DotGet - IdentifierBeforeDot x - Identifier prop + FunctionCallOrIdentifier + DotGet + IdentifierBeforeDot x + Identifier prop keyword end Word x.prop `) @@ -66,12 +69,14 @@ end`).toMatchTree(` AssignableIdentifier x AssignableIdentifier y colon : - DotGet - IdentifierBeforeDot x - Identifier foo - DotGet - IdentifierBeforeDot y - Identifier bar + FunctionCallOrIdentifier + DotGet + IdentifierBeforeDot x + Identifier foo + FunctionCallOrIdentifier + DotGet + IdentifierBeforeDot y + Identifier bar keyword end `) }) @@ -86,17 +91,19 @@ end`).toMatchTree(` Params AssignableIdentifier x colon : - DotGet - IdentifierBeforeDot x - Identifier outer + FunctionCallOrIdentifier + DotGet + IdentifierBeforeDot x + Identifier outer FunctionDef keyword do Params AssignableIdentifier y colon : - DotGet - IdentifierBeforeDot y - Identifier inner + FunctionCallOrIdentifier + DotGet + IdentifierBeforeDot y + Identifier inner keyword end keyword end `) @@ -117,6 +124,62 @@ end`).toMatchTree(` `) }) + 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