Skip to content

Commit

Permalink
implemented bitwise operations in SchemioScript
Browse files Browse the repository at this point in the history
  • Loading branch information
ishubin committed Jan 14, 2025
1 parent 74aad0c commit 51e8680
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 22 deletions.
59 changes: 40 additions & 19 deletions src/ui/templater/ast.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,39 @@
import { ReservedTerms, TokenTypes, isReserved, tokenizeExpression } from "./tokenizer";
import { parseStringExpression } from "./strings";
import { ASTAdd, ASTAssign, ASTBoolAnd, ASTBoolOr, ASTDecrementWith, ASTDivide, ASTDivideWith, ASTEquals, ASTExternalObjectLookup, ASTForLoop, ASTFunctionDeclaration, ASTFunctionInvocation, ASTGreaterThan, ASTGreaterThanOrEquals, ASTIFStatement, ASTIncrement, ASTIncrementWith, ASTLessThen, ASTLessThenOrEquals, ASTLocalVariable, ASTMod, ASTMultiExpression, ASTMultiply, ASTMultiplyWith, ASTNegate, ASTNot, ASTNotEqual, ASTObjectFieldAccessor, ASTPow, ASTString, ASTStringTemplate, ASTSubtract, ASTValue, ASTVarRef, ASTWhileStatement } from "./nodes";
import { ASTAdd, ASTAssign, ASTBitShiftLeft, ASTBitShiftRight, ASTBitwiseAnd, ASTBitwiseNot, ASTBitwiseOr, ASTBoolAnd, ASTBoolOr, ASTDecrementWith, ASTDivide, ASTDivideWith, ASTEquals, ASTExternalObjectLookup, ASTForLoop, ASTFunctionDeclaration, ASTFunctionInvocation, ASTGreaterThan, ASTGreaterThanOrEquals, ASTIFStatement, ASTIncrement, ASTIncrementWith, ASTLessThen, ASTLessThenOrEquals, ASTLocalVariable, ASTMod, ASTMultiExpression, ASTMultiply, ASTMultiplyWith, ASTNegate, ASTNot, ASTNotEqual, ASTObjectFieldAccessor, ASTPow, ASTString, ASTStringTemplate, ASTSubtract, ASTValue, ASTVarRef, ASTWhileStatement } from "./nodes";
import { TokenScanner } from "./scanner";
import { ASTStructNode } from "./struct";
import { normalizeTokens } from "./normalization";


const operatorPrecedences = new Map(Object.entries({
'^': 6,
'*': 5,
'/': 5,
'%': 5,
'+': 4,
'-': 4,
'<': 3,
'>': 3,
'<=': 3,
'>=': 3,
'!=': 2,
'==': 2,
'&&': 1,
'||': 0,
'=': -1,
}));
const operatorPrecedences = [
'^',
'*',
'/',
'%',
'+',
'-',
'<<',
'>>',
'&',
'|',
'<',
'>',
'<=',
'>=',
'!=',
'==',
'&&',
'||',
'++',
'--',
'-=',
'+=',
'=',
].reverse().reduce((m, o, idx) => {
m.set(o, idx);
return m;
}, new Map());

