feat: bitwise operator to binding

This commit is contained in:
Asaki Yuki 2026-01-30 15:31:25 +07:00
parent a2ad1bf977
commit 3cee867044
6 changed files with 179 additions and 27 deletions

View file

@ -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<string, Callback>()
export const FunctionMap = new Map<string, Callback>()
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 }
})

View file

@ -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 {
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

View file

@ -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<string, unknown>(),
) {
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(<string>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(<string>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(<string>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(<string>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<string, unknown> } {
return {
out: `(${this.output})`,
cache: this.cache,
gen: this.genBindings.map(
({ source, target }) =>
<BindingItem>{

View file

@ -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<T extends Type, K extends Renderer | null = null> extends Class
protected readonly extendType?: Type
protected properties: Properties<T, K> = <any>{}
protected bindCache = new Map<string, unknown>()
constructor(
public type?: T,
@ -83,7 +84,7 @@ export class UI<T extends Type, K extends Renderer | null = null> 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<T extends Type = Type.PANEL, S extends string = string> 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<T extends Type = Type.PANEL, S extends string = string> ex
return this.addModifications({
array_name: ArrayName.BINDINGS,
operation: Operation.INSERT_FRONT,
value: ResolveBinding(...bindings),
value: ResolveBinding(this.bindCache, ...bindings),
})
}

View file

@ -68,13 +68,13 @@ export function Color(hex: string | number): Array3<number> {
}
}
export function ResolveBinding(...bindings: BindingItem[]) {
export function ResolveBinding(cache: Map<string, unknown>, ...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
}

View file

@ -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"