diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index f1c1e01..227cc8b 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -220,6 +220,9 @@ export class Compiler { case '/': instructions.push(['DIV']) break + case '%': + instructions.push(['MOD']) + break default: throw new CompilerError(`Unsupported binary operator: ${opValue}`, op.from, op.to) } diff --git a/src/compiler/tests/compiler.test.ts b/src/compiler/tests/compiler.test.ts index 46ee0b7..5e58c11 100644 --- a/src/compiler/tests/compiler.test.ts +++ b/src/compiler/tests/compiler.test.ts @@ -38,6 +38,12 @@ describe('compiler', () => { expect('15 / 3').toEvaluateTo(5) }) + test('modulo', () => { + expect('44 % 2').toEvaluateTo(0) + expect('44 % 3').toEvaluateTo(2) + expect('3 % 4').toEvaluateTo(3) + }) + test('assign number', () => { expect('x = 5').toEvaluateTo(5) }) diff --git a/src/parser/operatorTokenizer.ts b/src/parser/operatorTokenizer.ts index 478c20d..d15318c 100644 --- a/src/parser/operatorTokenizer.ts +++ b/src/parser/operatorTokenizer.ts @@ -17,6 +17,7 @@ const operators: Array = [ { str: '-', tokenName: 'Minus' }, { str: '>', tokenName: 'Gt' }, { str: '<', tokenName: 'Lt' }, + { str: '%', tokenName: 'Modulo' }, ] export const operatorTokenizer = new ExternalTokenizer((input: InputStream) => { diff --git a/src/parser/shrimp.grammar b/src/parser/shrimp.grammar index ee89d1c..9147110 100644 --- a/src/parser/shrimp.grammar +++ b/src/parser/shrimp.grammar @@ -6,7 +6,7 @@ @top Program { item* } -@external tokens operatorTokenizer from "./operatorTokenizer" { Star, Slash, Plus, Minus, And, Or, Eq, Neq, Lt, Lte, Gt, Gte } +@external tokens operatorTokenizer from "./operatorTokenizer" { Star, Slash, Plus, Minus, And, Or, Eq, Neq, Lt, Lte, Gt, Gte, Modulo } @tokens { @precedence { Number Regex } @@ -148,6 +148,7 @@ Assign { } BinOp { + expression !multiplicative Modulo expression | (expression | BinOp) !multiplicative Star (expression | BinOp) | (expression | BinOp) !multiplicative Slash (expression | BinOp) | (expression | BinOp) !additive Plus (expression | BinOp) | diff --git a/src/parser/shrimp.terms.ts b/src/parser/shrimp.terms.ts index 69d6d47..eb818df 100644 --- a/src/parser/shrimp.terms.ts +++ b/src/parser/shrimp.terms.ts @@ -12,40 +12,41 @@ export const Lte = 10, Gt = 11, Gte = 12, - Identifier = 13, - AssignableIdentifier = 14, - Word = 15, - IdentifierBeforeDot = 16, - Do = 17, - Program = 18, - PipeExpr = 19, - FunctionCall = 20, - DotGet = 21, - Number = 22, - ParenExpr = 23, - FunctionCallOrIdentifier = 24, - BinOp = 25, - String = 26, - StringFragment = 27, - Interpolation = 28, - EscapeSeq = 29, - Boolean = 30, - Regex = 31, - Dict = 32, - NamedArg = 33, - NamedArgPrefix = 34, - FunctionDef = 35, - Params = 36, - colon = 37, - keyword = 52, - Underscore = 39, - Array = 40, - Null = 41, - ConditionalOp = 42, - PositionalArg = 43, - IfExpr = 45, - SingleLineThenBlock = 47, - ThenBlock = 48, - ElseIfExpr = 49, - ElseExpr = 51, - Assign = 53 + Modulo = 13, + Identifier = 14, + AssignableIdentifier = 15, + Word = 16, + IdentifierBeforeDot = 17, + Do = 18, + Program = 19, + PipeExpr = 20, + FunctionCall = 21, + DotGet = 22, + Number = 23, + ParenExpr = 24, + FunctionCallOrIdentifier = 25, + BinOp = 26, + String = 27, + StringFragment = 28, + Interpolation = 29, + EscapeSeq = 30, + Boolean = 31, + Regex = 32, + Dict = 33, + NamedArg = 34, + NamedArgPrefix = 35, + FunctionDef = 36, + Params = 37, + colon = 38, + keyword = 53, + Underscore = 40, + Array = 41, + Null = 42, + ConditionalOp = 43, + PositionalArg = 44, + IfExpr = 46, + SingleLineThenBlock = 48, + ThenBlock = 49, + ElseIfExpr = 50, + ElseExpr = 52, + Assign = 54 diff --git a/src/parser/shrimp.ts b/src/parser/shrimp.ts index 4865cb6..5b4e228 100644 --- a/src/parser/shrimp.ts +++ b/src/parser/shrimp.ts @@ -4,24 +4,24 @@ import {operatorTokenizer} from "./operatorTokenizer" import {tokenizer, specializeKeyword} from "./tokenizer" import {trackScope} from "./scopeTracker" import {highlighting} from "./highlight" -const spec_Identifier = {__proto__:null,end:76, null:82, if:92, elseif:100, else:104} +const spec_Identifier = {__proto__:null,end:78, null:84, if:94, elseif:102, else:106} export const parser = LRParser.deserialize({ version: 14, - states: "2YQYQbOOO!ZOpO'#CqO#mQcO'#CtO$gOSO'#CvO$uQbO'#ETOOQ`'#DP'#DPOOQa'#C|'#C|O%xQbO'#DUO&}QcO'#DxOOQa'#Dx'#DxO(TQcO'#DwO(lQRO'#CuO(zQcO'#DsO)cQbO'#CsOOQ`'#Dt'#DtO*ZQbO'#DsO*iQbO'#EZOOQ`'#DZ'#DZO+^QRO'#DcOOQ`'#Ds'#DsO+cQQO'#DrOOQ`'#Dr'#DrOOQ`'#Dd'#DdQYQbOOO+kObO,59]O+sQbO'#C}OOQa'#Dw'#DwOOQ`'#DX'#DXOOQ`'#EY'#EYOOQ`'#Dk'#DkO+}QbO,59[O,bQbO'#CxO,jQWO'#CyOOOO'#Dz'#DzOOOO'#De'#DeO-OOSO,59bOOQa,59b,59bOOQ`'#Dg'#DgO-^QbO'#DQO-fQQO,5:oOOQ`'#Df'#DfO-kQbO,59pO-rQQO,59hOOQa,59p,59pO-}QbO,59pO*iQbO,59aO*iQbO,59aO.XQRO,59_O/nQRO'#CuO0OQRO,59_O0[QQO,59_O0aQQO,59_O0iQbO'#DlO0tQbO,59ZO1VQRO,5:uO1^QQO,5:uO1cQbO,59}OOQ`,5:^,5:^OOQ`-E7b-E7bOOQa1G.w1G.wOOQ`,59i,59iOOQ`-E7i-E7iOOOO,59d,59dOOOO,59e,59eOOOO-E7c-E7cOOQa1G.|1G.|OOQ`-E7e-E7eO1mQbO1G0ZOOQ`-E7d-E7dO1zQQO1G/SOOQa1G/[1G/[O2VQbO1G/[OOQO'#Di'#DiO1zQQO1G/SOOQa1G/S1G/SOOQ`'#Dj'#DjO2VQbO1G/[OOQa1G.{1G.{O2aQcO1G.{OOQa1G.y1G.yO*iQbO,59rO*iQbO,59rO!`QbO'#CtO&PQbO'#CpOOQ`,5:W,5:WOOQ`-E7j-E7jO2{QbO1G0aOOQ`1G/i1G/iO3YQbO7+%uO3_QbO7+%vO3oQQO7+$nOOQa7+$n7+$nO3zQbO7+$vOOQa7+$v7+$vOOQO-E7g-E7gOOQ`-E7h-E7hOOQO1G/^1G/^O4UQRO1G/^OOQ`'#D]'#D]O4`QbO7+%{O4eQbO7+%|OOQ`<|AN>|O*iQbO'#D_OOQ`'#Dm'#DmO5xQbOAN?SO6TQQO'#DaOOQ`AN?SAN?SO6YQbOAN?SO6_QRO,59yO6fQQO,59yOOQ`-E7k-E7kOOQ`G24nG24nO6kQbOG24nO6pQQO,59{O6uQQO1G/eOOQ`LD*YLD*YO3_QbO1G/gO4eQbO7+%POOQ`7+%R7+%ROOQ`<}AN>}O*yQbO'#D`OOQ`'#Dn'#DnO7XQbOAN?TO7dQQO'#DbOOQ`AN?TAN?TO7iQbOAN?TO7nQRO,59zO7uQQO,59zOOQ`-E7l-E7lOOQ`G24oG24oO7zQbOG24oO8PQQO,59|O8UQQO1G/fOOQ`LD*ZLD*ZO4nQbO1G/hO5tQbO7+%QOOQ`7+%S7+%SOOQ`<h#i#o7^#o#p#{#p#q@`#q;'S#{;'S;=`$d<%l~#{~O#{~~@yS$QUkSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{^$qUkS!dYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UkS!vQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZkS!eYOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!eYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O!o~~'aO!m~U'hUkS!jQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RUkS!{QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(jWkSOt#{uw#{x!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U)ZYkSfQOt#{uw#{x!O#{!O!P)y!P!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U*OWkSOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U*oWkSfQOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U+^WkSOt#{uw#{x!P#{!P!Q+v!Q#O#{#P;'S#{;'S;=`$d<%lO#{U+{^kSOY,wYZ#{Zt,wtu-zuw,wwx-zx!P,w!P!Q#{!Q!},w!}#O2m#O#P0Y#P;'S,w;'S;=`3n<%lO,wU-O^kSoQOY,wYZ#{Zt,wtu-zuw,wwx-zx!P,w!P!Q0o!Q!},w!}#O2m#O#P0Y#P;'S,w;'S;=`3n<%lO,wQ.PXoQOY-zZ!P-z!P!Q.l!Q!}-z!}#O/Z#O#P0Y#P;'S-z;'S;=`0i<%lO-zQ.oP!P!Q.rQ.wUoQ#Z#[.r#]#^.r#a#b.r#g#h.r#i#j.r#m#n.rQ/^VOY/ZZ#O/Z#O#P/s#P#Q-z#Q;'S/Z;'S;=`0S<%lO/ZQ/vSOY/ZZ;'S/Z;'S;=`0S<%lO/ZQ0VP;=`<%l/ZQ0]SOY-zZ;'S-z;'S;=`0i<%lO-zQ0lP;=`<%l-zU0tWkSOt#{uw#{x!P#{!P!Q1^!Q#O#{#P;'S#{;'S;=`$d<%lO#{U1ebkSoQOt#{uw#{x#O#{#P#Z#{#Z#[1^#[#]#{#]#^1^#^#a#{#a#b1^#b#g#{#g#h1^#h#i#{#i#j1^#j#m#{#m#n1^#n;'S#{;'S;=`$d<%lO#{U2r[kSOY2mYZ#{Zt2mtu/Zuw2mwx/Zx#O2m#O#P/s#P#Q,w#Q;'S2m;'S;=`3h<%lO2mU3kP;=`<%l2mU3qP;=`<%l,wU3{UkSuQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U4fW!uQkSOt#{uw#{x!_#{!_!`5O!`#O#{#P;'S#{;'S;=`$d<%lO#{U5TVkSOt#{uw#{x#O#{#P#Q5j#Q;'S#{;'S;=`$d<%lO#{U5qU!tQkSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~6YO!p~U6aU!zQkSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U6zUkSwQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U7cYkSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#o7^#o;'S#{;'S;=`$d<%lO#{U8YUrQkSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U8qZkSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#U9d#U#o7^#o;'S#{;'S;=`$d<%lO#{U9i[kSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#`7^#`#a:_#a#o7^#o;'S#{;'S;=`$d<%lO#{U:d[kSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#g7^#g#h;Y#h#o7^#o;'S#{;'S;=`$d<%lO#{U;_[kSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#X7^#X#Yo[!rWkSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#f7^#f#g?e#g#o7^#o;'S#{;'S;=`$d<%lO#{U?j[kSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#i7^#i#j;Y#j#o7^#o;'S#{;'S;=`$d<%lO#{U@gU|QkSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~AOO#P~", - tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO!i~~", 11)], - topRules: {"Program":[0,18]}, - specialized: [{term: 13, get: (value: any, stack: any) => (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 13, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}], - tokenPrec: 1008 + tokenData: "AO~R|OX#{XY$jYZ%TZp#{pq$jqs#{st%ntu'Vuw#{wx'[xy'ayz'zz{#{{|(e|}#{}!O(e!O!P#{!P!Q+X!Q![)S![!]3t!]!^%T!^!}#{!}#O4_#O#P6T#P#Q6Y#Q#R#{#R#S6s#S#T#{#T#Y7^#Y#Z8l#Z#b7^#b#ch#i#o7^#o#p#{#p#q@`#q;'S#{;'S;=`$d<%l~#{~O#{~~@yS$QUlSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{^$qUlS!eYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UlS!wQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZlS!fYOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!fYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O!p~~'aO!n~U'hUlS!kQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RUlS!|QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(jWlSOt#{uw#{x!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U)ZYlSgQOt#{uw#{x!O#{!O!P)y!P!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U*OWlSOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U*oWlSgQOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U+^WlSOt#{uw#{x!P#{!P!Q+v!Q#O#{#P;'S#{;'S;=`$d<%lO#{U+{^lSOY,wYZ#{Zt,wtu-zuw,wwx-zx!P,w!P!Q#{!Q!},w!}#O2m#O#P0Y#P;'S,w;'S;=`3n<%lO,wU-O^lSpQOY,wYZ#{Zt,wtu-zuw,wwx-zx!P,w!P!Q0o!Q!},w!}#O2m#O#P0Y#P;'S,w;'S;=`3n<%lO,wQ.PXpQOY-zZ!P-z!P!Q.l!Q!}-z!}#O/Z#O#P0Y#P;'S-z;'S;=`0i<%lO-zQ.oP!P!Q.rQ.wUpQ#Z#[.r#]#^.r#a#b.r#g#h.r#i#j.r#m#n.rQ/^VOY/ZZ#O/Z#O#P/s#P#Q-z#Q;'S/Z;'S;=`0S<%lO/ZQ/vSOY/ZZ;'S/Z;'S;=`0S<%lO/ZQ0VP;=`<%l/ZQ0]SOY-zZ;'S-z;'S;=`0i<%lO-zQ0lP;=`<%l-zU0tWlSOt#{uw#{x!P#{!P!Q1^!Q#O#{#P;'S#{;'S;=`$d<%lO#{U1eblSpQOt#{uw#{x#O#{#P#Z#{#Z#[1^#[#]#{#]#^1^#^#a#{#a#b1^#b#g#{#g#h1^#h#i#{#i#j1^#j#m#{#m#n1^#n;'S#{;'S;=`$d<%lO#{U2r[lSOY2mYZ#{Zt2mtu/Zuw2mwx/Zx#O2m#O#P/s#P#Q,w#Q;'S2m;'S;=`3h<%lO2mU3kP;=`<%l2mU3qP;=`<%l,wU3{UlSvQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U4fW!vQlSOt#{uw#{x!_#{!_!`5O!`#O#{#P;'S#{;'S;=`$d<%lO#{U5TVlSOt#{uw#{x#O#{#P#Q5j#Q;'S#{;'S;=`$d<%lO#{U5qU!uQlSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~6YO!q~U6aU!{QlSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U6zUlSxQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U7cYlSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#o7^#o;'S#{;'S;=`$d<%lO#{U8YUsQlSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U8qZlSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#U9d#U#o7^#o;'S#{;'S;=`$d<%lO#{U9i[lSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#`7^#`#a:_#a#o7^#o;'S#{;'S;=`$d<%lO#{U:d[lSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#g7^#g#h;Y#h#o7^#o;'S#{;'S;=`$d<%lO#{U;_[lSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#X7^#X#Yo[!sWlSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#f7^#f#g?e#g#o7^#o;'S#{;'S;=`$d<%lO#{U?j[lSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#i7^#i#j;Y#j#o7^#o;'S#{;'S;=`$d<%lO#{U@gU}QlSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~AOO#Q~", + tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO!j~~", 11)], + topRules: {"Program":[0,19]}, + specialized: [{term: 14, get: (value: any, stack: any) => (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 14, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}], + tokenPrec: 1069 }) diff --git a/src/parser/tests/basics.test.ts b/src/parser/tests/basics.test.ts index 0d870a8..f5e03f2 100644 --- a/src/parser/tests/basics.test.ts +++ b/src/parser/tests/basics.test.ts @@ -408,6 +408,15 @@ describe('BinOp', () => { `) }) + test('modulo tests', () => { + expect('4 % 3').toMatchTree(` + BinOp + Number 4 + Modulo % + Number 3 + `) + }) + test('mixed operations with precedence', () => { expect('2 + 3 * 4 - 5 / 1').toMatchTree(` BinOp