diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index 11105cd..7a6e005 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -581,12 +581,14 @@ export class Compiler { ) } - case terms.Throw: { + case terms.Throw: + case terms.Not: { + const keyword = node.type.id === terms.Throw ? 'Throw' : 'Not' const children = getAllChildren(node) const [_throwKeyword, expression] = children if (!expression) { throw new CompilerError( - `Throw expected expression, got ${children.length} children`, + `${keyword} expected expression, got ${children.length} children`, node.from, node.to ) @@ -594,7 +596,7 @@ export class Compiler { const instructions: ProgramItem[] = [] instructions.push(...this.#compileNode(expression, input)) - instructions.push(['THROW']) + instructions.push([keyword.toUpperCase()]) // THROW or NOT return instructions } diff --git a/src/parser/node.ts b/src/parser/node.ts index 1bea752..8816fe4 100644 --- a/src/parser/node.ts +++ b/src/parser/node.ts @@ -51,6 +51,7 @@ export type NodeType = | 'FinallyExpr' | 'Throw' + | 'Not' | 'Eq' | 'Modulo' | 'Plus' @@ -275,6 +276,9 @@ class SyntaxNodeType { case 'Throw': return term.Throw + case 'Not': + return term.Not + case 'Eq': return term.Eq diff --git a/src/parser/parser2.ts b/src/parser/parser2.ts index 57ea905..e96fa42 100644 --- a/src/parser/parser2.ts +++ b/src/parser/parser2.ts @@ -216,6 +216,9 @@ export class Parser { if (this.is($T.Keyword, 'throw')) return this.throw() + if (this.is($T.Keyword, 'not')) + return this.not() + if (this.is($T.Keyword, 'import')) return this.import() @@ -307,7 +310,7 @@ export class Parser { break } - // [ a = true ] + // [ a = true ] const next = this.peek(peek) if (next?.type === $T.Operator && next.value === '=') { isDict = true @@ -760,6 +763,14 @@ export class Parser { return node.push(prefix, val) } + // not blah + not(): SyntaxNode { + const keyword = this.keyword('not') + const val = this.expression() + const node = new SyntaxNode('Not', keyword.from, val.to) + return node.push(keyword, val) + } + // operators like + - = op(op?: string): SyntaxNode { const token = op ? this.expect($T.Operator, op) : this.expect($T.Operator) diff --git a/src/parser/shrimp.grammar b/src/parser/shrimp.grammar index 4f2e032..e7cd9ee 100644 --- a/src/parser/shrimp.grammar +++ b/src/parser/shrimp.grammar @@ -44,6 +44,7 @@ try { @specialize[@name=keyword] } catch { @specialize[@name=keyword] } finally { @specialize[@name=keyword] } throw { @specialize[@name=keyword] } +not { @specialize[@name=keyword] } import { @specialize[@name=keyword] } null { @specialize[@name=Null] } @@ -77,6 +78,7 @@ consumeToTerminator { ambiguousFunctionCall | TryExpr | Throw | + Not | Import | IfExpr | FunctionDef | @@ -167,8 +169,12 @@ Throw { throw (BinOp | ConditionalOp | expression) } +Not { + not (BinOp | ConditionalOp | expression) +} + // this has to be in the parse tree so the scope tracker can use it -Import { +Import { import NamedArg* Identifier+ NamedArg* } @@ -247,7 +253,7 @@ expression { Dollar dot (DotGet | Number | Identifier | ParenExpr) } - String { + String { "'" stringContent* "'" | CurlyString | DoubleQuote } } diff --git a/src/parser/shrimp.terms.ts b/src/parser/shrimp.terms.ts index cc00537..c0c8482 100644 --- a/src/parser/shrimp.terms.ts +++ b/src/parser/shrimp.terms.ts @@ -32,14 +32,14 @@ export const Word = 30, IdentifierBeforeDot = 31, CurlyString = 32, - newline = 101, - pipeStartsLine = 102, + newline = 103, + pipeStartsLine = 104, Do = 33, Comment = 34, Program = 35, PipeExpr = 36, WhileExpr = 38, - keyword = 84, + keyword = 86, ConditionalOp = 40, ParenExpr = 41, FunctionCallWithNewlines = 42, @@ -76,6 +76,7 @@ export const FunctionCallWithBlock = 78, TryExpr = 79, Throw = 81, - Import = 83, - CompoundAssign = 85, - Assign = 86 + Not = 83, + Import = 85, + CompoundAssign = 87, + Assign = 88 diff --git a/src/parser/shrimp.ts b/src/parser/shrimp.ts index 7064a86..d99da52 100644 --- a/src/parser/shrimp.ts +++ b/src/parser/shrimp.ts @@ -4,14 +4,14 @@ import {operatorTokenizer} from "./operatorTokenizer" import {tokenizer, pipeStartsLineTokenizer, specializeKeyword} from "./tokenizer" import {trackScope} from "./parserScopeContext" import {highlighting} from "./highlight" -const spec_Identifier = {__proto__:null,while:78, null:116, catch:122, finally:128, end:130, if:138, else:144, try:160, throw:164, import:168} +const spec_Identifier = {__proto__:null,while:78, null:116, catch:122, finally:128, end:130, if:138, else:144, try:160, throw:164, not:168, import:172} export const parser = LRParser.deserialize({ version: 14, - states: "?[QYQ!SOOOOQ!Q'#Ek'#EkO!sO!bO'#DXO%kQ!TO'#DdO&UOSO'#DaOOQ!R'#Da'#DaO)SQ!TO'#EnOOQ!Q'#E{'#E{O)pQRO'#DxO+xQ!TO'#EjO,fQ!SO'#DVOOQ!R'#Dz'#DzO/WQ!SO'#D{OOQ!R'#En'#EnO/_Q!TO'#EnO1cQ!TO'#EmO2qQ!TO'#EjO3OQRO'#ETOOQ!Q'#Ej'#EjO3gQ!SO'#EjO3nQrO'#EiOOQ!Q'#Ei'#EiOOQ!Q'#EV'#EVQYQ!SOOO4PQbO'#D]O4[QbO'#DrO5YQbO'#DSO6WQQO'#D}O5YQbO'#EPO6]QbO'#ERO6eObO,59sOOQ!Q'#D['#D[O6vQbO'#DqOOQ!Q'#Eq'#EqOOQ!Q'#E_'#E_O7QQ!SO,5:`OOQ!R'#Em'#EmO8QQbO'#DcO8`QWO'#DeOOOO'#Es'#EsOOOO'#E['#E[O8tOSO,59{OOQ!R,59{,59{O5YQbO,5:dO5YQbO,5:dO5YQbO,5:dO5YQbO,5:dO5YQbO,59pO5YQbO,59pO5YQbO,59pO5YQbO,59pOOQ!Q'#EX'#EXO,fQ!SO,59qO9SQ!TO'#DdO9^Q!TO'#EnO9hQsO,59qO9uQQO,59qO9zQrO,59qO:VQrO,59qO:eQsO,59qO;TQsO,59qO;[QrO'#DQO;dQ!SO,5:gO;kQrO,5:fOOQ!R,5:g,5:gO;yQ!SO,5:gO]QQO'#EWOOQ!Q-E8T-E8TOOQ!Q'#EY'#EYO>bQbO'#D^O>mQbO'#D_OOQO'#EZ'#EZO>eQQO'#D^O?RQQO,59wO?WQcO'#EmO@TQRO'#EzOAQQRO'#EzOOQO'#Ez'#EzOAXQQO,5:^OA^QRO,59nOAeQRO,59nOYQ!SO,5:iOAsQ!TO,5:kOCXQ!TO,5:kOC{Q!TO,5:kODYQ!SO,5:mOOQ!Q'#Ec'#EcO6]QbO,5:mOOQ!R1G/_1G/_OOQ!Q,5:],5:]OOQ!Q-E8]-E8]OOOO'#Dd'#DdOOOO,59},59}OOOO,5:P,5:POOOO-E8Y-E8YOOQ!R1G/g1G/gOOQ!R1G0O1G0OOF_Q!TO1G0OOFiQ!TO1G0OOG}Q!TO1G0OOHXQ!TO1G0OOHfQ!TO1G0OOOQ!R1G/[1G/[OI}Q!TO1G/[OJUQ!TO1G/[OJ]Q!TO1G/[OKbQ!TO1G/[OJdQ!TO1G/[OOQ!Q-E8V-E8VOKxQsO1G/]OLVQQO1G/]OL[QrO1G/]OLgQrO1G/]OLuQsO1G/]OL|QsO1G/]OMTQ!SO,59rOM_QrO1G/]OOQ!R1G/]1G/]OMjQrO1G0QOOQ!R1G0R1G0ROMxQ!SO1G0ROOQp'#Ea'#EaOMjQrO1G0QOOQ!R1G0Q1G0QOOQ!Q'#Eb'#EbOMxQ!SO1G0RONVQ!SO1G0[ONwQ!SO1G0ZO! iQ!SO'#DlO! }Q!SO'#DlO!!_QbO1G0SOOQ!Q-E8U-E8UOYQ!SO,5:rOOQ!Q,5:r,5:rOYQ!SO,5:rOOQ!Q-E8W-E8WO!!jQQO,59xOOQO,59y,59yOOQO-E8X-E8XOYQ!SO1G/cOYQ!SO1G/xOYQ!SO1G/YO!!rQbO1G0TO!!}Q!SO1G0XO!#rQ!SO1G0XOOQ!Q-E8a-E8aO!#yQrO7+$wOOQ!R7+$w7+$wO!$UQrO1G/^O!$aQrO7+%lOOQ!R7+%l7+%lO!$oQ!SO7+%mOOQ!R7+%m7+%mOOQp-E8_-E8_OOQ!Q-E8`-E8`OOQ!Q'#E]'#E]O!$|QrO'#E]O!%[Q!SO'#EyOOQ`,5:W,5:WO!%lQbO'#DjO!%qQQO'#DmOOQ!Q7+%n7+%nO!%vQbO7+%nO!%{QbO7+%nOOQ!Q1G0^1G0^OYQ!SO1G0^O!&TQ!SO7+$}O!&fQ!SO7+$}O!&sQbO7+%dO!&{QbO7+$tOOQ!Q7+%o7+%oO!'QQbO7+%oO!'VQbO7+%oO!'_Q!SO7+%sOOQ!R<tAN>tOOQ!QAN>TAN>TO!*XQbOAN>TO!*^QbOAN>TOOQ`-E8^-E8^OOQ!QAN>jAN>jO!*fQbOAN>jO4[QbO,5:aOYQ!SO,5:cOOQ!QAN>uAN>uPMTQ!SO'#EXOOQ`7+%[7+%[OOQ!QG23oG23oO!*kQbOG23oP!)kQbO'#DuOOQ!QG24UG24UO!*pQQO1G/{OOQ`1G/}1G/}OOQ!QLD)ZLD)ZOYQ!SO7+%gOOQ`<fQQO'#EYOOQ!Q-E8V-E8VOOQ!Q'#E['#E[O>kQbO'#D^O>vQbO'#D_OOQO'#E]'#E]O>nQQO'#D^O?[QQO,59wO?aQcO'#EoO@^QRO'#E|OAZQRO'#E|OOQO'#E|'#E|OAbQQO,5:^OAgQRO,59nOAnQRO,59nOYQ!SO,5:iOA|Q!TO,5:kOCbQ!TO,5:kODUQ!TO,5:kODcQ!TO,5:mOEwQ!TO,5:mOFkQ!TO,5:mOFxQ!SO,5:oOOQ!Q'#Ee'#EeO6cQbO,5:oOOQ!R1G/_1G/_OOQ!Q,5:],5:]OOQ!Q-E8_-E8_OOOO'#Dd'#DdOOOO,59},59}OOOO,5:P,5:POOOO-E8[-E8[OOQ!R1G/g1G/gOOQ!R1G0O1G0OOH}Q!TO1G0OOIXQ!TO1G0OOJmQ!TO1G0OOJwQ!TO1G0OOKUQ!TO1G0OOOQ!R1G/[1G/[OLmQ!TO1G/[OLtQ!TO1G/[OL{Q!TO1G/[ONQQ!TO1G/[OMSQ!TO1G/[OOQ!Q-E8X-E8XONhQsO1G/]ONuQQO1G/]ONzQrO1G/]O! VQrO1G/]O! eQsO1G/]O! lQsO1G/]O! sQ!SO,59rO! }QrO1G/]OOQ!R1G/]1G/]O!!YQrO1G0QOOQ!R1G0R1G0RO!!hQ!SO1G0ROOQp'#Ec'#EcO!!YQrO1G0QOOQ!R1G0Q1G0QOOQ!Q'#Ed'#EdO!!hQ!SO1G0RO!!uQ!SO1G0^O!#gQ!SO1G0]O!$XQ!SO'#DlO!$mQ!SO'#DlO!$}QbO1G0SOOQ!Q-E8W-E8WOYQ!SO,5:tOOQ!Q,5:t,5:tOYQ!SO,5:tOOQ!Q-E8Y-E8YO!%YQQO,59xOOQO,59y,59yOOQO-E8Z-E8ZOYQ!SO1G/cOYQ!SO1G/xOYQ!SO1G/YO!%bQbO1G0TO!%mQ!SO1G0ZO!&bQ!SO1G0ZOOQ!Q-E8c-E8cO!&iQrO7+$wOOQ!R7+$w7+$wO!&tQrO1G/^O!'PQrO7+%lOOQ!R7+%l7+%lO!'_Q!SO7+%mOOQ!R7+%m7+%mOOQp-E8a-E8aOOQ!Q-E8b-E8bOOQ!Q'#E_'#E_O!'lQrO'#E_O!'zQ!SO'#E{OOQ`,5:W,5:WO!([QbO'#DjO!(aQQO'#DmOOQ!Q7+%n7+%nO!(fQbO7+%nO!(kQbO7+%nOOQ!Q1G0`1G0`OYQ!SO1G0`O!(sQ!SO7+$}O!)UQ!SO7+$}O!)cQbO7+%dO!)kQbO7+$tOOQ!Q7+%o7+%oO!)pQbO7+%oO!)uQbO7+%oO!)}Q!SO7+%uOOQ!R<tAN>tOOQ!QAN>TAN>TO!,wQbOAN>TO!,|QbOAN>TOOQ`-E8`-E8`OOQ!QAN>jAN>jO!-UQbOAN>jO4bQbO,5:aOYQ!SO,5:cOOQ!QAN>uAN>uP! sQ!SO'#EZOOQ`7+%[7+%[OOQ!QG23oG23oO!-ZQbOG23oP!,ZQbO'#DuOOQ!QG24UG24UO!-`QQO1G/{OOQ`1G/}1G/}OOQ!QLD)ZLD)ZOYQ!SO7+%gOOQ`<S!`#O$O#P;'S$O;'S;=`$g<%lO$OU>XV!USOt$Ouw$Ox#O$O#P#Q>n#Q;'S$O;'S;=`$g<%lO$OU>uU#qQ!USOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$O~?^O#i~U?eU#sQ!USOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU@OU!US!dQOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU@g^!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#o@b#o;'S$O;'S;=`$g<%lO$OUAjU!SQ!USOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OUBR_!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#UCQ#U#o@b#o;'S$O;'S;=`$g<%lO$OUCV`!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#`@b#`#aDX#a#o@b#o;'S$O;'S;=`$g<%lO$OUD^`!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#g@b#g#hE`#h#o@b#o;'S$O;'S;=`$g<%lO$OUEe`!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#X@b#X#YFg#Y#o@b#o;'S$O;'S;=`$g<%lO$OUFn^!ZQ!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#o@b#o;'S$O;'S;=`$g<%lO$O^Gq^#jW!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#o@b#o;'S$O;'S;=`$g<%lO$O^Ht^#lW!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#o@b#o;'S$O;'S;=`$g<%lO$O^Iw`#kW!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#f@b#f#gJy#g#o@b#o;'S$O;'S;=`$g<%lO$OUKO`!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#i@b#i#jE`#j#o@b#o;'S$O;'S;=`$g<%lO$OULXUuQ!USOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$O~LpO#t~", - tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, pipeStartsLineTokenizer, new LocalTokenGroup("[~RP!O!PU~ZO#d~~", 11)], + tokenData: "Lp~R}OX$OXY$mYp$Opq$mqr$Ors%Wst'^tu(uuw$Owx(|xy)Ryz)lz{$O{|*V|}$O}!O*V!O!P$O!P!Q3r!Q!R*w!R![-l![!]<_!]!^S!`#O$O#P;'S$O;'S;=`$g<%lO$OU>XV!USOt$Ouw$Ox#O$O#P#Q>n#Q;'S$O;'S;=`$g<%lO$OU>uU#sQ!USOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$O~?^O#k~U?eU#uQ!USOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU@OU!US!dQOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU@g^!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#o@b#o;'S$O;'S;=`$g<%lO$OUAjU!SQ!USOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OUBR_!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#UCQ#U#o@b#o;'S$O;'S;=`$g<%lO$OUCV`!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#`@b#`#aDX#a#o@b#o;'S$O;'S;=`$g<%lO$OUD^`!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#g@b#g#hE`#h#o@b#o;'S$O;'S;=`$g<%lO$OUEe`!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#X@b#X#YFg#Y#o@b#o;'S$O;'S;=`$g<%lO$OUFn^!ZQ!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#o@b#o;'S$O;'S;=`$g<%lO$O^Gq^#lW!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#o@b#o;'S$O;'S;=`$g<%lO$O^Ht^#nW!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#o@b#o;'S$O;'S;=`$g<%lO$O^Iw`#mW!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#f@b#f#gJy#g#o@b#o;'S$O;'S;=`$g<%lO$OUKO`!USOt$Ouw$Ox}$O}!O@b!O!Q$O!Q![@b![!_$O!_!`Ac!`#O$O#P#T$O#T#i@b#i#jE`#j#o@b#o;'S$O;'S;=`$g<%lO$OULXUuQ!USOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$O~LpO#v~", + tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, pipeStartsLineTokenizer, new LocalTokenGroup("[~RP!O!PU~ZO#f~~", 11)], topRules: {"Program":[0,35]}, specialized: [{term: 28, get: (value: any, stack: any) => (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 28, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}], - tokenPrec: 2589 + tokenPrec: 2711 }) diff --git a/src/parser/tests/tokens.test.ts b/src/parser/tests/tokens.test.ts index b166570..4d46790 100644 --- a/src/parser/tests/tokens.test.ts +++ b/src/parser/tests/tokens.test.ts @@ -304,6 +304,7 @@ describe('keywords', () => { expect(`catch`).toMatchToken('Keyword', 'catch') expect(`finally`).toMatchToken('Keyword', 'finally') expect(`throw`).toMatchToken('Keyword', 'throw') + expect(`not`).toMatchToken('Keyword', 'not') }) }) diff --git a/src/parser/tokenizer2.ts b/src/parser/tokenizer2.ts index dacaaca..fd51077 100644 --- a/src/parser/tokenizer2.ts +++ b/src/parser/tokenizer2.ts @@ -104,6 +104,7 @@ const keywords = new Set([ 'catch', 'finally', 'throw', + 'not', ]) // helper diff --git a/src/prelude/index.ts b/src/prelude/index.ts index d69cfc4..c0fb87b 100644 --- a/src/prelude/index.ts +++ b/src/prelude/index.ts @@ -89,7 +89,6 @@ export const globals: Record = { 'some?': (v: any) => toValue(v).type !== 'null', // boolean/logic - not: (v: any) => !v, bnot: (n: number) => ~(n | 0), // utilities diff --git a/src/prelude/tests/prelude.test.ts b/src/prelude/tests/prelude.test.ts index b297b6a..a260489 100644 --- a/src/prelude/tests/prelude.test.ts +++ b/src/prelude/tests/prelude.test.ts @@ -117,6 +117,17 @@ describe('boolean logic', () => { await expect(`not 42`).toEvaluateTo(false) await expect(`not null`).toEvaluateTo(true) }) + + test('not works with function calls', async () => { + await expect(`equals = do x y: x == y end; not equals 5 5`).toEvaluateTo(false) + await expect(`equals = do x y: x == y end; not equals 5 10`).toEvaluateTo(true) + }) + + test('not works with binary operations and comparisons', async () => { + await expect(`not 5 > 10`).toEvaluateTo(true) + await expect(`not 10 > 5`).toEvaluateTo(false) + await expect(`not true and false`).toEvaluateTo(true) + }) }) describe('utilities', () => {