diff --git a/package.json b/package.json index 7defb2b..427f9d8 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,9 @@ "types": "dist/index.d.ts", "scripts": { "build": "npx tsc", - "build-watch": "npx tsc --watch", + "dev": "npx tsc --watch", "test": "bun test/app.ts", - "test-watch": "bun --watch test/app.ts", + "test:watch": "bun --watch test/app.ts", "prefetch": "bun scripts/prefetch" }, "devDependencies": { diff --git a/src/compilers/bindings/Funtion.ts b/src/compilers/bindings/Funtion.ts index 8751990..964c23f 100644 --- a/src/compilers/bindings/Funtion.ts +++ b/src/compilers/bindings/Funtion.ts @@ -34,7 +34,7 @@ FuntionMap.set("sqrt", number => { $1 = RandomBindingString(16), $2 = RandomBindingString(16) - const { genBindings: absValue, value: absRtn } = callFn("abs") + const { genBindings: absValue, value: absRtn } = callFn("abs", number) return { genBindings: [ diff --git a/src/compilers/bindings/Lexer.ts b/src/compilers/bindings/Lexer.ts index cd12faa..c56127a 100644 --- a/src/compilers/bindings/Lexer.ts +++ b/src/compilers/bindings/Lexer.ts @@ -1,12 +1,12 @@ -import { makeToken, TokenKind, Token } from "./types.js" +import { makeToken, TokenKind, Token, TSToken, TSTokenKind } from "./types.js" import * as Checker from "./Checker.js" -export function Lexer(input: string) { +export function Lexer(input: string, start: number = 0, end?: number) { const tokens: Token[] = [] if (input.length === 0) return tokens - let index = 0 + let index = start do { const token = input[index] @@ -43,31 +43,88 @@ export function Lexer(input: string) { } case "`": { - const start = index++, - struct: boolean[] = [] + const tsTokens: TSToken[] = [] + const start = index - do { - const token = input[index] - let lastStruct = struct.lastItem() - - if (token === "`") { - if (struct.length) { - if (lastStruct === false) struct.pop() - else struct.push(false) - } else break - } - - if (token === "$") { - if (input[index + 1] === "{" && !lastStruct) { - struct.push(true) + const tokenization = (start: number) => { + while (index < input.length) { + const char = input[index] + if (char === "`") { index++ + eatString() + } else if (char === "}") { + tsTokens.push({ + kind: TSTokenKind.EXPRESSION, + tokens: Lexer(input, start + 1, index), + }) + break } + index++ } + } - if (token === "}" && lastStruct === true) struct.pop() - } while (++index < input.length) + const stringification = (start: number) => { + while (index < input.length) { + const char = input[index] + if (char === "`") { + if (start + 1 !== index) + tsTokens.push({ + kind: TSTokenKind.STRING, + tokens: { + kind: TokenKind.STRING, + start: start + 1, + length: index - start + 1, + value: `'${input.slice(start + 1, index)}'`, + }, + }) - tokens.push(makeToken(input, TokenKind.TEMPLATE_STRING, start, index - start + 1)) + break + } else if (char === "$" && input[index + 1] === "{") { + tsTokens.push({ + kind: TSTokenKind.STRING, + tokens: { + value: `'${input.slice(start + 1, index)}'`, + kind: TokenKind.STRING, + length: index - start + 1, + start, + }, + }) + tokenization(++index) + start = index + } + index++ + } + } + + const eatString = () => { + while (index < input.length) { + const char = input[index] + + if (char === "`") { + break + } else if (char === "$" && input[index + 1] === "{") { + index++ + eatTemplate() + } + + index++ + } + } + + const eatTemplate = () => { + while (index < input.length) { + const char = input[index] + if (char === "`") { + eatString() + } else if (char === "}") { + break + } + index++ + } + } + + stringification(index++) + tokens.push(makeToken(tsTokens, TokenKind.TEMPLATE_STRING, start, index - start + 1)) break } @@ -117,10 +174,15 @@ export function Lexer(input: string) { } else if (Checker.isWordChar(token)) { while (Checker.isWordChar(input[index + 1])) index++ tokens.push(makeToken(input, TokenKind.WORD, start, index - start + 1)) + } else if (!Checker.isBlankChar(token)) { + console.error( + `\x1b[31m${input.slice(0, index)}>>>${token}<<<${input.slice(index + 1)}\nInvalid character.\x1b[0m`, + ) + throw new Error() } } } - } while (++index < input.length) + } while (++index < (end || input.length)) return tokens } diff --git a/src/compilers/bindings/Parser.ts b/src/compilers/bindings/Parser.ts index 7c62930..65f435a 100644 --- a/src/compilers/bindings/Parser.ts +++ b/src/compilers/bindings/Parser.ts @@ -2,7 +2,7 @@ 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 } from "./types.js" +import { Expression, GenBinding, Token, TokenKind, TSToken, TSTokenKind } from "./types.js" export class Parser { position: number = 0 @@ -29,7 +29,74 @@ export class Parser { } private parseExpression(): Expression { - return this.parseAdditiveExpression() + return this.parseOrExpression() + } + + private parseOrExpression(): Expression { + let left = this.parseAndExpression(), + current + + while ( + (current = this.at()) && + this.at().kind === TokenKind.OPERATOR && + ["||"].includes(current.value) + ) { + this.eat() + left = `(${left} or ${this.parseAndExpression()})` + } + + return left + } + + private parseAndExpression(): Expression { + let left = this.parseComparisonExpression(), + current + + while ( + (current = this.at()) && + this.at().kind === TokenKind.OPERATOR && + ["&&"].includes(current.value) + ) { + this.eat() + left = `(${left} and ${this.parseComparisonExpression()})` + } + + return left + } + + private parseComparisonExpression(): Expression { + let left = this.parseAdditiveExpression(), + current + + while ( + (current = this.at()) && + this.at().kind === TokenKind.OPERATOR && + ["==", ">", "<", ">=", "<=", "!="].includes(current.value) + ) { + const operator = this.eat() + const right = this.parseAdditiveExpression() + + switch (operator.value) { + case "==": + left = `(${left} = ${right})` + break + + case "!=": + left = `(not (${left} = ${right}))` + break + + case ">=": + case "<=": + left = `((${left} ${operator.value[0]} ${right}) or (${left} = ${right}))` + break + + default: + left = `(${left} ${operator.value} ${right})` + break + } + } + + return left } private parseAdditiveExpression(): Expression { @@ -39,37 +106,12 @@ export class Parser { while ( (current = this.at()) && this.at()?.kind === TokenKind.OPERATOR && - ["+", "-", "==", ">", "<", ">=", "<=", "!=", "&&", "||"].includes(current.value) + ["+", "-"].includes(current.value) ) { const operator = this.eat() const right = this.parseMultiplicativeExpression() - switch (operator.value) { - case "==": - left = `(${left} = ${right})` - break - - case ">=": - case "<=": - left = `((${left} ${operator.value[0]} ${right}) or (${left} = ${right}))` - break - - case "!=": - left = `(not (${left} = ${right}))` - break - - case "&&": - left = `(${left} and ${right})` - break - - case "||": - left = `(${left} or ${right})` - break - - default: - left = `(${left} ${operator.value} ${right})` - break - } + left = `(${left} ${operator.value} ${right})` } return left @@ -82,7 +124,7 @@ export class Parser { while ( (current = this.at()) && this.at()?.kind === TokenKind.OPERATOR && - ["*", "/", "%"].includes(current.value) + ["*", "/", "%"].includes(current.value) ) { const operator = this.eat() const right = this.parsePrimaryExpression() @@ -99,7 +141,7 @@ export class Parser { switch (left?.kind) { case TokenKind.WORD: - return this.parseCallExpression(this.eat()) + return this.parseCallableOrLiteral(this.eat()) case TokenKind.OPEN_PARENTHESIS: { this.eat() @@ -109,6 +151,32 @@ export class Parser { return `(${value})` } + case TokenKind.VARIABLE: + case TokenKind.NUMBER: + case TokenKind.STRING: + return this.eat().value + + case TokenKind.TEMPLATE_STRING: + return `(${(this.eat().value) + .map(v => { + if (v.kind === TSTokenKind.STRING) return v.tokens.value + else { + const bakTokens = this.tokens + const bakPosition = this.position + + this.tokens = v.tokens + this.position = 0 + + const out = this.parseExpression() + + this.tokens = bakTokens + this.position = bakPosition + + return out + } + }) + .join(" + ")})` + case TokenKind.OPERATOR: { if (left.value === "-" || left.value === "+") { this.eat() @@ -144,35 +212,35 @@ export class Parser { const value = this.parsePrimaryExpression() return not ? `(not ${value})` : value - } else break + } } - case TokenKind.VARIABLE: - case TokenKind.NUMBER: - case TokenKind.STRING: - return this.eat().value - - case undefined: + default: + console.log(left) this.expect(TokenKind.NUMBER, "Unexpected token!") } - return left.value + return left.value } - private parseCallExpression(callerToken: Token): Expression { + private parseCallableOrLiteral(callerToken: Token): Expression { const left = this.at() if (left?.kind === TokenKind.OPEN_PARENTHESIS) { this.eat() const args: Expression[] = [] + if (this.at().kind === TokenKind.COMMA) { + this.expect(TokenKind.NUMBER, "Unexpected token!") + } + if (this.at().kind !== TokenKind.CLOSE_PARENTHESIS) { args.push(this.parseExpression()) while (this.at().kind === TokenKind.COMMA) { this.eat() if (this.at().kind === TokenKind.CLOSE_PARENTHESIS) { - this.expect(TokenKind.CLOSE_PARENTHESIS, "Unexpected token!") + this.expect(TokenKind.NUMBER, "Unexpected token!") } args.push(this.parseExpression()) } @@ -180,15 +248,18 @@ export class Parser { this.eat() - return this.funtionCall(callerToken.value, ...args) + return this.funtionCall(callerToken.value, ...args) } else { - this.warn("This token should be a string!", callerToken) - return callerToken.value + this.warn( + `Implicit string literal '${callerToken.value}'. Use quoted string ('${callerToken.value}') for clarity!`, + callerToken, + ) + return callerToken.value } } private funtionCall(name: string, ...params: Expression[]): Expression { - const func = FuntionMap.get(name) + const func = FuntionMap.get(name.toLowerCase()) if (!func) { return this.expect(TokenKind.WORD, "Function not found!")! } else { @@ -202,7 +273,7 @@ export class Parser { const prev = this.at() || this.last() if (!prev || prev.kind !== kind) { throw new Error( - `\x1b[31m${this.getPointer(prev)}\n` + `[ERROR]: ${err}\x1b[0m - Expected ${TokenKind[kind]}` + `\x1b[31m${this.getPointer(prev)}\n` + `[ERROR]: ${err}\x1b[0m - Expected ${TokenKind[kind]}`, ) } } @@ -215,7 +286,7 @@ export class Parser { private getPointer(token: Token) { return `${this.input.slice(0, token.start)}>>>${this.input.slice( token.start, - token.start + token.length + token.start + token.length, )}<<<${this.input.slice(token.start + token.length)}` } @@ -228,7 +299,7 @@ export class Parser { binding_type: BindingType.VIEW, source_property_name: `(${source})`, target_property_name: `${target}`, - } + }, ), } } diff --git a/src/compilers/bindings/types.ts b/src/compilers/bindings/types.ts index 3b8fca7..e8d95c9 100644 --- a/src/compilers/bindings/types.ts +++ b/src/compilers/bindings/types.ts @@ -12,27 +12,50 @@ export enum TokenKind { COMMA, } -export enum GroupType { - FUNCTION_CALL, - FUNCTION_PARAMS, - OPERATOR_SCOPE, +export enum TSTokenKind { + STRING, + EXPRESSION, } -export interface Token { - kind: TokenKind - value: string +export type TSToken = + | { + kind: TSTokenKind.EXPRESSION + tokens: Token[] + } + | { + kind: TSTokenKind.STRING + tokens: Token + } + +export interface BaseToken { start: number length: number } +export interface NormalToken extends BaseToken { + value: string + kind: Exclude +} + +export interface TemplateToken extends BaseToken { + value: TSToken[] + kind: TokenKind.TEMPLATE_STRING +} + +export type Token = NormalToken | TemplateToken + export type Expression = string -export function makeToken(input: string, kind: TokenKind, start: number, length: number = 1): Token { - return { - value: input.slice(start, start + length), - kind, - start, - length, +export function makeToken( + input: T extends TokenKind.TEMPLATE_STRING ? TSToken[] : string, + kind: T, + start: number, + length: number = 1, +): Token { + if (kind === TokenKind.TEMPLATE_STRING) { + return { value: input as TSToken[], kind: kind, start, length } + } else { + return { value: input.slice(start, start + length) as string, kind, start, length } } } diff --git a/test/app.ts b/test/app.ts index ef2d080..ddbd984 100644 --- a/test/app.ts +++ b/test/app.ts @@ -1,5 +1,4 @@ -import { Parser, Panel } from ".." +import { Lexer, Parser } from ".." -const { gen, out } = new Parser("abs(#a)").out() - -console.log(gen, out) +const { out } = new Parser("`A${`#a${#a + #b}`}A`").out() +console.log(out)