function operatorPrecedence(operator) {
if (operatorPrecedences.has(operator)) {
Expand All @@ -46,6 +57,10 @@ const operatorClasses = new Map(Object.entries({
'!=': ASTNotEqual,
'&&': ASTBoolAnd,
'||': ASTBoolOr,
'&': ASTBitwiseAnd,
'|': ASTBitwiseOr,
'<<': ASTBitShiftLeft,
'>>': ASTBitShiftRight,
'=': ASTAssign,
'+=': ASTIncrementWith,
'-=': ASTDecrementWith,
Expand Down Expand Up @@ -169,7 +184,7 @@ class ASTParser extends TokenScanner {

if (token.v === '++' || token.v === '--') {
if (!(a instanceof ASTVarRef)) {
throw new Error(`Unexpected ${token.v} operator`);
throw new Error(`Unexpected ${token.v} operator after ${a.type}`);
}

a = new ASTIncrement(a, false, token.v === '++' ? 1: -1);
Expand Down Expand Up @@ -332,6 +347,12 @@ class ASTParser extends TokenScanner {
return this.parseTermGroup(extVarRef);
} else if (token.t === TokenTypes.STRING) {
return this.parseTermGroup(new ASTString(token.v));
} else if (token.t === TokenTypes.OPERATOR && token.v === '~') {
const nextTerm = this.parseTerm();
if (!nextTerm) {
throw new Error('Expected term token after "~"');
}
return new ASTBitwiseNot(nextTerm);
} else if (token.t === TokenTypes.OPERATOR && token.v === '-') {
const nextTerm = this.parseTerm();
if (!nextTerm) {
Expand Down
37 changes: 36 additions & 1 deletion src/ui/templater/nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,22 @@ export class ASTNot extends ASTNode {
return !v;
}
print() {
return `(!${this.v.print()})`;
return `(!${this.node.print()})`;
}
}

export class ASTBitwiseNot extends ASTNode {
constructor(node) {
super('bitwiseNot');
this.node = node;
}
evalNode(scope) {
const v = this.node.evalNode(scope);
return ~v;
}

print() {
return `(~${this.node.print()})`;
}
}

Expand Down Expand Up @@ -359,6 +374,26 @@ export class ASTBoolAnd extends ASTOperator {
evalNode(scope) { return this.a.evalNode(scope) && this.b.evalNode(scope); }
}

export class ASTBitwiseAnd extends ASTOperator {
constructor(a, b) { super('bitwiseAnd', '&', a, b); }
evalNode(scope) { return this.a.evalNode(scope) & this.b.evalNode(scope); }
}

export class ASTBitwiseOr extends ASTOperator {
constructor(a, b) { super('bitwiseOr', '|', a, b); }
evalNode(scope) { return this.a.evalNode(scope) | this.b.evalNode(scope); }
}

export class ASTBitShiftLeft extends ASTOperator {
constructor(a, b) { super('bitShiftLeft', '<<', a, b); }
evalNode(scope) { return this.a.evalNode(scope) << this.b.evalNode(scope); }
}

export class ASTBitShiftRight extends ASTOperator {
constructor(a, b) { super('bitShiftRight', '>>', a, b); }
evalNode(scope) { return this.a.evalNode(scope) >> this.b.evalNode(scope); }
}

export class ASTAssign extends ASTOperator {
constructor(a, b) { super('assign', '=', a, b); }
evalNode(scope) {
Expand Down
5 changes: 5 additions & 0 deletions src/ui/templater/tokenizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ const singleCharTokens = new Map(Object.entries({
'>': {t: TokenTypes.OPERATOR, v: '>'},
'=': {t: TokenTypes.OPERATOR, v: '='},
'^': {t: TokenTypes.OPERATOR, v: '^'},
'&': {t: TokenTypes.OPERATOR, v: '&'},
'|': {t: TokenTypes.OPERATOR, v: '|'},
'~': {t: TokenTypes.OPERATOR, v: '~'},
';': {t: TokenTypes.DELIMITER, v: ';'},
':': {t: TokenTypes.COLON, v: ':'},
'!': {t: TokenTypes.NOT, v: '!'},
Expand All @@ -97,6 +100,8 @@ const doubleCharTokens = new Map(Object.entries({
'=>': {t: TokenTypes.OPERATOR, v: '=>'},
'++': {t: TokenTypes.OPERATOR, v: '++'},
'--': {t: TokenTypes.OPERATOR, v: '--'},
'>>': {t: TokenTypes.OPERATOR, v: '>>'},
'<<': {t: TokenTypes.OPERATOR, v: '<<'},
}));


Expand Down
35 changes: 33 additions & 2 deletions test/templater/ast.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@ import { List } from '../../src/ui/templater/list';
describe('templater ast parser', () => {
it('should parse various expressions', () => {
[
['2-5 + qwe', '((2 - 5) + qwe)'],
['2-5 + qwe', '(2 - (5 + qwe))'],
['2 * 8 + 1 - 4 *4 ', '(((2 * 8) + 1) - (4 * 4))'],
['1 + (x - 4) * 3', '(1 + ((x - 4) * 3))'],
['x < 5', '(x < 5)'],
['x + 1 < 5 - y', '((x + 1) < (5 - y))'],
['x + 1 < 5 - y || x > 10', '(((x + 1) < (5 - y)) || (x > 10))'],
['x + 1 < 5 - y && x > 10', '(((x + 1) < (5 - y)) && (x > 10))'],
['x + "qwe" == \'hi qwe\'', '((x + "qwe") == "hi qwe")'],
['x = (a = 1; b = 2; a + b); x*10', '((x = ((a = 1); (b = 2); (a + b))); (x * 10))']
['x = (a = 1; b = 2; a + b); x*10', '((x = ((a = 1); (b = 2); (a + b))); (x * 10))'],
['x = a & b | c', '(x = ((a & b) | c))'],
['x = a | b & c', '(x = (a | (b & c)))'],
['x = a | ~b & c', '(x = (a | ((~b) & c)))'],
['x = a | b << 2 ', '(x = (a | (b << 2)))'],
].forEach(([input, expected]) => {
const real = parseExpression(input).print();
expect(real).toBe(expected);
Expand Down Expand Up @@ -55,6 +59,33 @@ describe('templater ast parser', () => {
});
});

it('should evaluate bitwise operations', () => {
const a = 2675, b = 9428, c = 3480;
[
['a & b', {a, b}, a & b],
['a | b', {a, b}, a | b],
['~a', {a, b}, ~a],
['~b', {a, b}, ~b],
['~a & ~b', {a, b}, ~a & ~b],
['~a | ~b', {a, b}, ~a | ~b],
['a & b | c', {a, b, c}, a & b | c],
['a | b & c', {a, b, c}, a | b & c],
['a << 2', {a, b, c}, a << 2],
['a << 3', {a, b, c}, a << 3],
['a << 5', {a, b, c}, a << 5],
['a >> 2', {a, b, c}, a >> 2],
['a >> 3', {a, b, c}, a >> 3],
['a >> 6', {a, b, c}, a >> 6],
['a << 3 & b >> 2', {a, b, c}, a << 3 & b >> 2],
['a << 3 | b >> 2', {a, b, c}, a << 3 | b >> 2],
['a << 1 + 3', {a, b, c}, a << 1 + 3],
].forEach(([input, data, expected]) => {
const ast = parseExpression(input);
const result = ast.evalNode(new Scope(data));
expect(result).toBe(expected);
});
});

it('should fail parsing multiple expressions on one line', () => {
expect(() => {
parseExpression('a = b + c 45 - 3');
Expand Down

0 comments on commit 51e8680

Please sign in to comment.