diff --git a/.gitignore b/.gitignore index 98de768..83d1a01 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +build dist cache node_modules diff --git a/binding.gyp b/binding.gyp new file mode 100644 index 0000000..3a41ba1 --- /dev/null +++ b/binding.gyp @@ -0,0 +1,16 @@ +{ + "targets": [ + { + "target_name": "asajs-compiler", + "sources": [ + "src/native/main.cc", + ], + + "dependencies": [ + "= 21" + } + }, + "node_modules/node-api-headers": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/node-api-headers/-/node-api-headers-1.7.0.tgz", + "integrity": "sha512-uJMGdkhVwu9+I3UsVvI3KW6ICAy/yDfsu5Br9rSnTtY3WpoaComXvKloiV5wtx0Md2rn0B9n29Ys2WMNwWxj9A==", + "dev": true, + "license": "MIT" + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -50,7 +93,6 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, "license": "MIT" } } diff --git a/package.json b/package.json index f23656a..2d9425e 100644 --- a/package.json +++ b/package.json @@ -22,9 +22,9 @@ "scripts": { "build": "tsc", "dev": "tsc --watch", - "test": "bun test/app.ts", - "test:watch": "bun --watch test/app.ts", "prefetch": "bun scripts/prefetch", + "native:build": "node-gyp build", + "native:rebuild": "node-gyp rebuild", "vanilla:defs": "bun scripts/vanilladefs", "gen:enums": "bun scripts/enum", "gen:items": "bun scripts/items", @@ -32,9 +32,13 @@ }, "devDependencies": { "@types/node": "^25.0.3", + "node-api-headers": "^1.7.0", "typescript": "^5.9.3" }, "dependencies": { - "jsonc-parser": "^3.3.1" + "@types/bindings": "^1.5.5", + "bindings": "^1.5.0", + "jsonc-parser": "^3.3.1", + "node-addon-api": "^8.5.0" } } diff --git a/src/compilers/Bindings.ts b/src/compilers/Bindings.ts new file mode 100644 index 0000000..d1b999e --- /dev/null +++ b/src/compilers/Bindings.ts @@ -0,0 +1,14 @@ +import bindings from "bindings" +import { Token } from "./bindings/types.js" + +export const { + Lexer, + isBlankChar, + isWordChar, + isNumberChar, +}: { + Lexer: (input: string) => Token[] + isBlankChar: (char: string) => boolean + isWordChar: (char: string) => boolean + isNumberChar: (char: string) => boolean +} = bindings("asajs-compiler") diff --git a/src/compilers/bindings/Checker.ts b/src/compilers/bindings/Checker.ts index 542752c..3f985ee 100644 --- a/src/compilers/bindings/Checker.ts +++ b/src/compilers/bindings/Checker.ts @@ -9,3 +9,7 @@ export function isWordChar(char: string) { export function isNumberChar(char: string) { return /\d/.test(char) } + +export function isCompileBinding(input: string) { + return input.startsWith("[") && input.endsWith("]") +} diff --git a/src/compilers/bindings/Parser.ts b/src/compilers/bindings/Parser.ts index 6f9f6a9..2b9373a 100644 --- a/src/compilers/bindings/Parser.ts +++ b/src/compilers/bindings/Parser.ts @@ -1,8 +1,8 @@ +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 { Lexer } from "./Lexer.js" -import { Expression, GenBinding, Token, TokenKind, TSToken, TSTokenKind } from "./types.js" export class Parser { position: number = 0 @@ -240,14 +240,14 @@ export class Parser { this.eat() return this.funtionCall(callerToken.value, ...args) - } else if (left.kind === TokenKind.OPERATOR) { + } else if (left?.kind === TokenKind.OPERATOR) { this.warn( `Implicit string literal '${callerToken.value}'. Use quoted string ('${callerToken.value}') for clarity!`, callerToken, ) return callerToken.value } else { - this.expect(TokenKind.OPERATOR, "Unexpected token!") + this.expect(TokenKind.WORD, "Unexpected token!") return callerToken.value } } diff --git a/src/components/Modify.ts b/src/components/Modify.ts deleted file mode 100644 index 7edfa42..0000000 --- a/src/components/Modify.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { IntelliSense, Namespace, Element, VanillaType } from "../types/vanilla/intellisense.js" -import { paths } from "../types/vanilla/paths.js" -import { UI } from "./UI.js" - -export function Modify>(namespace: T, name: K) { - // @ts-ignore -- TS cannot prove this, but runtime guarantees it - return new UI>(undefined, name, namespace, paths[namespace][name]) -} diff --git a/src/components/UI.ts b/src/components/UI.ts index d4d8aaa..dcdc59c 100644 --- a/src/components/UI.ts +++ b/src/components/UI.ts @@ -1,8 +1,11 @@ +import { isCompileBinding } from "../compilers/bindings/Checker.js" +import { Parser } from "../compilers/bindings/Parser.js" import { FormatProperties } from "../compilers/FormatProperties.js" -import { Memory } from "../compilers/Memory.js" +import { BindingType } from "../types/enums/BindingType.js" import { Renderer } from "../types/enums/Renderer.js" import { Type } from "../types/enums/Type.js" import { Properties } from "../types/properties/components.js" +import { BindingItem } from "../types/properties/value.js" import { Class } from "./Class.js" import { RandomString } from "./Utils.js" @@ -18,6 +21,7 @@ export class UI extends Class extendable: boolean controls = new Map, Properties]>() + bindings: BindingItem[] = [] properties: Properties = {} constructor( @@ -47,6 +51,23 @@ export class UI extends Class return this } + addBindings(...bindings: 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() + if (gen) this.bindings.push(...gen) + binding.source_property_name = out + } + + binding.binding_type = BindingType.VIEW + + if (!binding.target_property_name) throw new Error("Binding must have a target property name") + } + this.bindings.push(binding) + } + } + addChild(child: UI, properties?: Properties, name?: string) { if (this === child) { throw new Error("Cannot add a child to itself") @@ -69,6 +90,10 @@ export class UI extends Class obj.type = this.type } + if (this.bindings.length) { + obj.bindings = this.bindings + } + if (this.controls.size) { obj.controls = [] this.controls.forEach((e, key) => obj.controls.push({ [key + e[0]]: e[1] })) @@ -78,7 +103,13 @@ export class UI extends Class } [util.inspect.custom]($: any, opts: any) { - const obj: any = FormatProperties(this.properties) + const obj: any = { + ...FormatProperties(this.properties), + } + + if (this.bindings.length) { + obj.bindings = this.bindings + } if (this.controls.size) { obj.controls = [] @@ -90,3 +121,10 @@ export class UI extends Class }\x1b[0m> \x1b[92m"${this}\x1b[92m"\x1b[0m ${util.inspect(obj, opts)}\n` } } + +export class ModifyUI extends UI { + constructor(namespace: string, name: string, path: string) { + if (!path) throw new Error("ModifyUI cannot have a path") + super(undefined, name, namespace, path) + } +} diff --git a/src/components/Utils.ts b/src/components/Utils.ts index 7f70ac8..108270a 100644 --- a/src/components/Utils.ts +++ b/src/components/Utils.ts @@ -1,6 +1,6 @@ import { Type } from "../types/enums/Type.js" import { Array3, Binding } from "../types/properties/value.js" -import { UI } from "./UI.js" +import { ModifyUI, UI } from "./UI.js" import { Renderer } from "../types/enums/Renderer.js" import { @@ -27,6 +27,8 @@ import { SliderBox, } from "../types/properties/components.js" import { ItemAuxID } from "../types/enums/Items.js" +import { Element, Namespace, VanillaType } from "../types/vanilla/intellisense.js" +import { paths } from "../types/vanilla/paths.js" const CHARS = "0123456789abcdefghijklmnopqrstuvwxyz" @@ -86,6 +88,11 @@ export function GetItemByAuxID(auxID: number) { } // Quick Elements +export function Modify>(namespace: T, name: K) { + // @ts-ignore -- TS cannot prove this, but runtime guarantees it + return new ModifyUI>(name, namespace, paths[namespace][name]) +} + export function Panel(properties?: Panel, name?: string, namespace?: string) { return new UI(Type.PANEL, name, namespace).setProperties(properties || {}) } diff --git a/src/index.ts b/src/index.ts index 2d6bf04..7e3c79a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,10 +3,11 @@ import "./compilers/RunEnd.js" export * from "./components/Animation.js" export * from "./components/UI.js" -export * from "./components/Modify.js" export * from "./components/Utils.js" export * from "./types/enums/index.js" export * as Properties from "./types/properties/index.js" +export * from "./compilers/Bindings.js" + export { ItemAuxID } from "./types/enums/Items.js" diff --git a/src/native/main.cc b/src/native/main.cc new file mode 100644 index 0000000..2d0e434 --- /dev/null +++ b/src/native/main.cc @@ -0,0 +1,131 @@ +#include +#include + +enum TokenKind { + VARIABLE, + NUMBER, + STRING, + TEMPLATE_STRING, + WORD, + + OPEN_PARENTHESIS, + CLOSE_PARENTHESIS, + + OPERATOR, + COMMA, +}; + +namespace Lexer { + bool isBlankChar(char c) { + std::regex pattern(R"(\s)"); + return std::regex_match(std::string(1, c), pattern); + }; + + bool isWordChar(char c) { + std::regex pattern(R"(\w)"); + return std::regex_match(std::string(1, c), pattern); + } + + bool isNumberChar(char c) { + std::regex pattern(R"(\d)"); + return std::regex_match(std::string(1, c), pattern); + } + + Napi::Object makeToken(Napi::Env &env, const std::string &input, TokenKind kind, size_t start, size_t length) { + Napi::Object token = Napi::Object::New(env); + + token.Set(Napi::String::New(env, "start"), Napi::Number::New(env, start)); + token.Set(Napi::String::New(env, "length"), Napi::Number::New(env, length)); + token.Set(Napi::String::New(env, "kind"), Napi::Number::New(env, kind)); + token.Set(Napi::String::New(env, "value"), Napi::String::New(env, input.substr(start, length))); + + return token; + } + + Napi::Array Lexer(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + Napi::Function consoleLog = env.Global().Get("console").As().Get("log").As(); + + if (info.Length() != 1) { + Napi::TypeError::New(env, "One string argument required").ThrowAsJavaScriptException(); + return Napi::Array::New(env, 0); + } + + if (!info[0].IsString()) { + Napi::TypeError::New(env, "Input must be a string").ThrowAsJavaScriptException(); + return Napi::Array::New(env, 0); + } + + const std::string input = info[0].As().Utf8Value(); + const size_t length = input.size(); + + if (length == 0) { + return Napi::Array::New(env, 0); + } + + std::vector tokens; + + size_t index = 0; + while (index < length) { + char c = input[index]; + + switch (c) { + default: { + size_t start = index; + + if (Lexer::isNumberChar(c)) { + while (Lexer::isNumberChar(input[index + 1])) index++; + tokens.push_back( + Lexer::makeToken(env, input, TokenKind::NUMBER, start, index - start + 1) + ); + } else if (Lexer::isWordChar(c)) { + while (Lexer::isWordChar(input[index + 1])) index++; + tokens.push_back( + Lexer::makeToken(env, input, TokenKind::WORD, start, index - start + 1) + ); + } else if (!Lexer::isBlankChar(c)) { + return Napi::Array::New(env, 0); + } + } + }; + + index++; + } + + size_t tokenLength = tokens.size(); + Napi::Array result = Napi::Array::New(env, tokenLength); + + for (size_t i = 0; i < tokenLength; i++) { + result.Set(i, tokens[i]); + } + + return result; + } +}; + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports.Set( + Napi::String::New(env, "Lexer"), + Napi::Function::New(env, Lexer::Lexer) + ); + + exports.Set( + Napi::String::New(env, "isBlankChar"), + Napi::Function::New(env, Lexer::isBlankChar) + ); + + exports.Set( + Napi::String::New(env, "isWordChar"), + Napi::Function::New(env, Lexer::isWordChar) + ); + + exports.Set( + Napi::String::New(env, "isNumberChar"), + Napi::Function::New(env, Lexer::isNumberChar) + ); + + return exports; +} + +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) \ No newline at end of file diff --git a/test/app.ts b/test/app.ts index ec5fa49..6356e7c 100644 --- a/test/app.ts +++ b/test/app.ts @@ -1,9 +1,3 @@ -import { Anchor, Modify, Properties } from ".." +import { AsaJSCompiler } from ".." -const vanilla = Modify("authentication_modals", "ad_modal_dialog").setProperties({ - ignored: true, - anchor: Anchor.CENTER, - offset: [10, 10], -}) - -console.log(vanilla) +console.log(AsaJSCompiler.Lexer("SAYGEX69"))