From fd3c5da59bc5ea3439fbd519e40523f29fbc0f87 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath <2+defunkt@users.noreply.github.com> Date: Fri, 7 Nov 2025 15:16:59 -0800 Subject: [PATCH 1/2] coerce values to string in str prelude functions --- src/prelude/str.ts | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/prelude/str.ts b/src/prelude/str.ts index 5aede56..ec688ae 100644 --- a/src/prelude/str.ts +++ b/src/prelude/str.ts @@ -1,37 +1,37 @@ // strings export const str = { join: (arr: string[], sep: string = ',') => arr.join(sep), - split: (str: string, sep: string = ',') => str.split(sep), - 'to-upper': (str: string) => str.toUpperCase(), - 'to-lower': (str: string) => str.toLowerCase(), - trim: (str: string) => str.trim(), + split: (str: string, sep: string = ',') => String(str ?? '').split(sep), + 'to-upper': (str: string) => String(str ?? '').toUpperCase(), + 'to-lower': (str: string) => String(str ?? '').toLowerCase(), + trim: (str: string) => String(str ?? '').trim(), // predicates - 'starts-with?': (str: string, prefix: string) => str.startsWith(prefix), - 'ends-with?': (str: string, suffix: string) => str.endsWith(suffix), - 'contains?': (str: string, substr: string) => str.includes(substr), - 'empty?': (str: string) => str.length === 0, + 'starts-with?': (str: string, prefix: string) => String(str ?? '').startsWith(prefix), + 'ends-with?': (str: string, suffix: string) => String(str ?? '').endsWith(suffix), + 'contains?': (str: string, substr: string) => String(str ?? '').includes(substr), + 'empty?': (str: string) => String(str ?? '').length === 0, // inspection - 'index-of': (str: string, search: string) => str.indexOf(search), - 'last-index-of': (str: string, search: string) => str.lastIndexOf(search), + 'index-of': (str: string, search: string) => String(str ?? '').indexOf(search), + 'last-index-of': (str: string, search: string) => String(str ?? '').lastIndexOf(search), // transformations - replace: (str: string, search: string, replacement: string) => str.replace(search, replacement), - 'replace-all': (str: string, search: string, replacement: string) => str.replaceAll(search, replacement), - slice: (str: string, start: number, end?: number | null) => str.slice(start, end ?? undefined), - substring: (str: string, start: number, end?: number | null) => str.substring(start, end ?? undefined), + replace: (str: string, search: string, replacement: string) => String(str ?? '').replace(search, replacement), + 'replace-all': (str: string, search: string, replacement: string) => String(str ?? '').replaceAll(search, replacement), + slice: (str: string, start: number, end?: number | null) => String(str ?? '').slice(start, end ?? undefined), + substring: (str: string, start: number, end?: number | null) => String(str ?? '').substring(start, end ?? undefined), repeat: (str: string, count: number) => { if (count < 0) throw new Error(`repeat: count must be non-negative, got ${count}`) if (!Number.isInteger(count)) throw new Error(`repeat: count must be an integer, got ${count}`) - return str.repeat(count) + return String(str ?? '').repeat(count) }, - 'pad-start': (str: string, length: number, pad: string = ' ') => str.padStart(length, pad), - 'pad-end': (str: string, length: number, pad: string = ' ') => str.padEnd(length, pad), - lines: (str: string) => str.split('\n'), - chars: (str: string) => str.split(''), + 'pad-start': (str: string, length: number, pad: string = ' ') => String(str ?? '').padStart(length, pad), + 'pad-end': (str: string, length: number, pad: string = ' ') => String(str ?? '').padEnd(length, pad), + lines: (str: string) => String(str ?? '').split('\n'), + chars: (str: string) => String(str ?? '').split(''), // regex - match: (str: string, regex: RegExp) => str.match(regex), - 'test?': (str: string, regex: RegExp) => regex.test(str), + match: (str: string, regex: RegExp) => String(str ?? '').match(regex), + 'test?': (str: string, regex: RegExp) => regex.test(String(str ?? '')), } \ No newline at end of file -- 2.50.1 From 890eb811b96c181da591c4ed96fb13d11e986053 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath <2+defunkt@users.noreply.github.com> Date: Fri, 7 Nov 2025 15:17:23 -0800 Subject: [PATCH 2/2] pipe atoms/literals to functions --- src/compiler/tests/pipe.test.ts | 26 +++++++++++ src/parser/shrimp.grammar | 6 +-- src/parser/shrimp.terms.ts | 62 +++++++++++++------------- src/parser/shrimp.ts | 16 +++---- src/parser/tests/pipes.test.ts | 77 +++++++++++++++++++++++++++++++++ 5 files changed, 145 insertions(+), 42 deletions(-) diff --git a/src/compiler/tests/pipe.test.ts b/src/compiler/tests/pipe.test.ts index 06d56c3..b8f92f1 100644 --- a/src/compiler/tests/pipe.test.ts +++ b/src/compiler/tests/pipe.test.ts @@ -92,4 +92,30 @@ describe('pipe expressions', () => { get-msg | length `).toEvaluateTo(5) }) + + + test('string literals can be piped', () => { + expect(`'hey there' | str.to-upper`).toEvaluateTo('HEY THERE') + }) + + test('number literals can be piped', () => { + expect(`42 | str.trim`).toEvaluateTo('42') + expect(`4.22 | str.trim`).toEvaluateTo('4.22') + }) + + test('null literals can be piped', () => { + expect(`null | type`).toEvaluateTo('null') + }) + + test('boolean literals can be piped', () => { + expect(`true | str.to-upper`).toEvaluateTo('TRUE') + }) + + test('array literals can be piped', () => { + expect(`[1 2 3] | str.join '-'`).toEvaluateTo('1-2-3') + }) + + test('dict literals can be piped', () => { + expect(`[a=1 b=2 c=3] | dict.values | list.sort | str.join '-'`).toEvaluateTo('1-2-3') + }) }) diff --git a/src/parser/shrimp.grammar b/src/parser/shrimp.grammar index ac38814..97908d9 100644 --- a/src/parser/shrimp.grammar +++ b/src/parser/shrimp.grammar @@ -50,7 +50,7 @@ null { @specialize[@name=Null] } call } -item { +item { consumeToTerminator newlineOrSemicolon | consumeToTerminator eof | newlineOrSemicolon // allow blank lines @@ -77,7 +77,7 @@ PipeExpr { } pipeOperand { - FunctionCall | FunctionCallOrIdentifier + consumeToTerminator } WhileExpr { @@ -235,7 +235,7 @@ Array { // We need expressionWithoutIdentifier to avoid conflicts in consumeToTerminator. // Without this, when parsing "my-var" at statement level, the parser can't decide: // - ambiguousFunctionCall → FunctionCallOrIdentifier → Identifier -// - expression → Identifier +// - expression → Identifier // Both want the same Identifier token! So we use expressionWithoutIdentifier // to remove Identifier from the second path, forcing standalone identifiers // to go through ambiguousFunctionCall (which is what we want semantically). diff --git a/src/parser/shrimp.terms.ts b/src/parser/shrimp.terms.ts index 716ef1c..04cb710 100644 --- a/src/parser/shrimp.terms.ts +++ b/src/parser/shrimp.terms.ts @@ -27,38 +27,38 @@ export const Comment = 25, Program = 26, PipeExpr = 27, - FunctionCall = 28, - DotGet = 29, - Number = 30, - ParenExpr = 31, - IfExpr = 32, + WhileExpr = 29, keyword = 70, - ConditionalOp = 34, - String = 35, - StringFragment = 36, - Interpolation = 37, - EscapeSeq = 38, - Boolean = 39, - Regex = 40, - Dict = 41, - NamedArg = 42, - NamedArgPrefix = 43, - FunctionDef = 44, - Params = 45, - NamedParam = 46, - Null = 47, - colon = 48, - CatchExpr = 49, - Block = 51, - FinallyExpr = 52, - Underscore = 55, - Array = 56, - ElseIfExpr = 57, - ElseExpr = 59, - FunctionCallOrIdentifier = 60, - BinOp = 61, - PositionalArg = 62, - WhileExpr = 64, + ConditionalOp = 31, + ParenExpr = 32, + IfExpr = 33, + FunctionCall = 35, + DotGet = 36, + Number = 37, + PositionalArg = 38, + FunctionDef = 39, + Params = 40, + NamedParam = 41, + NamedArgPrefix = 42, + String = 43, + StringFragment = 44, + Interpolation = 45, + EscapeSeq = 46, + Boolean = 47, + Null = 48, + colon = 49, + CatchExpr = 50, + Block = 52, + FinallyExpr = 53, + Underscore = 56, + NamedArg = 57, + ElseIfExpr = 58, + ElseExpr = 60, + FunctionCallOrIdentifier = 61, + BinOp = 62, + Regex = 63, + Dict = 64, + Array = 65, FunctionCallWithBlock = 66, TryExpr = 67, Throw = 69, diff --git a/src/parser/shrimp.ts b/src/parser/shrimp.ts index ffb958e..fa41719 100644 --- a/src/parser/shrimp.ts +++ b/src/parser/shrimp.ts @@ -4,23 +4,23 @@ import {operatorTokenizer} from "./operatorTokenizer" import {tokenizer, specializeKeyword} from "./tokenizer" import {trackScope} from "./parserScopeContext" import {highlighting} from "./highlight" -const spec_Identifier = {__proto__:null,if:66, null:94, catch:100, finally:106, end:108, else:116, while:130, try:136, throw:140} +const spec_Identifier = {__proto__:null,while:60, if:68, null:96, catch:102, finally:108, end:110, else:118, try:136, throw:140} export const parser = LRParser.deserialize({ version: 14, - states: "9[QYQbOOO!dOSO'#DPOOQa'#DV'#DVO#mQbO'#DfO%RQcO'#E^OOQa'#E^'#E^O&XQcO'#E^O'ZQcO'#E]O'qQcO'#E]O)^QRO'#DOO*mQcO'#EWO*wQcO'#EWO+XQbO'#C{O,SOpO'#CyOOQ`'#EX'#EXO,XQbO'#EWO,cQRO'#DuOOQ`'#EW'#EWO,wQQO'#EVOOQ`'#EV'#EVOOQ`'#Dw'#DwQYQbOOO-PQbO'#DYO-[QbO'#C|O.PQbO'#DnO.tQQO'#DqO.PQbO'#DsO.yQbO'#DRO/RQWO'#DSOOOO'#E`'#E`OOOO'#Dx'#DxO/gOSO,59kOOQa,59k,59kOOQ`'#Dy'#DyO/uQbO,5:QO/|QbO'#DWO0WQQO,59qOOQa,5:Q,5:QO0cQbO,5:QOOQa'#E]'#E]OOQ`'#Dl'#DlOOQ`'#El'#ElOOQ`'#EQ'#EQO0mQbO,59dO1gQbO,5:bO.PQbO,59jO.PQbO,59jO.PQbO,59jO.PQbO,5:VO.PQbO,5:VO.PQbO,5:VO1wQRO,59gO2OQRO,59gO2ZQRO,59gO2UQQO,59gO2lQQO,59gO2tObO,59eO3PQbO'#ERO3[QbO,59cO3vQbO,5:[O1gQbO,5:aOOQ`,5:q,5:qOOQ`-E7u-E7uOOQ`'#Dz'#DzO4ZQbO'#DZO4fQbO'#D[OOQO'#D{'#D{O4^QQO'#DZO4tQQO,59tO4yQcO'#E]O6_QRO'#E[O6fQRO'#E[OOQO'#E['#E[O6qQQO,59hO6vQRO,5:YO6}QRO,5:YO3vQbO,5:]O7YQcO,5:_O8UQcO,5:_O8`QcO,5:_OOOO,59m,59mOOOO,59n,59nOOOO-E7v-E7vOOQa1G/V1G/VOOQ`-E7w-E7wO8pQQO1G/]OOQa1G/l1G/lO8{QbO1G/lOOQ`,59r,59rOOQO'#D}'#D}O8pQQO1G/]OOQa1G/]1G/]OOQ`'#EO'#EOO8{QbO1G/lOOQ`-E8O-E8OOOQ`1G/|1G/|OOQa1G/U1G/UO:WQcO1G/UO:_QcO1G/UO:fQcO1G/UOOQa1G/q1G/qO;_QcO1G/qO;iQcO1G/qO;sQcO1G/qOOQa1G/R1G/ROOQa1G/P1G/PO]QbO1G/vOOQ`1G/{1G/{OOQ`-E7x-E7xO>hQQO,59uOOQO,59v,59vOOQO-E7y-E7yO>pQbO1G/`O3vQbO1G/SO3vQbO1G/tO?TQbO1G/wO?`QQO7+$wOOQa7+$w7+$wO?kQbO7+%WOOQa7+%W7+%WOOQO-E7{-E7{OOQ`-E7|-E7|OOQ`'#D|'#D|O?uQQO'#D|O?zQbO'#EiOOQ`,59{,59{O@kQbO'#D_O@pQQO'#DbOOQ`7+%b7+%bO@uQbO7+%bO@zQbO7+%bOASQbO7+$zOA_QbO7+$zOA{QbO7+$nOBTQbO7+%`OOQ`7+%c7+%cOBYQbO7+%cOB_QbO7+%cOOQa<hAN>hOOQ`AN>QAN>QOCuQbOAN>QOCzQbOAN>QOOQ`-E7}-E7}OOQ`AN=tAN=tODSQbOAN=tO-[QbO,5:RO3vQbO,5:TOOQ`AN>iAN>iOOQ`7+%P7+%POOQ`G23lG23lODXQbOG23lPD^QbO'#DgOOQ`G23`G23`ODcQQO1G/mOOQ`1G/o1G/oOOQ`LD)WLD)WO3vQbO7+%XOOQ`<VQbO'#DbO>hQbO'#DbO>{QbO1G/vOOQ`-E7v-E7vOOQ`,5:d,5:dOOQ`-E7x-E7xO?WQQO,59pOOQO,59q,59qOOQO-E7y-E7yO?`QbO1G/ZO4]QbO1G/TO4]QbO1G/PO?gQbO1G/wO?rQQO7+%`OOQa7+%`7+%`O?}QbO7+%aOOQa7+%a7+%aOOQO-E8O-E8OOOQ`-E8P-E8POOQ`'#D}'#D}O@XQQO'#D}O@aQbO'#EgOOQ`,59|,59|O@tQbO'#D`O@yQQO'#DcOOQ`7+%b7+%bOAOQbO7+%bOATQbO7+%bOA]QbO7+$uOAkQbO7+$uOA{QbO7+$oOBTQbO7+$kOOQ`7+%c7+%cOBYQbO7+%cOB_QbO7+%cOOQa<hAN>hOOQ`AN={AN={OCuQbOAN={OCzQbOAN={OOQ`-E7|-E7|OOQ`AN=uAN=uODSQbOAN=uO.WQbO,5:SO4]QbO,5:UOOQ`AN>iAN>iOOQ`7+%Q7+%QOOQ`G23gG23gODXQbOG23gPD^QbO'#DhOOQ`G23aG23aODcQQO1G/nOOQ`1G/p1G/pOOQ`LD)RLD)RO4]QbO7+%YOOQ`<c#Y#o,w#o;'S#{;'S;=`$d<%lO#{U>j[wQtSOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#o,w#o;'S#{;'S;=`$d<%lO#{^?g[#VWtSOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#o,w#o;'S#{;'S;=`$d<%lO#{^@d[#XWtSOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#o,w#o;'S#{;'S;=`$d<%lO#{^Aa^#WWtSOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#f,w#f#gB]#g#o,w#o;'S#{;'S;=`$d<%lO#{UBb^tSOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#i,w#i#j=b#j#o,w#o;'S#{;'S;=`$d<%lO#{UCeU!aQtSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~C|O#a~", - tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO!|~~", 11)], + tokenData: "C|~R|OX#{XY$jYZ%TZp#{pq$jqs#{st%ntu'tuw#{wx'yxy(Oyz(iz{#{{|)S|}#{}!O+v!O!P#{!P!Q.]!Q![)q![!]6x!]!^%T!^!}#{!}#O7c#O#P9X#P#Q9^#Q#R#{#R#S9w#S#T#{#T#Y,w#Y#Z:b#Z#b,w#b#c?`#c#f,w#f#g@]#g#h,w#h#iAY#i#o,w#o#p#{#p#qC^#q;'S#{;'S;=`$d<%l~#{~O#{~~CwS$QU|SOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{^$qU|S!xYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[U|S#YQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%sW|SOp#{pq&]qt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^&dZiY|SOY&]YZ#{Zt&]tu'Vuw&]wx'Vx#O&]#O#P'V#P;'S&];'S;=`'n<%lO&]Y'[SiYOY'VZ;'S'V;'S;=`'h<%lO'VY'kP;=`<%l'V^'qP;=`<%l&]~'yO#T~~(OO#R~U(VU|S!}QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(pU|S#]QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U)XW|SOt#{uw#{x!Q#{!Q![)q![#O#{#P;'S#{;'S;=`$d<%lO#{U)xY|SuQOt#{uw#{x!O#{!O!P*h!P!Q#{!Q![)q![#O#{#P;'S#{;'S;=`$d<%lO#{U*mW|SOt#{uw#{x!Q#{!Q![+V![#O#{#P;'S#{;'S;=`$d<%lO#{U+^W|SuQOt#{uw#{x!Q#{!Q![+V![#O#{#P;'S#{;'S;=`$d<%lO#{U+{^|SOt#{uw#{x}#{}!O,w!O!Q#{!Q![)q![!_#{!_!`-r!`#O#{#P#T#{#T#o,w#o;'S#{;'S;=`$d<%lO#{U,|[|SOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#o,w#o;'S#{;'S;=`$d<%lO#{U-yUzQ|SOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U.bW|SOt#{uw#{x!P#{!P!Q.z!Q#O#{#P;'S#{;'S;=`$d<%lO#{U/P^|SOY/{YZ#{Zt/{tu1Ouw/{wx1Ox!P/{!P!Q#{!Q!}/{!}#O5q#O#P3^#P;'S/{;'S;=`6r<%lO/{U0S^|S!aQOY/{YZ#{Zt/{tu1Ouw/{wx1Ox!P/{!P!Q3s!Q!}/{!}#O5q#O#P3^#P;'S/{;'S;=`6r<%lO/{Q1TX!aQOY1OZ!P1O!P!Q1p!Q!}1O!}#O2_#O#P3^#P;'S1O;'S;=`3m<%lO1OQ1sP!P!Q1vQ1{U!aQ#Z#[1v#]#^1v#a#b1v#g#h1v#i#j1v#m#n1vQ2bVOY2_Z#O2_#O#P2w#P#Q1O#Q;'S2_;'S;=`3W<%lO2_Q2zSOY2_Z;'S2_;'S;=`3W<%lO2_Q3ZP;=`<%l2_Q3aSOY1OZ;'S1O;'S;=`3m<%lO1OQ3pP;=`<%l1OU3xW|SOt#{uw#{x!P#{!P!Q4b!Q#O#{#P;'S#{;'S;=`$d<%lO#{U4ib|S!aQOt#{uw#{x#O#{#P#Z#{#Z#[4b#[#]#{#]#^4b#^#a#{#a#b4b#b#g#{#g#h4b#h#i#{#i#j4b#j#m#{#m#n4b#n;'S#{;'S;=`$d<%lO#{U5v[|SOY5qYZ#{Zt5qtu2_uw5qwx2_x#O5q#O#P2w#P#Q/{#Q;'S5q;'S;=`6l<%lO5qU6oP;=`<%l5qU6uP;=`<%l/{U7PU|S!RQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U7jW#_Q|SOt#{uw#{x!_#{!_!`8S!`#O#{#P;'S#{;'S;=`$d<%lO#{U8XV|SOt#{uw#{x#O#{#P#Q8n#Q;'S#{;'S;=`$d<%lO#{U8uU#^Q|SOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~9^O#U~U9eU#`Q|SOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U:OU|S!YQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U:g]|SOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#U;`#U#o,w#o;'S#{;'S;=`$d<%lO#{U;e^|SOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#`,w#`#ac#Y#o,w#o;'S#{;'S;=`$d<%lO#{U>j[!PQ|SOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#o,w#o;'S#{;'S;=`$d<%lO#{^?g[#VW|SOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#o,w#o;'S#{;'S;=`$d<%lO#{^@d[#XW|SOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#o,w#o;'S#{;'S;=`$d<%lO#{^Aa^#WW|SOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#f,w#f#gB]#g#o,w#o;'S#{;'S;=`$d<%lO#{UBb^|SOt#{uw#{x}#{}!O,w!O!_#{!_!`-r!`#O#{#P#T#{#T#i,w#i#j=b#j#o,w#o;'S#{;'S;=`$d<%lO#{UCeUlQ|SOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~C|O#a~", + tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO#P~~", 11)], topRules: {"Program":[0,26]}, specialized: [{term: 20, get: (value: any, stack: any) => (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 20, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}], tokenPrec: 1634 diff --git a/src/parser/tests/pipes.test.ts b/src/parser/tests/pipes.test.ts index 9e87114..0d5da6a 100644 --- a/src/parser/tests/pipes.test.ts +++ b/src/parser/tests/pipes.test.ts @@ -98,4 +98,81 @@ describe('pipe expressions', () => { Identifier double `) }) + + test('string literals can be piped', () => { + expect(`'hey there' | echo`).toMatchTree(` + PipeExpr + String + StringFragment hey there + operator | + FunctionCallOrIdentifier + Identifier echo + `) + }) + + test('number literals can be piped', () => { + expect(`42 | echo`).toMatchTree(` + PipeExpr + Number 42 + operator | + FunctionCallOrIdentifier + Identifier echo`) + + expect(`4.22 | echo`).toMatchTree(` + PipeExpr + Number 4.22 + operator | + FunctionCallOrIdentifier + Identifier echo`) + }) + + test('null literals can be piped', () => { + expect(`null | echo`).toMatchTree(` + PipeExpr + Null null + operator | + FunctionCallOrIdentifier + Identifier echo`) + }) + + test('boolean literals can be piped', () => { + expect(`true | echo`).toMatchTree(` + PipeExpr + Boolean true + operator | + FunctionCallOrIdentifier + Identifier echo`) + }) + + test('array literals can be piped', () => { + expect(`[1 2 3] | echo`).toMatchTree(` + PipeExpr + Array + Number 1 + Number 2 + Number 3 + operator | + FunctionCallOrIdentifier + Identifier echo + `) + }) + + test('dict literals can be piped', () => { + expect(`[a=1 b=2 c=3] | echo`).toMatchTree(` + PipeExpr + Dict + NamedArg + NamedArgPrefix a= + Number 1 + NamedArg + NamedArgPrefix b= + Number 2 + NamedArg + NamedArgPrefix c= + Number 3 + operator | + FunctionCallOrIdentifier + Identifier echo + `) + }) }) -- 2.50.1