diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index a5327ab..74ed78d 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -243,6 +243,24 @@ export class Compiler { case '%': instructions.push(['MOD']) break + case 'band': + instructions.push(['BIT_AND']) + break + case 'bor': + instructions.push(['BIT_OR']) + break + case 'bxor': + instructions.push(['BIT_XOR']) + break + case '<<': + instructions.push(['BIT_SHL']) + break + case '>>': + instructions.push(['BIT_SHR']) + break + case '>>>': + instructions.push(['BIT_USHR']) + break default: throw new CompilerError(`Unsupported binary operator: ${opValue}`, op.from, op.to) } diff --git a/src/compiler/tests/bitwise.test.ts b/src/compiler/tests/bitwise.test.ts new file mode 100644 index 0000000..0d39a52 --- /dev/null +++ b/src/compiler/tests/bitwise.test.ts @@ -0,0 +1,178 @@ +import { expect, describe, test } from 'bun:test' + +describe('bitwise operators', () => { + describe('band (bitwise AND)', () => { + test('basic AND operation', () => { + expect('5 band 3').toEvaluateTo(1) + // 5 = 0101, 3 = 0011, result = 0001 = 1 + }) + + test('AND with zero', () => { + expect('5 band 0').toEvaluateTo(0) + }) + + test('AND with all bits set', () => { + expect('15 band 7').toEvaluateTo(7) + // 15 = 1111, 7 = 0111, result = 0111 = 7 + }) + + test('AND in assignment', () => { + expect('x = 12 band 10').toEvaluateTo(8) + // 12 = 1100, 10 = 1010, result = 1000 = 8 + }) + }) + + describe('bor (bitwise OR)', () => { + test('basic OR operation', () => { + expect('5 bor 3').toEvaluateTo(7) + // 5 = 0101, 3 = 0011, result = 0111 = 7 + }) + + test('OR with zero', () => { + expect('5 bor 0').toEvaluateTo(5) + }) + + test('OR with all bits set', () => { + expect('8 bor 4').toEvaluateTo(12) + // 8 = 1000, 4 = 0100, result = 1100 = 12 + }) + }) + + describe('bxor (bitwise XOR)', () => { + test('basic XOR operation', () => { + expect('5 bxor 3').toEvaluateTo(6) + // 5 = 0101, 3 = 0011, result = 0110 = 6 + }) + + test('XOR with itself returns zero', () => { + expect('5 bxor 5').toEvaluateTo(0) + }) + + test('XOR with zero returns same value', () => { + expect('7 bxor 0').toEvaluateTo(7) + }) + + test('XOR in assignment', () => { + expect('result = 8 bxor 12').toEvaluateTo(4) + // 8 = 1000, 12 = 1100, result = 0100 = 4 + }) + }) + + describe('bnot (bitwise NOT)', () => { + test('NOT of positive number', () => { + expect('bnot 5').toEvaluateTo(-6) + // ~5 = -6 (two\'s complement) + }) + + test('NOT of zero', () => { + expect('bnot 0').toEvaluateTo(-1) + }) + + test('NOT of negative number', () => { + expect('bnot -1').toEvaluateTo(0) + }) + + test('double NOT returns original', () => { + expect('bnot (bnot 5)').toEvaluateTo(5) + }) + }) + + describe('<< (left shift)', () => { + test('basic left shift', () => { + expect('5 << 2').toEvaluateTo(20) + // 5 << 2 = 20 + }) + + test('shift by zero', () => { + expect('5 << 0').toEvaluateTo(5) + }) + + test('shift by one', () => { + expect('3 << 1').toEvaluateTo(6) + }) + + test('large shift', () => { + expect('1 << 10').toEvaluateTo(1024) + }) + }) + + describe('>> (signed right shift)', () => { + test('basic right shift', () => { + expect('20 >> 2').toEvaluateTo(5) + // 20 >> 2 = 5 + }) + + test('shift by zero', () => { + expect('20 >> 0').toEvaluateTo(20) + }) + + test('preserves sign for negative numbers', () => { + expect('-20 >> 2').toEvaluateTo(-5) + // Sign is preserved + }) + + test('negative number right shift', () => { + expect('-8 >> 1').toEvaluateTo(-4) + }) + }) + + describe('>>> (unsigned right shift)', () => { + test('basic unsigned right shift', () => { + expect('20 >>> 2').toEvaluateTo(5) + }) + + test('unsigned shift of -1', () => { + expect('-1 >>> 1').toEvaluateTo(2147483647) + // -1 >>> 1 = 2147483647 (unsigned, no sign extension) + }) + + test('unsigned shift of negative number', () => { + expect('-8 >>> 1').toEvaluateTo(2147483644) + }) + }) + + describe('compound expressions', () => { + test('multiple bitwise operations', () => { + expect('(5 band 3) bor (8 bxor 12)').toEvaluateTo(5) + // (5 & 3) | (8 ^ 12) = 1 | 4 = 5 + }) + + test('bitwise with variables', () => { + expect(` + a = 5 + b = 3 + a bor b + `).toEvaluateTo(7) + }) + + test('shift operations with variables', () => { + expect(` + x = 16 + y = 2 + x >> y + `).toEvaluateTo(4) + }) + + test('mixing shifts and bitwise', () => { + expect('(8 << 1) band 15').toEvaluateTo(0) + // (8 << 1) & 15 = 16 & 15 = 0 + }) + + test('mixing shifts and bitwise 2', () => { + expect('(7 << 1) band 15').toEvaluateTo(14) + // (7 << 1) & 15 = 14 & 15 = 14 + }) + }) + + describe('precedence', () => { + test('bitwise has correct precedence with arithmetic', () => { + expect('1 + 2 band 3').toEvaluateTo(3) + // (1 + 2) & 3 = 3 & 3 = 3 + }) + + test('shift has correct precedence', () => { + expect('4 + 8 << 1').toEvaluateTo(24) + // (4 + 8) << 1 = 12 << 1 = 24 + }) + }) +}) diff --git a/src/parser/operatorTokenizer.ts b/src/parser/operatorTokenizer.ts index 3c85400..c909543 100644 --- a/src/parser/operatorTokenizer.ts +++ b/src/parser/operatorTokenizer.ts @@ -5,6 +5,12 @@ type Operator = { str: string; tokenName: keyof typeof terms } const operators: Array = [ { str: 'and', tokenName: 'And' }, { str: 'or', tokenName: 'Or' }, + { str: 'band', tokenName: 'Band' }, + { str: 'bor', tokenName: 'Bor' }, + { str: 'bxor', tokenName: 'Bxor' }, + { str: '>>>', tokenName: 'Ushr' }, // Must come before >> + { str: '>>', tokenName: 'Shr' }, + { str: '<<', tokenName: 'Shl' }, { str: '>=', tokenName: 'Gte' }, { str: '<=', tokenName: 'Lte' }, { str: '!=', tokenName: 'Neq' }, diff --git a/src/parser/shrimp.grammar b/src/parser/shrimp.grammar index c021018..965c206 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, EqEq, Neq, Lt, Lte, Gt, Gte, Modulo, PlusEq, MinusEq, StarEq, SlashEq, ModuloEq } +@external tokens operatorTokenizer from "./operatorTokenizer" { Star, Slash, Plus, Minus, And, Or, Eq, EqEq, Neq, Lt, Lte, Gt, Gte, Modulo, PlusEq, MinusEq, StarEq, SlashEq, ModuloEq, Band, Bor, Bxor, Shl, Shr, Ushr } @tokens { @precedence { Number Regex } @@ -51,6 +51,7 @@ null { @specialize[@name=Null] } comparison @left, multiplicative @left, additive @left, + bitwise @left, call, functionWithNewlines } @@ -189,7 +190,13 @@ BinOp { (expression | BinOp) !multiplicative Star (expression | BinOp) | (expression | BinOp) !multiplicative Slash (expression | BinOp) | (expression | BinOp) !additive Plus (expression | BinOp) | - (expression | BinOp) !additive Minus (expression | BinOp) + (expression | BinOp) !additive Minus (expression | BinOp) | + (expression | BinOp) !bitwise Band (expression | BinOp) | + (expression | BinOp) !bitwise Bor (expression | BinOp) | + (expression | BinOp) !bitwise Bxor (expression | BinOp) | + (expression | BinOp) !bitwise Shl (expression | BinOp) | + (expression | BinOp) !bitwise Shr (expression | BinOp) | + (expression | BinOp) !bitwise Ushr (expression | BinOp) } ParenExpr { diff --git a/src/parser/shrimp.terms.ts b/src/parser/shrimp.terms.ts index 82f4419..da329d7 100644 --- a/src/parser/shrimp.terms.ts +++ b/src/parser/shrimp.terms.ts @@ -19,49 +19,55 @@ export const StarEq = 17, SlashEq = 18, ModuloEq = 19, - Identifier = 20, - AssignableIdentifier = 21, - Word = 22, - IdentifierBeforeDot = 23, - Do = 24, - Comment = 25, - Program = 26, - PipeExpr = 27, - WhileExpr = 29, - keyword = 71, - ConditionalOp = 31, - ParenExpr = 32, - FunctionCallWithNewlines = 33, - DotGet = 34, - Number = 35, - PositionalArg = 36, - FunctionDef = 37, - Params = 38, - NamedParam = 39, - NamedArgPrefix = 40, - String = 41, - StringFragment = 42, - Interpolation = 43, - EscapeSeq = 44, - Boolean = 45, - Null = 46, - colon = 47, - CatchExpr = 48, - Block = 50, - FinallyExpr = 51, - Underscore = 54, - NamedArg = 55, - IfExpr = 56, - FunctionCall = 58, - ElseIfExpr = 59, - ElseExpr = 61, - FunctionCallOrIdentifier = 62, - BinOp = 63, - Regex = 64, - Dict = 65, - Array = 66, - FunctionCallWithBlock = 67, - TryExpr = 68, - Throw = 70, - CompoundAssign = 72, - Assign = 73 + Band = 20, + Bor = 21, + Bxor = 22, + Shl = 23, + Shr = 24, + Ushr = 25, + Identifier = 26, + AssignableIdentifier = 27, + Word = 28, + IdentifierBeforeDot = 29, + Do = 30, + Comment = 31, + Program = 32, + PipeExpr = 33, + WhileExpr = 35, + keyword = 77, + ConditionalOp = 37, + ParenExpr = 38, + FunctionCallWithNewlines = 39, + DotGet = 40, + Number = 41, + PositionalArg = 42, + FunctionDef = 43, + Params = 44, + NamedParam = 45, + NamedArgPrefix = 46, + String = 47, + StringFragment = 48, + Interpolation = 49, + EscapeSeq = 50, + Boolean = 51, + Null = 52, + colon = 53, + CatchExpr = 54, + Block = 56, + FinallyExpr = 57, + Underscore = 60, + NamedArg = 61, + IfExpr = 62, + FunctionCall = 64, + ElseIfExpr = 65, + ElseExpr = 67, + FunctionCallOrIdentifier = 68, + BinOp = 69, + Regex = 70, + Dict = 71, + Array = 72, + FunctionCallWithBlock = 73, + TryExpr = 74, + Throw = 76, + CompoundAssign = 78, + Assign = 79 diff --git a/src/parser/shrimp.ts b/src/parser/shrimp.ts index fdab5b4..419523f 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 "./parserScopeContext" import {highlighting} from "./highlight" -const spec_Identifier = {__proto__:null,while:60, null:92, catch:98, finally:104, end:106, if:114, else:120, try:138, throw:142} +const spec_Identifier = {__proto__:null,while:72, null:104, catch:110, finally:116, end:118, if:126, else:132, try:150, throw:154} export const parser = LRParser.deserialize({ version: 14, - states: "iAN>iOOQ`AN=yAN=yOJWQbOAN=yOJ]QbOAN=yOOQ`-E8P-E8POOQ`AN>^AN>^OJeQbOAN>^O.mQbO,5:TO7[QbO,5:VOOQ`AN>jAN>jPAeQbO'#DzOOQ`7+%O7+%OOOQ`G23eG23eOJjQbOG23ePIjQbO'#DiOOQ`G23xG23xOJoQQO1G/oOOQ`1G/q1G/qOOQ`LD)PLD)PO7[QbO7+%ZOOQ`<qOU}O~P?TOUoi~P>qO#_#zO~P2uO#_#zO~O#_#zOl!|X~O!P!aO#_#zOl!|X~O#_#zO~P3zOT|OU}O#Q!OO#_#zOl!|X~OhfO!WrO~P*vO#Q!OO#_#zO~OxsO#Q#eO#b#}O~O#Q#hO#b$PO~P/bOl!dO#Q!ki#c!ki!R!ki!U!ki!V!ki#_!ki!^!ki~Ol!dO#Q!ji#c!ji!R!ji!U!ji!V!ji#_!ji!^!ji~Ol!dO!R!SX!U!SX!V!SX!^!SX~O#Q$SO!R#[P!U#[P!V#[P!^#[P~P6TO!R$WO!U$XO!V$YO~Ox!hO!Pva~O#Q$^O~P6TO!R$WO!U$XO!V$aO~O#Q!OO#_$dO~O#Q!OO#_qi~OxsO#Q#eO#b$gO~O#Q#hO#b$hO~P/bOl!dO#Q$iO~O#Q$SO!R#[X!U#[X!V#[X!^#[X~P6TOd$kO~O!P$lO~O!V$mO~O!U$XO!V$mO~Ol!dO!R$WO!U$XO!V$oO~O#Q$SO!R#[P!U#[P!V#[P~P6TO!V$vO!^$uO~O!V$xO~O!V$yO~O!U$XO!V$yO~OhfO!WrO#_qq~P*vO#Q!OO#_qq~O!P%OO~O!V%QO~O!V%RO~O!U$XO!V%RO~O!R$WO!U$XO!V%RO~O!V%VO!^$uO~O!P%YO!Z%XO~O!V%VO~O!V%ZO~OhfO!WrO#_qy~P*vO!V%^O~O!U$XO!V%^O~O!V%aO~O!V%dO~O!P%eO~Os!b~", - goto: "7t#_PPPPPPPPPPPPPPPPPPPPPPPPPPP#`P#yP$`%Z&g&mP'u(R({)OP)UP*Z*ZPPP*_P*k+TPPP+k#`P,T,nP,r,x-_P.R/T#y#yP#yP#y#y0X0_0k1_1e1o1u1|2S2^2d2nPPP2x2|3q5aPPP6iP6yPPPPP6}7T7Zr`Oe!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%eQ!UWR#Z!Pw`OWe!P!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%er^Oe!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%eQ!XWS!mg%XQ!rhQ!vjQ#S}Q#U|R#^!PvSOeg!_!`!a!d!s#m#u#v#w$U$^$l%O%X%Y%e!SZRSYhjsvxyz{|}!Q!R!Z!^!l#_#d#i$O$e$|%[S!RW!PQ!wkR!xlQ!TWR#Y!PrROe!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%e!SwRSYhjsvxyz{|}!Q!R!Z!^!l#_#d#i$O$e$|%[S!QW!PT!lg%XetRSv!Q!R!l#_$e$|%[r`Oe!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%edrRSv!Q!R!l#_$e$|%[Q!UWQ!|sR#Z!PR!kfX!if!g!j#r#OZORSWYeghjsvxyz{|}!P!Q!R!Z!^!_!`!a!d!l!s#_#d#i#m#u#v#w$O$U$^$e$l$|%O%X%Y%[%eR#s!hTnQpQ$[#nQ$c#xQ$q$]R%T$rQ#n!aQ#x!sQ$_#vQ$`#wQ%P$lQ%]%OQ%c%YR%f%eQ$Z#nQ$b#xQ$n$[Q$p$]Q$z$cS%S$q$rR%_%TdtRSv!Q!R!l#_$e$|%[Q![YQ#b!ZX#e![#b#f#|vTOWe!P!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%eT!og%XT$s$_$tQ$w$_R%W$twTOWe!P!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%erVOe!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%eQ!SWQ!ujQ#OyQ#RzR#X!P#PZORSWYeghjsvxyz{|}!P!Q!R!Z!^!_!`!a!d!l!s#_#d#i#m#u#v#w$O$U$^$e$l$|%O%X%Y%[%e!WZRSYghjsvxyz{|}!Q!R!Z!^!l#_#d#i$O$e$|%X%[w[OWe!P!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%eQeOR!ee^!bb!Y#j#k#l$T$]R#o!bQ!PWQ!ZY`#W!P!Z#_#`#y$e$|%[S#_!Q!RS#`!S!XS#y#X#^Q$e#{R$|$fQ!gfR#q!gQ!jfQ#r!gT#t!j#rQpQR!zpS$U#m$^R$j$UQ$f#{R$}$fYvRS!Q!R!lR!}vQ$t$_R%U$tQ#f![Q#|#bT$Q#f#|Q#i!^Q$O#dT$R#i$OTdOeSbOeS!YW!PQ#j!_Q#k!``#l!a!s#v#w$l%O%Y%eQ#p!dU$T#m$U$^R$]#uvUOWe!P!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%edrRSv!Q!R!l#_$e$|%[Q!^YS!ng%XQ!qhQ!tjQ!|sQ#OxQ#PyQ#QzQ#S{Q#T|Q#V}Q#d!ZX#h!^#d#i$Or]Oe!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%e!WwRSYghjsvxyz{|}!Q!R!Z!^!l#_#d#i$O$e$|%X%[Q!WWR#]!P[uRSv!Q!R!lQ#{#_V${$e$|%[ToQpQ$V#mR$r$^Q!pgR%b%XraOe!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%eQ!VWR#[!P", - nodeNames: "⚠ Star Slash Plus Minus And Or Eq EqEq Neq Lt Lte Gt Gte Modulo PlusEq MinusEq StarEq SlashEq ModuloEq Identifier AssignableIdentifier Word IdentifierBeforeDot Do Comment Program PipeExpr operator WhileExpr keyword ConditionalOp ParenExpr FunctionCall DotGet Number PositionalArg FunctionDef Params NamedParam NamedArgPrefix String StringFragment Interpolation EscapeSeq Boolean Null colon CatchExpr keyword Block FinallyExpr keyword keyword Underscore NamedArg IfExpr keyword FunctionCall ElseIfExpr keyword ElseExpr FunctionCallOrIdentifier BinOp Regex Dict Array FunctionCallWithBlock TryExpr keyword Throw keyword CompoundAssign Assign", - maxTerm: 111, + states: "PQcO,5:fO>mQcO,5:fOOQa1G/[1G/[OOOO,59y,59yOOOO,59z,59zOOOO-E8R-E8ROOQa1G/c1G/cOOQ`,5:V,5:VOOQ`-E8U-E8UOOQa1G/y1G/yO@fQcO1G/yO@pQcO1G/yOBOQcO1G/yOBYQcO1G/yOBgQcO1G/yOOQa1G/X1G/XOCuQcO1G/XOC|QcO1G/XODTQcO1G/XOOQ`-E8O-E8OOD[QRO1G/YODfQQO1G/YODkQQO1G/YODsQQO1G/YOEOQRO1G/YOEVQRO1G/YOEhQbO,59oOErQQO1G/YOOQa1G/Y1G/YOEzQQO1G/{OOQa1G/|1G/|OFVQbO1G/|OOQO'#EY'#EYOEzQQO1G/{OOQa1G/{1G/{OOQ`'#EZ'#EZOFVQbO1G/|OFaQbO1G0TOF{QbO1G0SOGgQbO'#DfOGxQbO'#DfOH]QbO1G/}OOQ`-E7}-E7}OOQ`,5:k,5:kOOQ`-E8P-E8POHhQQO,59tOOQO,59u,59uOOQO-E8Q-E8QOHpQbO1G/_O9PQbO1G/rO9PQbO1G/VOHwQbO1G0OOISQQO7+$tOOQa7+$t7+$tOI[QQO1G/ZOIdQQO7+%gOOQa7+%g7+%gOIoQbO7+%hOOQa7+%h7+%hOOQO-E8W-E8WOOQ`-E8X-E8XOOQ`'#EU'#EUOIyQQO'#EUOJRQbO'#EnOOQ`,5:Q,5:QOJfQbO'#DdOJkQQO'#DgOOQ`7+%i7+%iOJpQbO7+%iOJuQbO7+%iOJ}QbO7+$yOK]QbO7+$yOKmQbO7+%^OKuQbO7+$qOOQ`7+%j7+%jOKzQbO7+%jOLPQbO7+%jOOQa<oAN>oOOQ`AN>PAN>PONZQbOAN>PON`QbOAN>POOQ`-E8V-E8VOOQ`AN>dAN>dONhQbOAN>dO1qQbO,5:ZO9PQbO,5:]OOQ`AN>pAN>pPEhQbO'#EQOOQ`7+%U7+%UOOQ`G23kG23kONmQbOG23kPMmQbO'#DoOOQ`G24OG24OONrQQO1G/uOOQ`1G/w1G/wOOQ`LD)VLD)VO9PQbO7+%aOOQ`<`#Z#be_zSOt$Ouw$Ox}$O}!O (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 20, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}], - tokenPrec: 1922 + tokenData: "IS~R}OX$OXY$mYZ%WZp$Opq$mqs$Ost%qtu'Yuw$Owx'_xy'dyz'}z{$O{|(h|}$O}!O(h!O!P$O!P!Q0o!Q!R)Y!R![+w![!]9[!]!^%W!^!}$O!}#O9u#O#P;k#P#Q;p#Q#R$O#R#S`#Z#be_!QSOt$Ouw$Ox}$O}!O (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 26, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}], + tokenPrec: 2109 }) diff --git a/src/parser/tests/bitwise.test.ts b/src/parser/tests/bitwise.test.ts new file mode 100644 index 0000000..5ccc8ed --- /dev/null +++ b/src/parser/tests/bitwise.test.ts @@ -0,0 +1,72 @@ +import { expect, describe, test } from 'bun:test' + +import '../shrimp.grammar' // Importing this so changes cause it to retest! + +describe('bitwise operators - grammar', () => { + test('parses band (bitwise AND)', () => { + expect('5 band 3').toMatchTree(` + BinOp + Number 5 + Band band + Number 3`) + }) + + test('parses bor (bitwise OR)', () => { + expect('5 bor 3').toMatchTree(` + BinOp + Number 5 + Bor bor + Number 3`) + }) + + test('parses bxor (bitwise XOR)', () => { + expect('5 bxor 3').toMatchTree(` + BinOp + Number 5 + Bxor bxor + Number 3`) + }) + + test('parses << (left shift)', () => { + expect('5 << 2').toMatchTree(` + BinOp + Number 5 + Shl << + Number 2`) + }) + + test('parses >> (signed right shift)', () => { + expect('20 >> 2').toMatchTree(` + BinOp + Number 20 + Shr >> + Number 2`) + }) + + test('parses >>> (unsigned right shift)', () => { + expect('-1 >>> 1').toMatchTree(` + BinOp + Number -1 + Ushr >>> + Number 1`) + }) + + test('parses bnot (bitwise NOT) as function call', () => { + expect('bnot 5').toMatchTree(` + FunctionCall + Identifier bnot + PositionalArg + Number 5`) + }) + + test('bitwise operators work in expressions', () => { + expect('x = 5 band 3').toMatchTree(` + Assign + AssignableIdentifier x + Eq = + BinOp + Number 5 + Band band + Number 3`) + }) +}) diff --git a/src/prelude/index.ts b/src/prelude/index.ts index c2c77ec..c153870 100644 --- a/src/prelude/index.ts +++ b/src/prelude/index.ts @@ -63,6 +63,7 @@ export const globals = { // boolean/logic not: (v: any) => !v, + bnot: (n: number) => ~(n | 0), // utilities inc: (n: number) => n + 1,