port lexer to cpp

This commit is contained in:
Asaki Yuki 2026-01-18 20:46:18 +07:00
parent cacc641378
commit b3b21f3101
13 changed files with 276 additions and 32 deletions

14
src/compilers/Bindings.ts Normal file
View file

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

View file

@ -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("]")
}

View file

@ -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(<string>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 <string>callerToken.value
} else {
this.expect(TokenKind.OPERATOR, "Unexpected token!")
this.expect(TokenKind.WORD, "Unexpected token!")
return <string>callerToken.value
}
}

View file

@ -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<T extends Namespace, K extends Element<T>>(namespace: T, name: K) {
// @ts-ignore -- TS cannot prove this, but runtime guarantees it
return new UI<VanillaType<T, K>>(undefined, name, namespace, paths[namespace][name])
}

View file

@ -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<T extends Type, K extends Renderer | null = null> extends Class
extendable: boolean
controls = new Map<string, [UI<Type, Renderer | null>, Properties<Type, Renderer | null>]>()
bindings: BindingItem[] = []
properties: Properties<T, K> = <any>{}
constructor(
@ -47,6 +51,23 @@ export class UI<T extends Type, K extends Renderer | null = null> 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<T extends Type, K extends Renderer | null>(child: UI<T, K>, properties?: Properties<T, K>, name?: string) {
if (this === <any>child) {
throw new Error("Cannot add a child to itself")
@ -69,6 +90,10 @@ export class UI<T extends Type, K extends Renderer | null = null> 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<T extends Type, K extends Renderer | null = null> 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<T extends Type, K extends Renderer | null = null> extends Class
}\x1b[0m> \x1b[92m"${this}\x1b[92m"\x1b[0m ${util.inspect(obj, opts)}\n`
}
}
export class ModifyUI<T extends Type> extends UI<T, null> {
constructor(namespace: string, name: string, path: string) {
if (!path) throw new Error("ModifyUI cannot have a path")
super(undefined, name, namespace, path)
}
}

View file

@ -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<T extends Namespace, K extends Element<T>>(namespace: T, name: K) {
// @ts-ignore -- TS cannot prove this, but runtime guarantees it
return new ModifyUI<VanillaType<T, K>>(name, namespace, paths[namespace][name])
}
export function Panel(properties?: Panel, name?: string, namespace?: string) {
return new UI(Type.PANEL, name, namespace).setProperties(properties || {})
}

View file

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

131
src/native/main.cc Normal file
View file

@ -0,0 +1,131 @@
#include <napi.h>
#include <regex>
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<Napi::Object>().Get("log").As<Napi::Function>();
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<Napi::String>().Utf8Value();
const size_t length = input.size();
if (length == 0) {
return Napi::Array::New(env, 0);
}
std::vector<Napi::Object> 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)