From 3cee8670446b9e4ca964e90d255aaef85e0c3935 Mon Sep 17 00:00:00 2001 From: Asaki Yuki Date: Fri, 30 Jan 2026 15:31:25 +0700 Subject: [PATCH] feat: bitwise operator to binding --- src/compilers/bindings/Funtion.ts | 24 +++-- src/compilers/bindings/Lexer.ts | 15 +-- src/compilers/bindings/Parser.ts | 152 ++++++++++++++++++++++++++++-- src/components/UI.ts | 9 +- src/components/Utils.ts | 4 +- src/index.ts | 2 + 6 files changed, 179 insertions(+), 27 deletions(-) diff --git a/src/compilers/bindings/Funtion.ts b/src/compilers/bindings/Funtion.ts index 854288b..70cf47f 100644 --- a/src/compilers/bindings/Funtion.ts +++ b/src/compilers/bindings/Funtion.ts @@ -1,4 +1,5 @@ import { RandomBindingString } from "../../components/Utils.js" +import { Parser } from "./Parser.js" import { Expression, GenBinding } from "./types.js" type Callback = (...args: Expression[]) => { @@ -6,14 +7,14 @@ type Callback = (...args: Expression[]) => { value: Expression } -export const FuntionMap = new Map() +export const FunctionMap = new Map() function callFn(name: string, ...args: Expression[]) { - return FuntionMap.get(name)!(...args) + return FunctionMap.get(name)!(...args) } // Default Functions -FuntionMap.set("abs", number => { +FunctionMap.set("abs", number => { const randomBinding = RandomBindingString(16) return { genBindings: [{ source: `((-1 + (${number} > 0) * 2) * ${number})`, target: randomBinding }], @@ -21,7 +22,7 @@ FuntionMap.set("abs", number => { } }) -FuntionMap.set("new", expression => { +FunctionMap.set("new", expression => { const randomBinding = RandomBindingString(16) return { genBindings: [{ source: expression, target: randomBinding }], @@ -29,7 +30,7 @@ FuntionMap.set("new", expression => { } }) -FuntionMap.set("sqrt", number => { +FunctionMap.set("sqrt", number => { const rtn = RandomBindingString(16), $1 = RandomBindingString(16), $2 = RandomBindingString(16) @@ -56,8 +57,19 @@ FuntionMap.set("sqrt", number => { } }) -FuntionMap.set("translatable", key => { +FunctionMap.set("translatable", key => { return { value: `'%' + ${key}`, } }) + +FunctionMap.set("bin", input => { + const { ret, bindings } = Parser.intToBin(input) + + bindings.push({ + source: `'§z' + ${Array.from({ length: 30 }, (_, i) => `${ret}${30 - i - 1}`).join(" + ")}`, + target: ret, + }) + + return { genBindings: bindings, value: ret } +}) diff --git a/src/compilers/bindings/Lexer.ts b/src/compilers/bindings/Lexer.ts index bfb5ca9..4870e53 100644 --- a/src/compilers/bindings/Lexer.ts +++ b/src/compilers/bindings/Lexer.ts @@ -47,6 +47,7 @@ export function Lexer(input: string, start: number = 0, end?: number) { case "*": case "/": case "%": + case "^": tokens.push(makeToken(input, TokenKind.OPERATOR, index)) break @@ -63,19 +64,19 @@ export function Lexer(input: string, start: number = 0, end?: number) { case "|": case "=": if (input[index + 1] === input[index]) tokens.push(makeToken(input, TokenKind.OPERATOR, index++, 2)) - else { - console.error( - `\x1b[31merror: ${input + "\n" + " ".repeat(index + 7) + "^"}\nInvalid character.\x1b[0m`, - ) - throw new Error() - } + else tokens.push(makeToken(input, TokenKind.OPERATOR, index)) break case "!": case ">": case "<": if (input[index + 1] === "=") tokens.push(makeToken(input, TokenKind.OPERATOR, index++, 2)) - else tokens.push(makeToken(input, TokenKind.OPERATOR, index)) + else { + if (input[index] === input[index + 1]) { + if (input[index] !== "!") tokens.push(makeToken(input, TokenKind.OPERATOR, index++, 2)) + else tokens.push(makeToken(input, TokenKind.OPERATOR, index)) + } else tokens.push(makeToken(input, TokenKind.OPERATOR, index)) + } break // string diff --git a/src/compilers/bindings/Parser.ts b/src/compilers/bindings/Parser.ts index 370decd..e3d7fa3 100644 --- a/src/compilers/bindings/Parser.ts +++ b/src/compilers/bindings/Parser.ts @@ -1,8 +1,9 @@ import { Expression, GenBinding, Token, TokenKind, TSToken, TSTokenKind } from "./types.js" import { BindingType } from "../../types/enums/BindingType.js" -import { BindingItem } from "../../types/properties/value.js" -import { FuntionMap } from "./Funtion.js" +import { Binding, BindingItem } from "../../types/properties/value.js" +import { FunctionMap } from "./Funtion.js" import { Lexer } from "./Lexer.js" +import { RandomBindingString } from "../../components/Utils.js" export class Parser { position: number = 0 @@ -11,7 +12,10 @@ export class Parser { genBindings: GenBinding[] = [] output: Expression - constructor(private input: string) { + constructor( + private input: string, + private cache = new Map(), + ) { this.tokens = Lexer(this.input) this.output = this.parseExpression() @@ -20,6 +24,36 @@ export class Parser { } } + static intToBin(input: string) { + const bindings: GenBinding[] = [] + const rtn = RandomBindingString(16) + + for (let i = 0; i < 30; i++) { + bindings.push({ + source: `(${input} / ${2 ** i} - (${input} / ${2 ** (i + 1)}) * 2)`, + target: `${rtn}${i}`, + }) + } + + return { + ret: rtn, + bindings, + } + } + + intToBin(input: string) { + const inStr = `expr:${input}` + + if (this.cache.has(inStr)) { + return this.cache.get(inStr) as string + } else { + const { ret, bindings } = Parser.intToBin(input) + this.cache.set(inStr, ret) + this.genBindings.push(...bindings) + return ret + } + } + at() { return this.tokens[this.position] } @@ -114,7 +148,7 @@ export class Parser { } private parseMultiplicativeExpression(): Expression { - let left = this.parsePrimaryExpression(), + let left = this.parseBitwiseLogicExpression(), current while ( @@ -132,6 +166,99 @@ export class Parser { return left } + private parseBitwiseLogicExpression(): Expression { + let left = this.parseBitwiseShiftExpression(), + current + + while ( + (current = this.at()) && + this.at()?.kind === TokenKind.OPERATOR && + ["&", "|", "^"].includes(current.value) + ) { + const operator = this.eat() + const right = this.parsePrimaryExpression() + + const ret = RandomBindingString(16) + + const leftBin = this.intToBin(left) + const rightBin = this.intToBin(right) + + if (operator.value === "&") { + this.genBindings.push( + ...Array.from({ length: 30 }, (_, i) => { + return { + source: `(${leftBin}${i} * ${rightBin}${i})`, + target: `${ret}${i}`, + } + }), + ) + } else if (operator.value === "|") { + this.genBindings.push( + ...Array.from({ length: 30 }, (_, i) => { + return { + source: `(${leftBin}${i} + ${rightBin}${i} - (${leftBin}${i} * ${rightBin}${i}))`, + target: `${ret}${i}`, + } + }), + ) + } else { + this.genBindings.push( + ...Array.from({ length: 30 }, (_, i) => { + return { + source: `(${leftBin}${i} + ${rightBin}${i} - 2 * (${leftBin}${i} * ${rightBin}${i}))`, + target: `${ret}${i}`, + } + }), + ) + } + + this.genBindings.push({ + source: `(${Array.from({ length: 30 }, (_, i) => `(${ret}${i} * ${2 ** i})`).join(" + ")})`, + target: ret, + }) + + left = ret + } + + return left + } + + private parseBitwiseShiftExpression(): Expression { + let left = this.parsePrimaryExpression(), + current + + while ( + (current = this.at()) && + this.at()?.kind === TokenKind.OPERATOR && + [">>", "<<"].includes(current.value) + ) { + const operator = this.eat() + const right = this.parsePrimaryExpression() + const ret = RandomBindingString(16) + const leftBind = this.intToBin(left) + + const op = operator.value === "<<" ? "-" : "+" + + this.genBindings.push( + ...Array.from({ length: 30 }, (_, i) => { + return { + source: `((0 * ${left}) + ('${leftBind}' + (${i} ${op} ${right})))`, + target: `${ret}${i}`, + } + }), + ) + + this.genBindings.push({ + source: `(${Array.from({ length: 30 }, (_, i) => `(${ret}${i} * ${2 ** i})`).join(" + ")})`, + target: ret, + }) + + left = ret + } + + return left + } + private parsePrimaryExpression(): Expression { const left = this.at() @@ -247,7 +374,15 @@ export class Parser { this.eat() - return this.funtionCall(callerToken.value, ...args) + const inputStr = `func:${callerToken.value}(${args.join(", ")})` + + if (this.cache.has(inputStr)) { + return this.cache.get(inputStr) as Expression + } else { + const ret = this.functionCall(callerToken.value, ...args) + this.cache.set(inputStr, ret) + return ret + } } else if (left?.kind === TokenKind.OPERATOR) { this.warn( `Implicit string literal '${callerToken.value}'. Use quoted string ('${callerToken.value}') for clarity!`, @@ -265,8 +400,8 @@ export class Parser { } } - private funtionCall(name: string, ...params: Expression[]): Expression { - const func = FuntionMap.get(name.toLowerCase()) + private functionCall(name: string, ...params: Expression[]): Expression { + const func = FunctionMap.get(name.toLowerCase()) if (!func) { return this.expect(TokenKind.WORD, "Function not found!")! } else { @@ -294,9 +429,10 @@ export class Parser { return this.input + "\n" + " ".repeat(token.start + (isWarn ? 5 : 7)) + "^".repeat(token.length) } - out(): { gen?: BindingItem[]; out: Expression } { + out(): { gen?: BindingItem[]; out: Expression; cache: Map } { return { out: `(${this.output})`, + cache: this.cache, gen: this.genBindings.map( ({ source, target }) => { diff --git a/src/components/UI.ts b/src/components/UI.ts index 0f42cd5..f622d16 100644 --- a/src/components/UI.ts +++ b/src/components/UI.ts @@ -10,7 +10,7 @@ import { BindingItem, ButtonMapping, ModificationItem, VariableItem, Variables } import { Animation } from "./Animation.js" import { AnimationKeyframe } from "./AnimationKeyframe.js" import { Class } from "./Class.js" -import { ExtendsOf, RandomString, ResolveBinding } from "./Utils.js" +import { RandomString, ResolveBinding } from "./Utils.js" import { RandomNamespace } from "../compilers/Random.js" import util from "node:util" @@ -37,6 +37,7 @@ export class UI extends Class protected readonly extendType?: Type protected properties: Properties = {} + protected bindCache = new Map() constructor( public type?: T, @@ -83,7 +84,7 @@ export class UI extends Class * @returns */ addBindings(...bindings: BindingItem[]) { - this.bindings.push(...ResolveBinding(...bindings)) + this.bindings.push(...ResolveBinding(this.bindCache, ...bindings)) return this } @@ -336,7 +337,7 @@ export class ModifyUI ex return this.addModifications({ array_name: ArrayName.BINDINGS, operation: Operation.INSERT_BACK, - value: ResolveBinding(...bindings), + value: ResolveBinding(this.bindCache, ...bindings), }) } @@ -344,7 +345,7 @@ export class ModifyUI ex return this.addModifications({ array_name: ArrayName.BINDINGS, operation: Operation.INSERT_FRONT, - value: ResolveBinding(...bindings), + value: ResolveBinding(this.bindCache, ...bindings), }) } diff --git a/src/components/Utils.ts b/src/components/Utils.ts index bbf3e16..5615e96 100644 --- a/src/components/Utils.ts +++ b/src/components/Utils.ts @@ -68,13 +68,13 @@ export function Color(hex: string | number): Array3 { } } -export function ResolveBinding(...bindings: BindingItem[]) { +export function ResolveBinding(cache: Map, ...bindings: BindingItem[]) { const result: BindingItem[] = [] for (const binding of bindings) { if (binding.source_property_name) { if (isCompileBinding(binding.source_property_name)) { - const { gen, out } = new Parser(binding.source_property_name.slice(1, -1)).out() + const { gen, out } = new Parser(binding.source_property_name.slice(1, -1), cache).out() if (gen) result.push(...gen) binding.source_property_name = out } diff --git a/src/index.ts b/src/index.ts index cbd3433..cd215b2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,3 +14,5 @@ export * as Properties from "./types/properties/index.js" export { ItemAuxID } from "./types/enums/Items.js" export { ArrayName, Operation } from "./types/properties/index.js" + +export { Lexer } from "./compilers/bindings/Lexer.js"