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 { RandomBindingString } from "../../components/Utils.js"
import { Parser } from "./Parser.js"
import { Expression, GenBinding } from "./types.js" import { Expression, GenBinding } from "./types.js"
type Callback = (...args: Expression[]) => { type Callback = (...args: Expression[]) => {
@ -6,14 +7,14 @@ type Callback = (...args: Expression[]) => {
value: Expression value: Expression
} }
export const FuntionMap = new Map<string, Callback>() export const FunctionMap = new Map<string, Callback>()
function callFn(name: string, ...args: Expression[]) { function callFn(name: string, ...args: Expression[]) {
return FuntionMap.get(name)!(...args) return FunctionMap.get(name)!(...args)
} }
// Default Functions // Default Functions
FuntionMap.set("abs", number => { FunctionMap.set("abs", number => {
const randomBinding = RandomBindingString(16) const randomBinding = RandomBindingString(16)
return { return {
genBindings: [{ source: `((-1 + (${number} > 0) * 2) * ${number})`, target: randomBinding }], 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) const randomBinding = RandomBindingString(16)
return { return {
genBindings: [{ source: expression, target: randomBinding }], 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), const rtn = RandomBindingString(16),
$1 = RandomBindingString(16), $1 = RandomBindingString(16),
$2 = RandomBindingString(16) $2 = RandomBindingString(16)
@ -56,8 +57,19 @@ FuntionMap.set("sqrt", number => {
} }
}) })
FuntionMap.set("translatable", key => { FunctionMap.set("translatable", key => {
return { return {
value: `'%' + ${key}`, 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 "/":
case "%": case "%":
case "^":
tokens.push(makeToken(input, TokenKind.OPERATOR, index)) tokens.push(makeToken(input, TokenKind.OPERATOR, index))
break break
@ -63,19 +64,19 @@ export function Lexer(input: string, start: number = 0, end?: number) {
case "|": case "|":
case "=": case "=":
if (input[index + 1] === input[index]) tokens.push(makeToken(input, TokenKind.OPERATOR, index++, 2)) if (input[index + 1] === input[index]) tokens.push(makeToken(input, TokenKind.OPERATOR, index++, 2))
else { else tokens.push(makeToken(input, TokenKind.OPERATOR, index))
console.error(
`\x1b[31merror: ${input + "\n" + " ".repeat(index + 7) + "^"}\nInvalid character.\x1b[0m`,
)
throw new Error()
}
break break
case "!": case "!":
case ">": case ">":
case "<": case "<":
if (input[index + 1] === "=") tokens.push(makeToken(input, TokenKind.OPERATOR, index++, 2)) 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 break
// string // string

View file

@ -1,8 +1,9 @@
import { Expression, GenBinding, Token, TokenKind, TSToken, TSTokenKind } from "./types.js" import { Expression, GenBinding, Token, TokenKind, TSToken, TSTokenKind } from "./types.js"
import { BindingType } from "../../types/enums/BindingType.js" import { BindingType } from "../../types/enums/BindingType.js"
import { BindingItem } from "../../types/properties/value.js" import { Binding, BindingItem } from "../../types/properties/value.js"
import { FuntionMap } from "./Funtion.js" import { FunctionMap } from "./Funtion.js"
import { Lexer } from "./Lexer.js" import { Lexer } from "./Lexer.js"
import { RandomBindingString } from "../../components/Utils.js"
export class Parser { export class Parser {
position: number = 0 position: number = 0
@ -11,7 +12,10 @@ export class Parser {
genBindings: GenBinding[] = [] genBindings: GenBinding[] = []
output: Expression output: Expression
constructor(private input: string) { constructor(
private input: string,
private cache = new Map<string, unknown>(),
) {
this.tokens = Lexer(this.input) this.tokens = Lexer(this.input)
this.output = this.parseExpression() 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() { at() {
return this.tokens[this.position] return this.tokens[this.position]
} }
@ -114,7 +148,7 @@ export class Parser {
} }
private parseMultiplicativeExpression(): Expression { private parseMultiplicativeExpression(): Expression {
let left = this.parsePrimaryExpression(), let left = this.parseBitwiseLogicExpression(),
current current
while ( while (
@ -132,6 +166,99 @@ export class Parser {
return left 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 { private parsePrimaryExpression(): Expression {
const left = this.at() const left = this.at()
@ -247,7 +374,15 @@ export class Parser {
this.eat() 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) { } else if (left?.kind === TokenKind.OPERATOR) {
this.warn( this.warn(
`Implicit string literal '${callerToken.value}'. Use quoted string ('${callerToken.value}') for clarity!`, `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 { private functionCall(name: string, ...params: Expression[]): Expression {
const func = FuntionMap.get(name.toLowerCase()) const func = FunctionMap.get(name.toLowerCase())
if (!func) { if (!func) {
return this.expect(TokenKind.WORD, "Function not found!")! return this.expect(TokenKind.WORD, "Function not found!")!
} else { } else {
@ -294,9 +429,10 @@ export class Parser {
return this.input + "\n" + " ".repeat(token.start + (isWarn ? 5 : 7)) + "^".repeat(token.length) 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 { return {
out: `(${this.output})`, out: `(${this.output})`,
cache: this.cache,
gen: this.genBindings.map( gen: this.genBindings.map(
({ source, target }) => ({ source, target }) =>
<BindingItem>{ <BindingItem>{

View file

@ -10,7 +10,7 @@ import { BindingItem, ButtonMapping, ModificationItem, VariableItem, Variables }
import { Animation } from "./Animation.js" import { Animation } from "./Animation.js"
import { AnimationKeyframe } from "./AnimationKeyframe.js" import { AnimationKeyframe } from "./AnimationKeyframe.js"
import { Class } from "./Class.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 { RandomNamespace } from "../compilers/Random.js"
import util from "node:util" 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 readonly extendType?: Type
protected properties: Properties<T, K> = <any>{} protected properties: Properties<T, K> = <any>{}
protected bindCache = new Map<string, unknown>()
constructor( constructor(
public type?: T, public type?: T,
@ -83,7 +84,7 @@ export class UI<T extends Type, K extends Renderer | null = null> extends Class
* @returns * @returns
*/ */
addBindings(...bindings: BindingItem[]) { addBindings(...bindings: BindingItem[]) {
this.bindings.push(...ResolveBinding(...bindings)) this.bindings.push(...ResolveBinding(this.bindCache, ...bindings))
return this return this
} }
@ -336,7 +337,7 @@ export class ModifyUI<T extends Type = Type.PANEL, S extends string = string> ex
return this.addModifications({ return this.addModifications({
array_name: ArrayName.BINDINGS, array_name: ArrayName.BINDINGS,
operation: Operation.INSERT_BACK, 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({ return this.addModifications({
array_name: ArrayName.BINDINGS, array_name: ArrayName.BINDINGS,
operation: Operation.INSERT_FRONT, 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[] = [] const result: BindingItem[] = []
for (const binding of bindings) { for (const binding of bindings) {
if (binding.source_property_name) { if (binding.source_property_name) {
if (isCompileBinding(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) if (gen) result.push(...gen)
binding.source_property_name = out 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 { ItemAuxID } from "./types/enums/Items.js"
export { ArrayName, Operation } from "./types/properties/index.js" export { ArrayName, Operation } from "./types/properties/index.js"
export { Lexer } from "./compilers/bindings/Lexer.js"