From 1452b2e00e2a85ad40124f90f8c70919f89413c8 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Wed, 17 Dec 2025 09:58:02 -0800 Subject: [PATCH] Formatted EVERYTHING with prettier --- bin/parser-tree.ts | 20 +- package.json | 2 +- src/compiler/compiler.ts | 56 +- src/compiler/compilerError.ts | 6 +- src/compiler/tests/compiler.test.ts | 22 +- src/compiler/tests/function-blocks.test.ts | 11 +- src/compiler/tests/literals.test.ts | 14 +- src/compiler/tests/native-exceptions.test.ts | 4 +- src/compiler/tests/ribbit.test.ts | 58 +- src/compiler/tests/while.test.ts | 14 +- src/compiler/utils.ts | 43 +- src/editor/editor.css | 4 +- src/editor/editor.tsx | 2 +- src/editor/plugins/catchErrors.ts | 2 +- src/editor/plugins/debugTags.ts | 2 +- src/editor/plugins/errors.ts | 2 +- src/editor/plugins/inlineHints.tsx | 2 +- src/editor/plugins/persistence.ts | 2 +- src/editor/plugins/theme.tsx | 2 +- src/index.ts | 140 +- src/parser/curlyTokenizer.ts | 5 +- src/parser/node.ts | 48 +- src/parser/parser2.ts | 197 ++- src/parser/stringParser.ts | 25 +- src/parser/tests/control-flow.test.ts | 5 +- src/parser/tests/destructuring.test.ts | 2 +- src/parser/tests/function-blocks.test.ts | 31 +- src/parser/tests/functions.test.ts | 3 +- src/parser/tests/import.test.ts | 2 +- src/parser/tests/pipes.test.ts | 2 +- src/parser/tests/strings.test.ts | 2 +- src/parser/tests/tokens.test.ts | 67 +- src/parser/tokenizer2.ts | 128 +- src/prelude/date.ts | 18 +- src/prelude/dict.ts | 12 +- src/prelude/fs.ts | 76 +- src/prelude/index.ts | 54 +- src/prelude/json.ts | 5 +- src/prelude/list.ts | 21 +- src/prelude/load.ts | 5 +- src/prelude/math.ts | 2 +- src/prelude/str.ts | 20 +- src/prelude/tests/date.test.ts | 2 +- src/prelude/tests/fs.test.ts | 10 +- src/prelude/tests/json.test.ts | 28 +- src/prelude/tests/prelude.test.ts | 10 +- src/prelude/types.ts | 1 - src/server/index.css | 72 +- src/server/index.html | 2 +- src/testSetup.ts | 36 +- vscode-extension/.vscode/launch.json | 5 +- vscode-extension/client/src/extension.ts | 8 +- vscode-extension/package.json | 2 +- .../scripts/generate-prelude-metadata.ts | 2 +- .../src/completion/completionProvider.ts | 2 +- .../server/src/completion/contextAnalyzer.ts | 2 +- .../src/metadata/prelude-completions.ts | 1205 ++++++----------- .../server/src/metadata/prelude-names.ts | 80 +- vscode-extension/server/src/semanticTokens.ts | 14 +- vscode-extension/server/src/server.ts | 2 +- vscode-extension/server/src/signatureHelp.ts | 10 +- 61 files changed, 1176 insertions(+), 1455 deletions(-) diff --git a/bin/parser-tree.ts b/bin/parser-tree.ts index cf5ee9c..68d59b2 100755 --- a/bin/parser-tree.ts +++ b/bin/parser-tree.ts @@ -1,6 +1,6 @@ #!/usr/bin/env bun -// WARNING: [[ No human has been anywhere near this file. It's pure Claude slop. +// WARNING: [[ No human has been anywhere near this file. It's pure Claude slop. // Enter at your own risk. ]] import { readFileSync } from 'fs' @@ -42,7 +42,7 @@ function analyzeParser(filePath: string): Map { methods.set(currentMethod, { method: currentMethod, line: i + 1, - calls: new Set() + calls: new Set(), }) } } @@ -85,7 +85,7 @@ function buildTree( indent = '', isLast = true, depth = 0, - maxDepth = 3 + maxDepth = 3, ): string[] { const lines: string[] = [] const info = callGraph.get(method) @@ -93,7 +93,7 @@ function buildTree( if (!info) return lines // Add current method - const prefix = depth === 0 ? '' : (isLast ? '└─> ' : '├─> ') + const prefix = depth === 0 ? '' : isLast ? '└─> ' : '├─> ' const suffix = info.isRecursive ? ' (recursive)' : '' const lineNum = `[line ${info.line}]` lines.push(`${indent}${prefix}${method}() ${lineNum}${suffix}`) @@ -116,9 +116,9 @@ function buildTree( // Get sorted unique calls (filter out recursive self-calls for display) const calls = Array.from(info.calls) - .filter(c => callGraph.has(c)) // Only show parser methods - .filter(c => c !== method) // Don't show immediate self-recursion - .filter(c => !helperPatterns.test(c)) // Filter out helpers + .filter((c) => callGraph.has(c)) // Only show parser methods + .filter((c) => c !== method) // Don't show immediate self-recursion + .filter((c) => !helperPatterns.test(c)) // Filter out helpers .sort() // Add children @@ -131,7 +131,7 @@ function buildTree( newIndent, idx === calls.length - 1, depth + 1, - maxDepth + maxDepth, ) lines.push(...childLines) }) @@ -163,11 +163,11 @@ console.log(` Entry point: parse()`) // Find methods that are never called (potential dead code or entry points) const allCalled = new Set() for (const info of callGraph.values()) { - info.calls.forEach(c => allCalled.add(c)) + info.calls.forEach((c) => allCalled.add(c)) } const uncalled = Array.from(callGraph.keys()) - .filter(m => !allCalled.has(m) && m !== 'parse') + .filter((m) => !allCalled.has(m) && m !== 'parse') .sort() if (uncalled.length > 0) { diff --git a/package.json b/package.json index 8fdc7e2..758b491 100644 --- a/package.json +++ b/package.json @@ -33,4 +33,4 @@ "singleQuote": true, "printWidth": 100 } -} \ No newline at end of file +} diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index a3a4326..68c198c 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -58,7 +58,10 @@ export class Compiler { bytecode: Bytecode pipeCounter = 0 - constructor(public input: string, globals?: string[] | Record) { + constructor( + public input: string, + globals?: string[] | Record, + ) { try { if (globals) setGlobals(Array.isArray(globals) ? globals : Object.keys(globals)) const ast = parse(input) @@ -109,9 +112,15 @@ export class Compiler { // Handle sign prefix for hex, binary, and octal literals // Number() doesn't parse '-0xFF', '+0xFF', '-0o77', etc. correctly let numberValue: number - if (value.startsWith('-') && (value.includes('0x') || value.includes('0b') || value.includes('0o'))) { + if ( + value.startsWith('-') && + (value.includes('0x') || value.includes('0b') || value.includes('0o')) + ) { numberValue = -Number(value.slice(1)) - } else if (value.startsWith('+') && (value.includes('0x') || value.includes('0b') || value.includes('0o'))) { + } else if ( + value.startsWith('+') && + (value.includes('0x') || value.includes('0b') || value.includes('0o')) + ) { numberValue = Number(value.slice(1)) } else { numberValue = Number(value) @@ -123,8 +132,7 @@ export class Compiler { return [[`PUSH`, numberValue]] case 'String': { - if (node.firstChild?.type.is('CurlyString')) - return this.#compileCurlyString(value, input) + if (node.firstChild?.type.is('CurlyString')) return this.#compileCurlyString(value, input) const { parts, hasInterpolation } = getStringParts(node, input) @@ -166,7 +174,7 @@ export class Compiler { throw new CompilerError( `Unexpected string part: ${part.type.name}`, part.from, - part.to + part.to, ) } }) @@ -351,7 +359,7 @@ export class Compiler { } // Standard compound assignments: evaluate both sides, then operate - instructions.push(['LOAD', identifierName]) // will throw if undefined + instructions.push(['LOAD', identifierName]) // will throw if undefined instructions.push(...this.#compileNode(right, input)) switch (opValue) { @@ -374,7 +382,7 @@ export class Compiler { throw new CompilerError( `Unknown compound operator: ${opValue}`, operator.from, - operator.to + operator.to, ) } @@ -422,8 +430,8 @@ export class Compiler { catchVariable, catchBody, finallyBody, - input - ) + input, + ), ) } else { instructions.push(...compileFunctionBody()) @@ -532,7 +540,7 @@ export class Compiler { ...block .filter((x) => x.type.name !== 'keyword') .map((x) => this.#compileNode(x!, input)) - .flat() + .flat(), ) instructions.push(['RETURN']) instructions.push([`${afterLabel}:`]) @@ -559,7 +567,7 @@ export class Compiler { instructions.push(...body) } else { throw new Error( - `FunctionCallWithBlock: Expected FunctionCallOrIdentifier or FunctionCall` + `FunctionCallWithBlock: Expected FunctionCallOrIdentifier or FunctionCall`, ) } @@ -574,7 +582,7 @@ export class Compiler { catchVariable, catchBody, finallyBody, - input + input, ) } @@ -587,7 +595,7 @@ export class Compiler { throw new CompilerError( `${keyword} expected expression, got ${children.length} children`, node.from, - node.to + node.to, ) } @@ -601,7 +609,7 @@ export class Compiler { case 'IfExpr': { const { conditionNode, thenBlock, elseIfBlocks, elseThenBlock } = getIfExprParts( node, - input + input, ) const instructions: ProgramItem[] = [] instructions.push(...this.#compileNode(conditionNode, input)) @@ -732,13 +740,13 @@ export class Compiler { const { identifierNode, namedArgs, positionalArgs } = getFunctionCallParts( pipeReceiver, - input + input, ) instructions.push(...this.#compileNode(identifierNode, input)) - const isUnderscoreInPositionalArgs = positionalArgs.some( - (arg) => arg.type.is('Underscore') + const isUnderscoreInPositionalArgs = positionalArgs.some((arg) => + arg.type.is('Underscore'), ) const isUnderscoreInNamedArgs = namedArgs.some((arg) => { const { valueNode } = getNamedArgParts(arg, input) @@ -837,14 +845,12 @@ export class Compiler { case 'Import': { const instructions: ProgramItem[] = [] const [_import, ...nodes] = getAllChildren(node) - const args = nodes.filter(node => node.type.is('Identifier')) - const namedArgs = nodes.filter(node => node.type.is('NamedArg')) + const args = nodes.filter((node) => node.type.is('Identifier')) + const namedArgs = nodes.filter((node) => node.type.is('NamedArg')) instructions.push(['LOAD', 'import']) - args.forEach((dict) => - instructions.push(['PUSH', input.slice(dict.from, dict.to)]) - ) + args.forEach((dict) => instructions.push(['PUSH', input.slice(dict.from, dict.to)])) namedArgs.forEach((arg) => { const { name, valueNode } = getNamedArgParts(arg, input) @@ -867,7 +873,7 @@ export class Compiler { throw new CompilerError( `Compiler doesn't know how to handle a "${node.type.name}" node.`, node.from, - node.to + node.to, ) } } @@ -877,7 +883,7 @@ export class Compiler { catchVariable: string | undefined, catchBody: SyntaxNode | undefined, finallyBody: SyntaxNode | undefined, - input: string + input: string, ): ProgramItem[] { const instructions: ProgramItem[] = [] this.tryLabelCount++ diff --git a/src/compiler/compilerError.ts b/src/compiler/compilerError.ts index c5492a1..cfdd0d1 100644 --- a/src/compiler/compilerError.ts +++ b/src/compiler/compilerError.ts @@ -1,5 +1,9 @@ export class CompilerError extends Error { - constructor(message: string, private from: number, private to: number) { + constructor( + message: string, + private from: number, + private to: number, + ) { super(message) if (from < 0 || to < 0 || to < from) { diff --git a/src/compiler/tests/compiler.test.ts b/src/compiler/tests/compiler.test.ts index bcddafb..df5ed0d 100644 --- a/src/compiler/tests/compiler.test.ts +++ b/src/compiler/tests/compiler.test.ts @@ -112,8 +112,12 @@ describe('compiler', () => { test('function call with no args', () => { expect(`bloop = do: 'bleep' end; bloop`).toEvaluateTo('bleep') expect(`bloop = [ go=do: 'bleep' end ]; bloop.go`).toEvaluateTo('bleep') - expect(`bloop = [ go=do: 'bleep' end ]; abc = do x: x end; abc (bloop.go)`).toEvaluateTo('bleep') - expect(`num = ((math.random) * 10 + 1) | math.floor; num >= 1 and num <= 10 `).toEvaluateTo(true) + expect(`bloop = [ go=do: 'bleep' end ]; abc = do x: x end; abc (bloop.go)`).toEvaluateTo( + 'bleep', + ) + expect(`num = ((math.random) * 10 + 1) | math.floor; num >= 1 and num <= 10 `).toEvaluateTo( + true, + ) }) test('function call with if statement and multiple expressions', () => { @@ -376,7 +380,7 @@ describe('default params', () => { age: 60, }) expect( - 'make-person = do person=[name=Bob age=60]: person end; make-person [name=Jon age=21]' + 'make-person = do person=[name=Bob age=60]: person end; make-person [name=Jon age=21]', ).toEvaluateTo({ name: 'Jon', age: 21 }) }) }) @@ -408,7 +412,9 @@ describe('Nullish coalescing operator (??)', () => { }) test('short-circuits evaluation', () => { - const throwError = () => { throw new Error('Should not evaluate') } + const throwError = () => { + throw new Error('Should not evaluate') + } expect('5 ?? throw-error').toEvaluateTo(5, { 'throw-error': throwError }) }) @@ -424,7 +430,7 @@ describe('Nullish coalescing operator (??)', () => { // Use explicit call syntax to invoke the function expect('(get-value) ?? (get-default)').toEvaluateTo(42, { 'get-value': getValue, - 'get-default': getDefault + 'get-default': getDefault, }) }) }) @@ -456,7 +462,9 @@ describe('Nullish coalescing assignment (??=)', () => { }) test('short-circuits evaluation when not null', () => { - const throwError = () => { throw new Error('Should not evaluate') } + const throwError = () => { + throw new Error('Should not evaluate') + } expect('x = 5; x ??= throw-error; x').toEvaluateTo(5, { 'throw-error': throwError }) }) @@ -522,4 +530,4 @@ describe('import', () => { [a c] `).toEvaluateTo([true, 'si']) }) -}) \ No newline at end of file +}) diff --git a/src/compiler/tests/function-blocks.test.ts b/src/compiler/tests/function-blocks.test.ts index 4c91731..f03cad2 100644 --- a/src/compiler/tests/function-blocks.test.ts +++ b/src/compiler/tests/function-blocks.test.ts @@ -10,12 +10,16 @@ describe('single line function blocks', () => { }) test('work with named args', () => { - expect(`attach = do signal fn: [ signal (fn) ] end; attach signal='exit': true end`).toEvaluateTo(['exit', true]) + expect( + `attach = do signal fn: [ signal (fn) ] end; attach signal='exit': true end`, + ).toEvaluateTo(['exit', true]) }) - test('work with dot-get', () => { - expect(`signals = [trap=do x y: [x (y)] end]; signals.trap 'EXIT': true end`).toEvaluateTo(['EXIT', true]) + expect(`signals = [trap=do x y: [x (y)] end]; signals.trap 'EXIT': true end`).toEvaluateTo([ + 'EXIT', + true, + ]) }) }) @@ -44,7 +48,6 @@ attach signal='exit': end`).toEvaluateTo(['exit', true]) }) - test('work with dot-get', () => { expect(` signals = [trap=do x y: [x (y)] end] diff --git a/src/compiler/tests/literals.test.ts b/src/compiler/tests/literals.test.ts index 2b102a4..f7f1760 100644 --- a/src/compiler/tests/literals.test.ts +++ b/src/compiler/tests/literals.test.ts @@ -219,7 +219,7 @@ describe('dict literals', () => { describe('curly strings', () => { test('work on one line', () => { - expect('{ one two three }').toEvaluateTo(" one two three ") + expect('{ one two three }').toEvaluateTo(' one two three ') }) test('work on multiple lines', () => { @@ -227,7 +227,7 @@ describe('curly strings', () => { one two three - }`).toEvaluateTo("\n one\n two\n three\n ") + }`).toEvaluateTo('\n one\n two\n three\n ') }) test('can contain other curlies', () => { @@ -235,7 +235,7 @@ describe('curly strings', () => { { one } two { three } - }`).toEvaluateTo("\n { one }\n two\n { three }\n ") + }`).toEvaluateTo('\n { one }\n two\n { three }\n ') }) test('interpolates variables', () => { @@ -263,7 +263,7 @@ describe('curly strings', () => { }) describe('double quoted strings', () => { - test("work", () => { + test('work', () => { expect(`"hello world"`).toEvaluateTo('hello world') }) @@ -272,13 +272,13 @@ describe('double quoted strings', () => { expect(`"hello $(1 + 2)"`).toEvaluateTo('hello $(1 + 2)') }) - test("equal regular strings", () => { + test('equal regular strings', () => { expect(`"hello world" == 'hello world'`).toEvaluateTo(true) }) - test("can contain newlines", () => { + test('can contain newlines', () => { expect(` "hello world"`).toEvaluateTo('hello\n world') }) -}) \ No newline at end of file +}) diff --git a/src/compiler/tests/native-exceptions.test.ts b/src/compiler/tests/native-exceptions.test.ts index f7e2e37..3b42aa8 100644 --- a/src/compiler/tests/native-exceptions.test.ts +++ b/src/compiler/tests/native-exceptions.test.ts @@ -40,7 +40,7 @@ describe('Native Function Exceptions', () => { const vm = new VM(compiler.bytecode) vm.set('async-fail', async () => { - await new Promise(resolve => setTimeout(resolve, 1)) + await new Promise((resolve) => setTimeout(resolve, 1)) throw new Error('async error') }) @@ -237,7 +237,7 @@ describe('Native Function Exceptions', () => { const result = await vm.run() expect(result).toEqual({ type: 'string', - value: 'This is a very specific error message with details' + value: 'This is a very specific error message with details', }) }) diff --git a/src/compiler/tests/ribbit.test.ts b/src/compiler/tests/ribbit.test.ts index def34c4..3fba47e 100644 --- a/src/compiler/tests/ribbit.test.ts +++ b/src/compiler/tests/ribbit.test.ts @@ -5,7 +5,7 @@ const buffer: string[] = [] const ribbitGlobals = { ribbit: async (cb: Function) => { await cb() - return buffer.join("\n") + return buffer.join('\n') }, tag: async (tagFn: Function, atDefaults = {}) => { return (atNamed = {}, ...args: any[]) => tagFn(Object.assign({}, atDefaults, atNamed), ...args) @@ -20,10 +20,12 @@ const ribbitGlobals = { ul: (atNamed: {}, ...args: any[]) => tag('ul', atNamed, ...args), li: (atNamed: {}, ...args: any[]) => tag('li', atNamed, ...args), nospace: () => NOSPACE_TOKEN, - echo: (...args: any[]) => console.log(...args) + echo: (...args: any[]) => console.log(...args), } -function raw(fn: Function) { (fn as any).raw = true } +function raw(fn: Function) { + ;(fn as any).raw = true +} const tagBlock = async (tagName: string, props = {}, fn: Function) => { const attrs = Object.entries(props).map(([key, value]) => `${key}="${value}"`) @@ -39,14 +41,13 @@ const tagCall = (tagName: string, atNamed = {}, ...args: any[]) => { const space = attrs.length ? ' ' : '' const children = args .reverse() - .map(a => a === TAG_TOKEN ? buffer.pop() : a) - .reverse().join(' ') + .map((a) => (a === TAG_TOKEN ? buffer.pop() : a)) + .reverse() + .join(' ') .replaceAll(` ${NOSPACE_TOKEN} `, '') - if (SELF_CLOSING.includes(tagName)) - buffer.push(`<${tagName}${space}${attrs.join(' ')} />`) - else - buffer.push(`<${tagName}${space}${attrs.join(' ')}>${children}`) + if (SELF_CLOSING.includes(tagName)) buffer.push(`<${tagName}${space}${attrs.join(' ')} />`) + else buffer.push(`<${tagName}${space}${attrs.join(' ')}>${children}`) } const tag = async (tagName: string, atNamed = {}, ...args: any[]) => { @@ -60,10 +61,25 @@ const tag = async (tagName: string, atNamed = {}, ...args: any[]) => { const NOSPACE_TOKEN = '!!ribbit-nospace!!' const TAG_TOKEN = '!!ribbit-tag!!' -const SELF_CLOSING = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"] +const SELF_CLOSING = [ + 'area', + 'base', + 'br', + 'col', + 'embed', + 'hr', + 'img', + 'input', + 'link', + 'meta', + 'param', + 'source', + 'track', + 'wbr', +] describe('ribbit', () => { - beforeEach(() => buffer.length = 0) + beforeEach(() => (buffer.length = 0)) test('head tag', () => { expect(` @@ -74,11 +90,14 @@ ribbit: meta name=viewport content='width=device-width, initial-scale=1, viewport-fit=cover' end end - `).toEvaluateTo(` + `).toEvaluateTo( + ` What up -`, ribbitGlobals) +`, + ribbitGlobals, + ) }) test('custom tags', () => { @@ -90,11 +109,14 @@ ribbit: li two li three end -end`).toEvaluateTo(`
    +end`).toEvaluateTo( + `
    • one
    • two
    • three
    • -
    `, ribbitGlobals) +
`, + ribbitGlobals, + ) }) test('inline expressions', () => { @@ -110,6 +132,8 @@ end`).toEvaluateTo(`

    Heya

    man that is wild!

    Double the fun.

    -

    `, ribbitGlobals) +

    `, + ribbitGlobals, + ) }) -}) \ No newline at end of file +}) diff --git a/src/compiler/tests/while.test.ts b/src/compiler/tests/while.test.ts index c3afdb9..3654838 100644 --- a/src/compiler/tests/while.test.ts +++ b/src/compiler/tests/while.test.ts @@ -10,8 +10,7 @@ describe('while', () => { a = false b = done end - b`) - .toEvaluateTo('done') + b`).toEvaluateTo('done') }) test('basic expression', () => { @@ -20,8 +19,7 @@ describe('while', () => { while a < 10: a += 1 end - a`) - .toEvaluateTo(10) + a`).toEvaluateTo(10) }) test('compound expression', () => { @@ -31,8 +29,7 @@ describe('while', () => { while a > 0 and b < 100: b += 1 end - b`) - .toEvaluateTo(100) + b`).toEvaluateTo(100) }) test('returns value', () => { @@ -42,7 +39,6 @@ describe('while', () => { a += 1 done end - ret`) - .toEvaluateTo('done') + ret`).toEvaluateTo('done') }) -}) \ No newline at end of file +}) diff --git a/src/compiler/utils.ts b/src/compiler/utils.ts index e3816c2..335859b 100644 --- a/src/compiler/utils.ts +++ b/src/compiler/utils.ts @@ -45,7 +45,7 @@ export const getAssignmentParts = (node: SyntaxNode) => { throw new CompilerError( `Assign expected 3 children, got ${children.length}`, node.from, - node.to + node.to, ) } @@ -57,10 +57,11 @@ export const getAssignmentParts = (node: SyntaxNode) => { if (!left || !left.type.is('AssignableIdentifier')) { throw new CompilerError( - `Assign left child must be an AssignableIdentifier or Array, got ${left ? left.type.name : 'none' + `Assign left child must be an AssignableIdentifier or Array, got ${ + left ? left.type.name : 'none' }`, node.from, - node.to + node.to, ) } @@ -73,16 +74,17 @@ export const getCompoundAssignmentParts = (node: SyntaxNode) => { if (!left || !left.type.is('AssignableIdentifier')) { throw new CompilerError( - `CompoundAssign left child must be an AssignableIdentifier, got ${left ? left.type.name : 'none' + `CompoundAssign left child must be an AssignableIdentifier, got ${ + left ? left.type.name : 'none' }`, node.from, - node.to + node.to, ) } else if (!operator || !right) { throw new CompilerError( `CompoundAssign expected 3 children, got ${children.length}`, node.from, - node.to + node.to, ) } @@ -97,7 +99,7 @@ export const getFunctionDefParts = (node: SyntaxNode, input: string) => { throw new CompilerError( `FunctionDef expected at least 4 children, got ${children.length}`, node.from, - node.to + node.to, ) } @@ -106,7 +108,7 @@ export const getFunctionDefParts = (node: SyntaxNode, input: string) => { throw new CompilerError( `FunctionDef params must be Identifier or NamedParam, got ${param.type.name}`, param.from, - param.to + param.to, ) } return input.slice(param.from, param.to) @@ -129,7 +131,7 @@ export const getFunctionDefParts = (node: SyntaxNode, input: string) => { throw new CompilerError( `CatchExpr expected identifier and body, got ${catchChildren.length} children`, child.from, - child.to + child.to, ) } catchVariable = input.slice(identifierNode.from, identifierNode.to) @@ -142,7 +144,7 @@ export const getFunctionDefParts = (node: SyntaxNode, input: string) => { throw new CompilerError( `FinallyExpr expected body, got ${finallyChildren.length} children`, child.from, - child.to + child.to, ) } finallyBody = body @@ -197,7 +199,7 @@ export const getIfExprParts = (node: SyntaxNode, input: string) => { throw new CompilerError( `IfExpr expected at least 4 children, got ${children.length}`, node.from, - node.to + node.to, ) } @@ -251,7 +253,6 @@ export const getStringParts = (node: SyntaxNode, input: string) => { child.type.is('Interpolation') || child.type.is('EscapeSeq') || child.type.is('CurlyString') - ) }) @@ -266,16 +267,14 @@ export const getStringParts = (node: SyntaxNode, input: string) => { throw new CompilerError( `String child must be StringFragment, Interpolation, or EscapeSeq, got ${part.type.name}`, part.from, - part.to + part.to, ) } }) // hasInterpolation means the string has interpolation ($var) or escape sequences (\n) // A simple string like 'hello' has one StringFragment but no interpolation - const hasInterpolation = parts.some( - (p) => p.type.is('Interpolation') || p.type.is('EscapeSeq') - ) + const hasInterpolation = parts.some((p) => p.type.is('Interpolation') || p.type.is('EscapeSeq')) return { parts, hasInterpolation } } @@ -287,7 +286,7 @@ export const getDotGetParts = (node: SyntaxNode, input: string) => { throw new CompilerError( `DotGet expected 2 identifier children, got ${children.length}`, node.from, - node.to + node.to, ) } @@ -295,7 +294,7 @@ export const getDotGetParts = (node: SyntaxNode, input: string) => { throw new CompilerError( `DotGet object must be an IdentifierBeforeDot, got ${object.type.name}`, object.from, - object.to + object.to, ) } @@ -303,7 +302,7 @@ export const getDotGetParts = (node: SyntaxNode, input: string) => { throw new CompilerError( `DotGet property must be an Identifier, Number, ParenExpr, or DotGet, got ${property.type.name}`, property.from, - property.to + property.to, ) } @@ -322,7 +321,7 @@ export const getTryExprParts = (node: SyntaxNode, input: string) => { throw new CompilerError( `TryExpr expected at least 3 children, got ${children.length}`, node.from, - node.to + node.to, ) } @@ -341,7 +340,7 @@ export const getTryExprParts = (node: SyntaxNode, input: string) => { throw new CompilerError( `CatchExpr expected identifier and body, got ${catchChildren.length} children`, child.from, - child.to + child.to, ) } catchVariable = input.slice(identifierNode.from, identifierNode.to) @@ -354,7 +353,7 @@ export const getTryExprParts = (node: SyntaxNode, input: string) => { throw new CompilerError( `FinallyExpr expected body, got ${finallyChildren.length} children`, child.from, - child.to + child.to, ) } finallyBody = body diff --git a/src/editor/editor.css b/src/editor/editor.css index 29ec6f6..bc09fd9 100644 --- a/src/editor/editor.css +++ b/src/editor/editor.css @@ -32,7 +32,7 @@ } #status-bar .multiline { - display: flex; + display: flex; .dot { padding-top: 1px; @@ -50,4 +50,4 @@ .syntax-error { text-decoration: underline dotted var(--color-error); -} \ No newline at end of file +} diff --git a/src/editor/editor.tsx b/src/editor/editor.tsx index ce7e0b1..91cc130 100644 --- a/src/editor/editor.tsx +++ b/src/editor/editor.tsx @@ -123,7 +123,7 @@ const valueToString = (value: Value | string): string => { return `${value.value.map(valueToString).join('\n')}` case 'dict': { const entries = Array.from(value.value.entries()).map( - ([key, val]) => `"${key}": ${valueToString(val)}` + ([key, val]) => `"${key}": ${valueToString(val)}`, ) return `{${entries.join(', ')}}` } diff --git a/src/editor/plugins/catchErrors.ts b/src/editor/plugins/catchErrors.ts index b40cee6..1cc9e68 100644 --- a/src/editor/plugins/catchErrors.ts +++ b/src/editor/plugins/catchErrors.ts @@ -4,6 +4,6 @@ import { EditorView } from '@codemirror/view' export const catchErrors = EditorView.exceptionSink.of((exception) => { console.error('CodeMirror error:', exception) errorSignal.emit( - `Editor error: ${exception instanceof Error ? exception.message : String(exception)}` + `Editor error: ${exception instanceof Error ? exception.message : String(exception)}`, ) }) diff --git a/src/editor/plugins/debugTags.ts b/src/editor/plugins/debugTags.ts index d959d64..e44f24e 100644 --- a/src/editor/plugins/debugTags.ts +++ b/src/editor/plugins/debugTags.ts @@ -31,5 +31,5 @@ export const debugTags = ViewPlugin.fromClass( order: -1, }) } - } + }, ) diff --git a/src/editor/plugins/errors.ts b/src/editor/plugins/errors.ts index 6536145..5cb19b2 100644 --- a/src/editor/plugins/errors.ts +++ b/src/editor/plugins/errors.ts @@ -58,5 +58,5 @@ export const shrimpErrors = ViewPlugin.fromClass( }, { decorations: (v) => v.decorations, - } + }, ) diff --git a/src/editor/plugins/inlineHints.tsx b/src/editor/plugins/inlineHints.tsx index e96e491..c1a130e 100644 --- a/src/editor/plugins/inlineHints.tsx +++ b/src/editor/plugins/inlineHints.tsx @@ -215,7 +215,7 @@ export const inlineHints = [ } }, }, - } + }, ), ghostTextTheme, ] diff --git a/src/editor/plugins/persistence.ts b/src/editor/plugins/persistence.ts index dd7c6d1..4654593 100644 --- a/src/editor/plugins/persistence.ts +++ b/src/editor/plugins/persistence.ts @@ -17,7 +17,7 @@ export const persistencePlugin = ViewPlugin.fromClass( destroy() { if (this.saveTimeout) clearTimeout(this.saveTimeout) } - } + }, ) export const getContent = () => { diff --git a/src/editor/plugins/theme.tsx b/src/editor/plugins/theme.tsx index c1e46f1..d8c6cc3 100644 --- a/src/editor/plugins/theme.tsx +++ b/src/editor/plugins/theme.tsx @@ -56,5 +56,5 @@ export const shrimpTheme = EditorView.theme( backgroundColor: 'var(--color-string)', }, }, - { dark: true } + { dark: true }, ) diff --git a/src/index.ts b/src/index.ts index 6062fb3..a1c44a1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,99 +14,101 @@ export { type Value, type Bytecode } from 'reefvm' export { toValue, fromValue, isValue, Scope, VM, bytecodeToString } from 'reefvm' export class Shrimp { - vm: VM - private globals?: Record + vm: VM + private globals?: Record - constructor(globals?: Record) { - const emptyBytecode = { instructions: [], constants: [], labels: new Map() } - this.vm = new VM(emptyBytecode, Object.assign({}, prelude, globals ?? {})) - this.globals = globals + constructor(globals?: Record) { + const emptyBytecode = { instructions: [], constants: [], labels: new Map() } + this.vm = new VM(emptyBytecode, Object.assign({}, prelude, globals ?? {})) + this.globals = globals + } + + get(name: string): any { + const value = this.vm.scope.get(name) + return value ? fromValue(value, this.vm) : null + } + + set(name: string, value: any) { + this.vm.scope.set(name, toValue(value, this.vm)) + } + + has(name: string): boolean { + return this.vm.scope.has(name) + } + + async call(name: string, ...args: any[]): Promise { + const result = await this.vm.call(name, ...args) + return isValue(result) ? fromValue(result, this.vm) : result + } + + parse(code: string): Tree { + return parseCode(code, this.globals) + } + + compile(code: string): Bytecode { + return compileCode(code, this.globals) + } + + async run(code: string | Bytecode, locals?: Record): Promise { + let bytecode + + if (typeof code === 'string') { + const compiler = new Compiler( + code, + Object.keys(Object.assign({}, prelude, this.globals ?? {}, locals ?? {})), + ) + bytecode = compiler.bytecode + } else { + bytecode = code } - get(name: string): any { - const value = this.vm.scope.get(name) - return value ? fromValue(value, this.vm) : null - } - - set(name: string, value: any) { - this.vm.scope.set(name, toValue(value, this.vm)) - } - - has(name: string): boolean { - return this.vm.scope.has(name) - } - - async call(name: string, ...args: any[]): Promise { - const result = await this.vm.call(name, ...args) - return isValue(result) ? fromValue(result, this.vm) : result - } - - parse(code: string): Tree { - return parseCode(code, this.globals) - } - - compile(code: string): Bytecode { - return compileCode(code, this.globals) - } - - async run(code: string | Bytecode, locals?: Record): Promise { - let bytecode - - if (typeof code === 'string') { - const compiler = new Compiler(code, Object.keys(Object.assign({}, prelude, this.globals ?? {}, locals ?? {}))) - bytecode = compiler.bytecode - } else { - bytecode = code - } - - if (locals) this.vm.pushScope(locals) - this.vm.appendBytecode(bytecode) - await this.vm.continue() - if (locals) this.vm.popScope() - - return this.vm.stack.length ? fromValue(this.vm.stack.at(-1)!, this.vm) : null - } + if (locals) this.vm.pushScope(locals) + this.vm.appendBytecode(bytecode) + await this.vm.continue() + if (locals) this.vm.popScope() + return this.vm.stack.length ? fromValue(this.vm.stack.at(-1)!, this.vm) : null + } } export async function runFile(path: string, globals?: Record): Promise { - const code = readFileSync(path, 'utf-8') - return await runCode(code, globals) + const code = readFileSync(path, 'utf-8') + return await runCode(code, globals) } export async function runCode(code: string, globals?: Record): Promise { - return await runBytecode(compileCode(code, globals), globals) + return await runBytecode(compileCode(code, globals), globals) } export async function runBytecode(bytecode: Bytecode, globals?: Record): Promise { - const vm = new VM(bytecode, Object.assign({}, prelude, globals)) - await vm.run() - return vm.stack.length ? fromValue(vm.stack[vm.stack.length - 1]!, vm) : null + const vm = new VM(bytecode, Object.assign({}, prelude, globals)) + await vm.run() + return vm.stack.length ? fromValue(vm.stack[vm.stack.length - 1]!, vm) : null } export function compileFile(path: string, globals?: Record): Bytecode { - const code = readFileSync(path, 'utf-8') - return compileCode(code, globals) + const code = readFileSync(path, 'utf-8') + return compileCode(code, globals) } export function compileCode(code: string, globals?: Record): Bytecode { - const globalNames = [...Object.keys(prelude), ...(globals ? Object.keys(globals) : [])] - const compiler = new Compiler(code, globalNames) - return compiler.bytecode + const globalNames = [...Object.keys(prelude), ...(globals ? Object.keys(globals) : [])] + const compiler = new Compiler(code, globalNames) + return compiler.bytecode } export function parseFile(path: string, globals?: Record): Tree { - const code = readFileSync(path, 'utf-8') - return parseCode(code, globals) + const code = readFileSync(path, 'utf-8') + return parseCode(code, globals) } export function parseCode(code: string, globals?: Record): Tree { - const oldGlobals = [...parserGlobals] - const globalNames = [...Object.keys(prelude), ...(globals ? Object.keys(globals) : [])] + const oldGlobals = [...parserGlobals] + const globalNames = [...Object.keys(prelude), ...(globals ? Object.keys(globals) : [])] - setParserGlobals(globalNames) - const result = parse(code) - setParserGlobals(oldGlobals) + setParserGlobals(globalNames) + const result = parse(code) + setParserGlobals(oldGlobals) - return new Tree(result) -} \ No newline at end of file + return new Tree(result) +} diff --git a/src/parser/curlyTokenizer.ts b/src/parser/curlyTokenizer.ts index 9b35749..cc22399 100644 --- a/src/parser/curlyTokenizer.ts +++ b/src/parser/curlyTokenizer.ts @@ -44,8 +44,7 @@ export const tokenizeCurlyString = (value: string): (string | [string, SyntaxNod if (!char) break if (!isIdentStart(char.charCodeAt(0))) break - while (char && isIdentChar(char.charCodeAt(0))) - char = value[++pos] + while (char && isIdentChar(char.charCodeAt(0))) char = value[++pos] const input = value.slice(start + 1, pos) // skip '$' tokens.push([input, parse(input)]) @@ -59,4 +58,4 @@ export const tokenizeCurlyString = (value: string): (string | [string, SyntaxNod tokens.push(value.slice(start, pos - 1)) return tokens -} \ No newline at end of file +} diff --git a/src/parser/node.ts b/src/parser/node.ts index ca256de..9e1fa49 100644 --- a/src/parser/node.ts +++ b/src/parser/node.ts @@ -3,18 +3,15 @@ import { type Token, TokenType } from './tokenizer2' export type NodeType = | 'Program' | 'Block' - | 'FunctionCall' | 'FunctionCallOrIdentifier' | 'FunctionCallWithBlock' | 'PositionalArg' | 'NamedArg' | 'NamedArgPrefix' - | 'FunctionDef' | 'Params' | 'NamedParam' - | 'Null' | 'Boolean' | 'Number' @@ -32,7 +29,6 @@ export type NodeType = | 'Array' | 'Dict' | 'Comment' - | 'BinOp' | 'ConditionalOp' | 'ParenExpr' @@ -40,7 +36,6 @@ export type NodeType = | 'CompoundAssign' | 'DotGet' | 'PipeExpr' - | 'IfExpr' | 'ElseIfExpr' | 'ElseExpr' @@ -49,14 +44,12 @@ export type NodeType = | 'CatchExpr' | 'FinallyExpr' | 'Throw' - | 'Not' | 'Eq' | 'Modulo' | 'Plus' | 'Star' | 'Slash' - | 'Import' | 'Do' | 'Underscore' @@ -67,13 +60,13 @@ export type NodeType = // TODO: remove this when we switch from lezer export const operators: Record = { // Logic - 'and': 'And', - 'or': 'Or', + and: 'And', + or: 'Or', // Bitwise - 'band': 'Band', - 'bor': 'Bor', - 'bxor': 'Bxor', + band: 'Band', + bor: 'Bor', + bxor: 'Bxor', '>>>': 'Ushr', '>>': 'Shr', '<<': 'Shl', @@ -114,7 +107,7 @@ export const operators: Record = { } export class Tree { - constructor(public topNode: SyntaxNode) { } + constructor(public topNode: SyntaxNode) {} get length(): number { return this.topNode.to @@ -158,12 +151,17 @@ export class SyntaxNode { return new SyntaxNode(TokenType[token.type] as NodeType, token.from, token.to, parent ?? null) } - get type(): { type: NodeType, name: NodeType, isError: boolean, is: (other: NodeType) => boolean } { + get type(): { + type: NodeType + name: NodeType + isError: boolean + is: (other: NodeType) => boolean + } { return { type: this.#type, name: this.#type, isError: this.#isError, - is: (other: NodeType) => other === this.#type + is: (other: NodeType) => other === this.#type, } } @@ -211,7 +209,7 @@ export class SyntaxNode { } push(...nodes: SyntaxNode[]): SyntaxNode { - nodes.forEach(child => child.parent = this) + nodes.forEach((child) => (child.parent = this)) this.children.push(...nodes) return this } @@ -224,8 +222,8 @@ export class SyntaxNode { // Operator precedence (binding power) - higher = tighter binding export const precedence: Record = { // Logical - 'or': 10, - 'and': 20, + or: 10, + and: 20, // Comparison '==': 30, @@ -248,9 +246,9 @@ export const precedence: Record = { '-': 40, // Bitwise AND/OR/XOR (higher precedence than addition) - 'band': 45, - 'bor': 45, - 'bxor': 45, + band: 45, + bor: 45, + bxor: 45, // Multiplication/Division/Modulo '*': 50, @@ -261,10 +259,6 @@ export const precedence: Record = { '**': 60, } -export const conditionals = new Set([ - '==', '!=', '<', '>', '<=', '>=', '??', 'and', 'or' -]) +export const conditionals = new Set(['==', '!=', '<', '>', '<=', '>=', '??', 'and', 'or']) -export const compounds = [ - '??=', '+=', '-=', '*=', '/=', '%=' -] +export const compounds = ['??=', '+=', '-=', '*=', '/=', '%='] diff --git a/src/parser/parser2.ts b/src/parser/parser2.ts index 5cfa672..a3b02dc 100644 --- a/src/parser/parser2.ts +++ b/src/parser/parser2.ts @@ -42,7 +42,7 @@ export class Parser { pos = 0 inParens = 0 input = '' - scope = new Scope + scope = new Scope() inTestExpr = false parse(input: string): SyntaxNode { @@ -78,14 +78,11 @@ export class Parser { // statement is a line of code statement(): SyntaxNode | null { - if (this.is($T.Comment)) - return this.comment() + if (this.is($T.Comment)) return this.comment() - while (this.is($T.Newline) || this.is($T.Semicolon)) - this.next() + while (this.is($T.Newline) || this.is($T.Semicolon)) this.next() - if (this.isEOF() || this.isExprEndKeyword()) - return null + if (this.isEOF() || this.isExprEndKeyword()) return null return this.expression() } @@ -99,51 +96,38 @@ export class Parser { let expr // x = value - if (this.is($T.Identifier) && ( - this.nextIs($T.Operator, '=') || compounds.some(x => this.nextIs($T.Operator, x)) - )) + if ( + this.is($T.Identifier) && + (this.nextIs($T.Operator, '=') || compounds.some((x) => this.nextIs($T.Operator, x))) + ) expr = this.assign() - // if, while, do, etc - else if (this.is($T.Keyword)) - expr = this.keywords() - + else if (this.is($T.Keyword)) expr = this.keywords() // dotget - else if (this.nextIs($T.Operator, '.')) - expr = this.dotGetFunctionCall() - + else if (this.nextIs($T.Operator, '.')) expr = this.dotGetFunctionCall() // echo hello world else if (this.is($T.Identifier) && !this.nextIs($T.Operator) && !this.nextIsExprEnd()) expr = this.functionCall() - // bare-function-call - else if (this.is($T.Identifier) && this.nextIsExprEnd()) - expr = this.functionCallOrIdentifier() - + else if (this.is($T.Identifier) && this.nextIsExprEnd()) expr = this.functionCallOrIdentifier() // everything else - else - expr = this.exprWithPrecedence() + else expr = this.exprWithPrecedence() // check for destructuring - if (expr.type.is('Array') && this.is($T.Operator, '=')) - return this.destructure(expr) + if (expr.type.is('Array') && this.is($T.Operator, '=')) return this.destructure(expr) // check for parens function call // ex: (ref my-func) my-arg - if (expr.type.is('ParenExpr') && !this.isExprEnd()) - expr = this.functionCall(expr) + if (expr.type.is('ParenExpr') && !this.isExprEnd()) expr = this.functionCall(expr) // if dotget is followed by binary operator, continue parsing as binary expression if (expr.type.is('DotGet') && this.is($T.Operator) && !this.is($T.Operator, '|')) expr = this.dotGetBinOp(expr) // one | echo - if (allowPipe && this.isPipe()) - return this.pipe(expr) - + if (allowPipe && this.isPipe()) return this.pipe(expr) // regular - else - return expr + else return expr } // piping | stuff | is | cool @@ -207,26 +191,19 @@ export class Parser { // if, while, do, etc keywords(): SyntaxNode { - if (this.is($T.Keyword, 'if')) - return this.if() + if (this.is($T.Keyword, 'if')) return this.if() - if (this.is($T.Keyword, 'while')) - return this.while() + if (this.is($T.Keyword, 'while')) return this.while() - if (this.is($T.Keyword, 'do')) - return this.do() + if (this.is($T.Keyword, 'do')) return this.do() - if (this.is($T.Keyword, 'try')) - return this.try() + if (this.is($T.Keyword, 'try')) return this.try() - if (this.is($T.Keyword, 'throw')) - return this.throw() + if (this.is($T.Keyword, 'throw')) return this.throw() - if (this.is($T.Keyword, 'not')) - return this.not() + if (this.is($T.Keyword, 'not')) return this.not() - if (this.is($T.Keyword, 'import')) - return this.import() + if (this.is($T.Keyword, 'import')) return this.import() return this.expect($T.Keyword, 'if/while/do/import') as never } @@ -238,15 +215,12 @@ export class Parser { // 3. binary operations // 4. anywhere an expression can be used value(): SyntaxNode { - if (this.is($T.OpenParen)) - return this.parens() + if (this.is($T.OpenParen)) return this.parens() - if (this.is($T.OpenBracket)) - return this.arrayOrDict() + if (this.is($T.OpenBracket)) return this.arrayOrDict() // dotget - if (this.nextIs($T.Operator, '.')) - return this.dotGet() + if (this.nextIs($T.Operator, '.')) return this.dotGet() return this.atom() } @@ -324,8 +298,7 @@ export class Parser { } // probably an array - if (curr.type !== $T.Comment && curr.type !== $T.Semicolon && curr.type !== $T.Newline) - break + if (curr.type !== $T.Comment && curr.type !== $T.Semicolon && curr.type !== $T.Newline) break curr = this.peek(peek++) } @@ -343,7 +316,7 @@ export class Parser { const node = new SyntaxNode( opToken.value === '=' ? 'Assign' : 'CompoundAssign', ident.from, - expr.to + expr.to, ) return node.push(ident, op, expr) @@ -360,8 +333,7 @@ export class Parser { // atoms are the basic building blocks: literals, identifiers, words atom(): SyntaxNode { - if (this.is($T.String)) - return this.string() + if (this.is($T.String)) return this.string() if (this.isAny($T.Null, $T.Boolean, $T.Number, $T.Identifier, $T.Word, $T.Regex, $T.Underscore)) return SyntaxNode.from(this.next()) @@ -402,8 +374,7 @@ export class Parser { const keyword = this.keyword('catch') let catchVar - if (this.is($T.Identifier)) - catchVar = this.identifier() + if (this.is($T.Identifier)) catchVar = this.identifier() const block = this.block() @@ -507,12 +478,14 @@ export class Parser { this.scope.add(varName) let arg - if (this.is($T.Identifier)) - arg = this.identifier() - else if (this.is($T.NamedArgPrefix)) - arg = this.namedParam() + if (this.is($T.Identifier)) arg = this.identifier() + else if (this.is($T.NamedArgPrefix)) arg = this.namedParam() else - throw new CompilerError(`Expected Identifier or NamedArgPrefix, got ${TokenType[this.current().type]}`, this.current().from, this.current().to) + throw new CompilerError( + `Expected Identifier or NamedArgPrefix, got ${TokenType[this.current().type]}`, + this.current().from, + this.current().to, + ) params.push(arg) } @@ -520,11 +493,9 @@ export class Parser { const block = this.block(false) let catchNode, finalNode - if (this.is($T.Keyword, 'catch')) - catchNode = this.catch() + if (this.is($T.Keyword, 'catch')) catchNode = this.catch() - if (this.is($T.Keyword, 'finally')) - finalNode = this.finally() + if (this.is($T.Keyword, 'finally')) finalNode = this.finally() const end = this.keyword('end') @@ -536,11 +507,7 @@ export class Parser { node.add(doNode) - const paramsNode = new SyntaxNode( - 'Params', - params[0]?.from ?? 0, - params.at(-1)?.to ?? 0 - ) + const paramsNode = new SyntaxNode('Params', params[0]?.from ?? 0, params.at(-1)?.to ?? 0) if (params.length) paramsNode.push(...params) node.add(paramsNode) @@ -561,8 +528,7 @@ export class Parser { const ident = this.input.slice(left.from, left.to) // not in scope, just return Word - if (!this.scope.has(ident)) - return this.word(left) + if (!this.scope.has(ident)) return this.word(left) if (left.type.is('Identifier')) left.type = 'IdentifierBeforeDot' @@ -602,16 +568,13 @@ export class Parser { const dotGet = this.dotGet() // if followed by a binary operator (not pipe), return dotGet/Word as-is for expression parser - if (this.is($T.Operator) && !this.is($T.Operator, '|')) - return dotGet + if (this.is($T.Operator) && !this.is($T.Operator, '|')) return dotGet // dotget not in scope, regular Word if (dotGet.type.is('Word')) return dotGet - if (this.isExprEnd()) - return this.functionCallOrIdentifier(dotGet) - else - return this.functionCall(dotGet) + if (this.isExprEnd()) return this.functionCallOrIdentifier(dotGet) + else return this.functionCall(dotGet) } // can be used in functions or try block @@ -763,7 +726,11 @@ export class Parser { const val = this.value() if (!['Null', 'Boolean', 'Number', 'String'].includes(val.type.name)) - throw new CompilerError(`Default value must be null, boolean, number, or string, got ${val.type.name}`, val.from, val.to) + throw new CompilerError( + `Default value must be null, boolean, number, or string, got ${val.type.name}`, + val.from, + val.to, + ) const node = new SyntaxNode('NamedParam', prefix.from, val.to) return node.push(prefix, val) @@ -781,7 +748,8 @@ export class Parser { op(op?: string): SyntaxNode { const token = op ? this.expect($T.Operator, op) : this.expect($T.Operator) const name = operators[token.value!] - if (!name) throw new CompilerError(`Operator not registered: ${token.value!}`, token.from, token.to) + if (!name) + throw new CompilerError(`Operator not registered: ${token.value!}`, token.from, token.to) return new SyntaxNode(name, token.from, token.to) } @@ -828,11 +796,9 @@ export class Parser { let last = tryBlock.at(-1) let catchNode, finalNode - if (this.is($T.Keyword, 'catch')) - catchNode = this.catch() + if (this.is($T.Keyword, 'catch')) catchNode = this.catch() - if (this.is($T.Keyword, 'finally')) - finalNode = this.finally() + if (this.is($T.Keyword, 'finally')) finalNode = this.finally() const end = this.keyword('end') @@ -842,11 +808,9 @@ export class Parser { const node = new SyntaxNode('TryExpr', tryNode.from, last!.to) node.push(tryNode, ...tryBlock) - if (catchNode) - node.push(catchNode) + if (catchNode) node.push(catchNode) - if (finalNode) - node.push(finalNode) + if (finalNode) node.push(finalNode) return node.push(end) } @@ -868,8 +832,7 @@ export class Parser { while (this.is($T.Operator, '.')) { this.next() - if (this.isAny($T.Word, $T.Identifier, $T.Number)) - parts.push(this.next()) + if (this.isAny($T.Word, $T.Identifier, $T.Number)) parts.push(this.next()) } return new SyntaxNode('Word', parts[0]!.from, parts.at(-1)!.to) @@ -892,8 +855,7 @@ export class Parser { let offset = 1 let peek = this.peek(offset) - while (peek && peek.type === $T.Newline) - peek = this.peek(++offset) + while (peek && peek.type === $T.Newline) peek = this.peek(++offset) if (!peek || peek.type !== type) return false if (value !== undefined && peek.value !== value) return false @@ -914,7 +876,7 @@ export class Parser { } isAny(...type: TokenType[]): boolean { - return type.some(x => this.is(x)) + return type.some((x) => this.is(x)) } nextIs(type: TokenType, value?: string): boolean { @@ -925,43 +887,58 @@ export class Parser { } nextIsAny(...type: TokenType[]): boolean { - return type.some(x => this.nextIs(x)) + return type.some((x) => this.nextIs(x)) } isExprEnd(): boolean { - return this.isAny($T.Colon, $T.Semicolon, $T.Newline, $T.CloseParen, $T.CloseBracket) || + return ( + this.isAny($T.Colon, $T.Semicolon, $T.Newline, $T.CloseParen, $T.CloseBracket) || this.is($T.Operator, '|') || - this.isExprEndKeyword() || !this.current() + this.isExprEndKeyword() || + !this.current() + ) } nextIsExprEnd(): boolean { // pipes act like expression end for function arg parsing - if (this.nextIs($T.Operator, '|')) - return true + if (this.nextIs($T.Operator, '|')) return true - return this.nextIsAny($T.Colon, $T.Semicolon, $T.Newline, $T.CloseBracket, $T.CloseParen) || - this.nextIs($T.Keyword, 'end') || this.nextIs($T.Keyword, 'else') || - this.nextIs($T.Keyword, 'catch') || this.nextIs($T.Keyword, 'finally') || + return ( + this.nextIsAny($T.Colon, $T.Semicolon, $T.Newline, $T.CloseBracket, $T.CloseParen) || + this.nextIs($T.Keyword, 'end') || + this.nextIs($T.Keyword, 'else') || + this.nextIs($T.Keyword, 'catch') || + this.nextIs($T.Keyword, 'finally') || !this.peek() + ) } isExprEndKeyword(): boolean { - return this.is($T.Keyword, 'end') || this.is($T.Keyword, 'else') || - this.is($T.Keyword, 'catch') || this.is($T.Keyword, 'finally') + return ( + this.is($T.Keyword, 'end') || + this.is($T.Keyword, 'else') || + this.is($T.Keyword, 'catch') || + this.is($T.Keyword, 'finally') + ) } isPipe(): boolean { // inside parens, only look for pipes on same line (don't look past newlines) const canLookPastNewlines = this.inParens === 0 - return this.is($T.Operator, '|') || - (canLookPastNewlines && this.peekPastNewlines($T.Operator, '|')) + return ( + this.is($T.Operator, '|') || (canLookPastNewlines && this.peekPastNewlines($T.Operator, '|')) + ) } expect(type: TokenType, value?: string): Token | never { if (!this.is(type, value)) { const token = this.current() - throw new CompilerError(`Expected ${TokenType[type]}${value ? ` "${value}"` : ''}, got ${TokenType[token?.type || 0]}${token?.value ? ` "${token.value}"` : ''} at position ${this.pos}`, token.from, token.to) + throw new CompilerError( + `Expected ${TokenType[type]}${value ? ` "${value}"` : ''}, got ${TokenType[token?.type || 0]}${token?.value ? ` "${token.value}"` : ''} at position ${this.pos}`, + token.from, + token.to, + ) } return this.next() } @@ -981,7 +958,7 @@ function collapseDotGets(origNodes: SyntaxNode[]): SyntaxNode { if (left.type.is('Identifier')) left.type = 'IdentifierBeforeDot' - const dot = new SyntaxNode("DotGet", left.from, right.to) + const dot = new SyntaxNode('DotGet', left.from, right.to) dot.push(left, right) right = dot diff --git a/src/parser/stringParser.ts b/src/parser/stringParser.ts index 4218b54..30e9b60 100644 --- a/src/parser/stringParser.ts +++ b/src/parser/stringParser.ts @@ -39,11 +39,18 @@ export const parseString = (input: string, from: number, to: number, parser: any * Parse single-quoted string: 'hello $name\n' * Supports: interpolation ($var, $(expr)), escape sequences (\n, \$, etc) */ -const parseSingleQuoteString = (stringNode: SyntaxNode, input: string, from: number, to: number, parser: any) => { +const parseSingleQuoteString = ( + stringNode: SyntaxNode, + input: string, + from: number, + to: number, + parser: any, +) => { let pos = from + 1 // Skip opening ' let fragmentStart = pos - while (pos < to - 1) { // -1 to skip closing ' + while (pos < to - 1) { + // -1 to skip closing ' const char = input[pos] // Escape sequence @@ -115,7 +122,13 @@ const parseSingleQuoteString = (stringNode: SyntaxNode, input: string, from: num * Supports: interpolation ($var, $(expr)), nested braces * Does NOT support: escape sequences (raw content) */ -const parseCurlyString = (stringNode: SyntaxNode, input: string, from: number, to: number, parser: any) => { +const parseCurlyString = ( + stringNode: SyntaxNode, + input: string, + from: number, + to: number, + parser: any, +) => { let pos = from + 1 // Skip opening { let fragmentStart = from // Include the opening { in the fragment let depth = 1 @@ -188,7 +201,11 @@ const parseCurlyString = (stringNode: SyntaxNode, input: string, from: number, t * Returns the parsed expression node and the position after the closing ) * pos is position of the opening ( in the full input string */ -const parseInterpolationExpr = (input: string, pos: number, parser: any): { node: SyntaxNode, endPos: number } => { +const parseInterpolationExpr = ( + input: string, + pos: number, + parser: any, +): { node: SyntaxNode; endPos: number } => { // Find matching closing paren let depth = 1 let start = pos diff --git a/src/parser/tests/control-flow.test.ts b/src/parser/tests/control-flow.test.ts index 4e776ee..22339fe 100644 --- a/src/parser/tests/control-flow.test.ts +++ b/src/parser/tests/control-flow.test.ts @@ -195,7 +195,6 @@ describe('if/else if/else', () => { `) }) - test('parses function calls in else-if tests', () => { expect(`if false: true else if var? 'abc': true end`).toMatchTree(` IfExpr @@ -286,7 +285,6 @@ describe('while', () => { keyword end`) }) - test('compound expression', () => { expect(`while a > 0 and b < 100 and c < 1000: true end`).toMatchTree(` WhileExpr @@ -344,7 +342,6 @@ describe('while', () => { keyword end`) }) - test('multiline compound expression', () => { expect(` while a > 0 and b < 100 and c < 1000: @@ -373,4 +370,4 @@ describe('while', () => { Boolean true keyword end`) }) -}) \ No newline at end of file +}) diff --git a/src/parser/tests/destructuring.test.ts b/src/parser/tests/destructuring.test.ts index f3aa839..a45b76b 100644 --- a/src/parser/tests/destructuring.test.ts +++ b/src/parser/tests/destructuring.test.ts @@ -53,4 +53,4 @@ describe('Array destructuring', () => { IdentifierBeforeDot a Number 1`) }) -}) \ No newline at end of file +}) diff --git a/src/parser/tests/function-blocks.test.ts b/src/parser/tests/function-blocks.test.ts index c70ac2c..d6679bc 100644 --- a/src/parser/tests/function-blocks.test.ts +++ b/src/parser/tests/function-blocks.test.ts @@ -14,8 +14,7 @@ describe('single line function blocks', () => { Identifier bye PositionalArg Identifier bye - keyword end` - ) + keyword end`) }) test('work with one arg', () => { @@ -33,8 +32,7 @@ describe('single line function blocks', () => { Identifier bye PositionalArg Identifier bye - keyword end` - ) + keyword end`) }) test('work with named args', () => { @@ -54,11 +52,9 @@ describe('single line function blocks', () => { Identifier bye PositionalArg Identifier bye - keyword end` - ) + keyword end`) }) - test('work with dot-get', () => { expect(`signals = [=]; signals.trap 'EXIT': echo bye bye end`).toMatchTree(` Assign @@ -81,8 +77,7 @@ describe('single line function blocks', () => { Identifier bye PositionalArg Identifier bye - keyword end` - ) + keyword end`) }) }) @@ -104,8 +99,7 @@ end Identifier bye PositionalArg Identifier bye - keyword end` - ) + keyword end`) }) test('work with one arg', () => { @@ -126,8 +120,7 @@ end`).toMatchTree(` Identifier bye PositionalArg Identifier bye - keyword end` - ) + keyword end`) }) test('work with named args', () => { @@ -153,11 +146,9 @@ end`).toMatchTree(` Identifier bye PositionalArg Identifier bye - keyword end` - ) + keyword end`) }) - test('work with dot-get', () => { expect(` signals = [=] @@ -184,8 +175,7 @@ end`).toMatchTree(` Identifier bye PositionalArg Identifier bye - keyword end` - ) + keyword end`) }) }) @@ -262,8 +252,7 @@ end`).toMatchTree(` p: h1 class=bright style='font-family: helvetica' Heya h2 man that is (b wild)! -end`) - .toMatchTree(` +end`).toMatchTree(` FunctionCallWithBlock FunctionCallOrIdentifier Identifier p @@ -298,4 +287,4 @@ end`) Word ! keyword end`) }) -}) \ No newline at end of file +}) diff --git a/src/parser/tests/functions.test.ts b/src/parser/tests/functions.test.ts index 1d98721..4750a2c 100644 --- a/src/parser/tests/functions.test.ts +++ b/src/parser/tests/functions.test.ts @@ -92,7 +92,6 @@ describe('calling functions', () => { `) }) - test('command with arg that is also a command', () => { expect('tail tail').toMatchTree(` FunctionCall @@ -281,4 +280,4 @@ describe('default params', () => { Identifier y keyword end`) }) -}) \ No newline at end of file +}) diff --git a/src/parser/tests/import.test.ts b/src/parser/tests/import.test.ts index 71537e3..f60eafa 100644 --- a/src/parser/tests/import.test.ts +++ b/src/parser/tests/import.test.ts @@ -29,4 +29,4 @@ describe('import', () => { Identifier ends-with? `) }) -}) \ No newline at end of file +}) diff --git a/src/parser/tests/pipes.test.ts b/src/parser/tests/pipes.test.ts index b281381..985e2b7 100644 --- a/src/parser/tests/pipes.test.ts +++ b/src/parser/tests/pipes.test.ts @@ -404,4 +404,4 @@ describe('Underscore', () => { Number 5 `) }) -}) \ No newline at end of file +}) diff --git a/src/parser/tests/strings.test.ts b/src/parser/tests/strings.test.ts index c8d95b6..521c22e 100644 --- a/src/parser/tests/strings.test.ts +++ b/src/parser/tests/strings.test.ts @@ -161,7 +161,7 @@ describe('curly strings', () => { }) describe('double quoted strings', () => { - test("work", () => { + test('work', () => { expect(`"hello world"`).toMatchTree(` String DoubleQuote "hello world"`) diff --git a/src/parser/tests/tokens.test.ts b/src/parser/tests/tokens.test.ts index 4d46790..9c92c39 100644 --- a/src/parser/tests/tokens.test.ts +++ b/src/parser/tests/tokens.test.ts @@ -15,10 +15,7 @@ describe('numbers', () => { test('non-numbers', () => { expect(`1st`).toMatchToken('Word', '1st') expect(`1_`).toMatchToken('Word', '1_') - expect(`100.`).toMatchTokens( - { type: 'Number', value: '100' }, - { type: 'Operator', value: '.' }, - ) + expect(`100.`).toMatchTokens({ type: 'Number', value: '100' }, { type: 'Operator', value: '.' }) }) test('simple numbers', () => { @@ -130,10 +127,7 @@ describe('identifiers', () => { expect('dog#pound').toMatchToken('Word', 'dog#pound') expect('http://website.com').toMatchToken('Word', 'http://website.com') expect('school$cool').toMatchToken('Identifier', 'school$cool') - expect('EXIT:').toMatchTokens( - { type: 'Word', value: 'EXIT' }, - { type: 'Colon' }, - ) + expect('EXIT:').toMatchTokens({ type: 'Word', value: 'EXIT' }, { type: 'Colon' }) expect(`if y == 1: 'cool' end`).toMatchTokens( { type: 'Keyword', value: 'if' }, { type: 'Identifier', value: 'y' }, @@ -214,18 +208,24 @@ describe('curly strings', () => { expect(`{ one two - three }`).toMatchToken('String', `{ + three }`).toMatchToken( + 'String', + `{ one two - three }`) + three }`, + ) }) test('can contain other curlies', () => { expect(`{ { one } two - { three } }`).toMatchToken('String', `{ { one } + { three } }`).toMatchToken( + 'String', + `{ { one } two - { three } }`) + { three } }`, + ) }) test('empty curly string', () => { @@ -408,12 +408,12 @@ f ]`).toMatchTokens( { type: 'OpenBracket' }, - { type: 'Identifier', value: "a" }, - { type: 'Identifier', value: "b" }, - { type: 'Identifier', value: "c" }, - { type: 'Identifier', value: "d" }, - { type: 'Identifier', value: "e" }, - { type: 'Identifier', value: "f" }, + { type: 'Identifier', value: 'a' }, + { type: 'Identifier', value: 'b' }, + { type: 'Identifier', value: 'c' }, + { type: 'Identifier', value: 'd' }, + { type: 'Identifier', value: 'e' }, + { type: 'Identifier', value: 'f' }, { type: 'CloseBracket' }, ) }) @@ -506,7 +506,6 @@ f { type: 'Identifier', value: 'y' }, ) - expect(`if (var? 'abc'): y`).toMatchTokens( { type: 'Keyword', value: 'if' }, { type: 'OpenParen' }, @@ -552,25 +551,25 @@ end`).toMatchTokens( test('dot operator beginning word with slash', () => { expect(`(basename ./cool)`).toMatchTokens( - { 'type': 'OpenParen' }, - { 'type': 'Identifier', 'value': 'basename' }, - { 'type': 'Word', 'value': './cool' }, - { 'type': 'CloseParen' } + { type: 'OpenParen' }, + { type: 'Identifier', value: 'basename' }, + { type: 'Word', value: './cool' }, + { type: 'CloseParen' }, ) }) test('dot word after identifier with space', () => { expect(`expand-path .git`).toMatchTokens( - { 'type': 'Identifier', 'value': 'expand-path' }, - { 'type': 'Word', 'value': '.git' }, + { type: 'Identifier', value: 'expand-path' }, + { type: 'Word', value: '.git' }, ) }) test('dot operator after identifier without space', () => { expect(`config.path`).toMatchTokens( - { 'type': 'Identifier', 'value': 'config' }, - { 'type': 'Operator', 'value': '.' }, - { 'type': 'Identifier', 'value': 'path' }, + { type: 'Identifier', value: 'config' }, + { type: 'Operator', value: '.' }, + { type: 'Identifier', value: 'path' }, ) }) }) @@ -649,11 +648,7 @@ describe('empty and whitespace input', () => { }) test('only newlines', () => { - expect('\n\n\n').toMatchTokens( - { type: 'Newline' }, - { type: 'Newline' }, - { type: 'Newline' }, - ) + expect('\n\n\n').toMatchTokens({ type: 'Newline' }, { type: 'Newline' }, { type: 'Newline' }) }) }) @@ -665,14 +660,14 @@ describe('named args', () => { ) }) - test("can have spaces", () => { + test('can have spaces', () => { expect(`named= arg`).toMatchTokens( { type: 'NamedArgPrefix', value: 'named=' }, { type: 'Identifier', value: 'arg' }, ) }) - test("can include numbers", () => { + test('can include numbers', () => { expect(`named123= arg`).toMatchTokens( { type: 'NamedArgPrefix', value: 'named123=' }, { type: 'Identifier', value: 'arg' }, @@ -747,4 +742,4 @@ describe('dot operator', () => { { type: 'CloseParen' }, ) }) -}) \ No newline at end of file +}) diff --git a/src/parser/tokenizer2.ts b/src/parser/tokenizer2.ts index 4619c55..a5d4a89 100644 --- a/src/parser/tokenizer2.ts +++ b/src/parser/tokenizer2.ts @@ -2,9 +2,9 @@ const DEBUG = process.env.DEBUG || false export type Token = { type: TokenType - value?: string, - from: number, - to: number, + value?: string + from: number + to: number } export enum TokenType { @@ -36,10 +36,16 @@ export enum TokenType { const valueTokens = new Set([ TokenType.Comment, - TokenType.Keyword, TokenType.Operator, - TokenType.Identifier, TokenType.Word, TokenType.NamedArgPrefix, - TokenType.Boolean, TokenType.Number, TokenType.String, TokenType.Regex, - TokenType.Underscore + TokenType.Keyword, + TokenType.Operator, + TokenType.Identifier, + TokenType.Word, + TokenType.NamedArgPrefix, + TokenType.Boolean, + TokenType.Number, + TokenType.String, + TokenType.Regex, + TokenType.Underscore, ]) const operators = new Set([ @@ -109,7 +115,7 @@ const keywords = new Set([ // helper function c(strings: TemplateStringsArray, ...values: any[]) { - return strings.reduce((result, str, i) => result + str + (values[i] ?? ""), "").charCodeAt(0) + return strings.reduce((result, str, i) => result + str + (values[i] ?? ''), '').charCodeAt(0) } function s(c: number): string { @@ -155,11 +161,17 @@ export class Scanner { to ??= this.pos - getCharSize(this.char) if (to < from) to = from - this.tokens.push(Object.assign({}, { - type, - from, - to, - }, valueTokens.has(type) ? { value: this.input.slice(from, to) } : {})) + this.tokens.push( + Object.assign( + {}, + { + type, + from, + to, + }, + valueTokens.has(type) ? { value: this.input.slice(from, to) } : {}, + ), + ) if (DEBUG) { const tok = this.tokens.at(-1) @@ -238,8 +250,7 @@ export class Scanner { } if (char === c`\n`) { - if (this.inParen === 0 && this.inBracket === 0) - this.pushChar(TokenType.Newline) + if (this.inParen === 0 && this.inBracket === 0) this.pushChar(TokenType.Newline) this.next() continue } @@ -266,16 +277,20 @@ export class Scanner { switch (this.char) { case c`(`: this.inParen++ - this.pushChar(TokenType.OpenParen); break + this.pushChar(TokenType.OpenParen) + break case c`)`: this.inParen-- - this.pushChar(TokenType.CloseParen); break + this.pushChar(TokenType.CloseParen) + break case c`[`: this.inBracket++ - this.pushChar(TokenType.OpenBracket); break + this.pushChar(TokenType.OpenBracket) + break case c`]`: this.inBracket-- - this.pushChar(TokenType.CloseBracket); break + this.pushChar(TokenType.CloseBracket) + break } this.next() } @@ -339,29 +354,14 @@ export class Scanner { const word = this.input.slice(this.start, this.pos - getCharSize(this.char)) // classify the token based on what we read - if (word === '_') - this.push(TokenType.Underscore) - - else if (word === 'null') - this.push(TokenType.Null) - - else if (word === 'true' || word === 'false') - this.push(TokenType.Boolean) - - else if (isKeyword(word)) - this.push(TokenType.Keyword) - - else if (isOperator(word)) - this.push(TokenType.Operator) - - else if (isIdentifer(word)) - this.push(TokenType.Identifier) - - else if (word.endsWith('=')) - this.push(TokenType.NamedArgPrefix) - - else - this.push(TokenType.Word) + if (word === '_') this.push(TokenType.Underscore) + else if (word === 'null') this.push(TokenType.Null) + else if (word === 'true' || word === 'false') this.push(TokenType.Boolean) + else if (isKeyword(word)) this.push(TokenType.Keyword) + else if (isOperator(word)) this.push(TokenType.Operator) + else if (isIdentifer(word)) this.push(TokenType.Identifier) + else if (word.endsWith('=')) this.push(TokenType.NamedArgPrefix) + else this.push(TokenType.Word) } readNumber() { @@ -394,8 +394,7 @@ export class Scanner { this.next() // skip / // read regex flags - while (this.char > 0 && isIdentStart(this.char)) - this.next() + while (this.char > 0 && isIdentStart(this.char)) this.next() // validate regex const to = this.pos - getCharSize(this.char) @@ -422,30 +421,29 @@ export class Scanner { } canBeDotGet(lastToken?: Token): boolean { - return !this.prevIsWhitespace && !!lastToken && + return ( + !this.prevIsWhitespace && + !!lastToken && (lastToken.type === TokenType.Identifier || lastToken.type === TokenType.Number || lastToken.type === TokenType.CloseParen || lastToken.type === TokenType.CloseBracket) + ) } } const isNumber = (word: string): boolean => { // regular number - if (/^[+-]?\d+(_?\d+)*(\.(\d+(_?\d+)*))?$/.test(word)) - return true + if (/^[+-]?\d+(_?\d+)*(\.(\d+(_?\d+)*))?$/.test(word)) return true // binary - if (/^[+-]?0b[01]+(_?[01]+)*(\.[01](_?[01]*))?$/.test(word)) - return true + if (/^[+-]?0b[01]+(_?[01]+)*(\.[01](_?[01]*))?$/.test(word)) return true // octal - if (/^[+-]?0o[0-7]+(_?[0-7]+)*(\.[0-7](_?[0-7]*))?$/.test(word)) - return true + if (/^[+-]?0o[0-7]+(_?[0-7]+)*(\.[0-7](_?[0-7]*))?$/.test(word)) return true // hex - if (/^[+-]?0x[0-9a-f]+([0-9a-f]_?[0-9a-f]+)*(\.([0-9a-f]_?[0-9a-f]*))?$/i.test(word)) - return true + if (/^[+-]?0x[0-9a-f]+([0-9a-f]_?[0-9a-f]+)*(\.([0-9a-f]_?[0-9a-f]*))?$/i.test(word)) return true return false } @@ -461,14 +459,14 @@ const isIdentifer = (s: string): boolean => { chars.push(out) } - if (chars.length === 1) - return isIdentStart(chars[0]!) - else if (chars.length === 2) - return isIdentStart(chars[0]!) && isIdentEnd(chars[1]!) + if (chars.length === 1) return isIdentStart(chars[0]!) + else if (chars.length === 2) return isIdentStart(chars[0]!) && isIdentEnd(chars[1]!) else - return isIdentStart(chars[0]!) && + return ( + isIdentStart(chars[0]!) && chars.slice(1, chars.length - 1).every(isIdentChar) && isIdentEnd(chars.at(-1)!) + ) } const isStringDelim = (ch: number): boolean => { @@ -498,9 +496,14 @@ const isDigit = (ch: number): boolean => { } const isWhitespace = (ch: number): boolean => { - return ch === 32 /* space */ || ch === 9 /* tab */ || - ch === 13 /* \r */ || ch === 10 /* \n */ || - ch === -1 || ch === 0 /* EOF */ + return ( + ch === 32 /* space */ || + ch === 9 /* tab */ || + ch === 13 /* \r */ || + ch === 10 /* \n */ || + ch === -1 || + ch === 0 + ) /* EOF */ } const isWordChar = (ch: number): boolean => { @@ -527,8 +530,7 @@ const isBracket = (char: number): boolean => { return char === c`(` || char === c`)` || char === c`[` || char === c`]` } -const getCharSize = (ch: number) => - (ch > 0xffff ? 2 : 1) // emoji takes 2 UTF-16 code units +const getCharSize = (ch: number) => (ch > 0xffff ? 2 : 1) // emoji takes 2 UTF-16 code units const getFullCodePoint = (input: string, pos: number): number => { const ch = input[pos]?.charCodeAt(0) || 0 diff --git a/src/prelude/date.ts b/src/prelude/date.ts index dda92ef..635c8de 100644 --- a/src/prelude/date.ts +++ b/src/prelude/date.ts @@ -1,12 +1,12 @@ export const date = { now: () => Date.now(), - year: (time: number) => (new Date(time)).getFullYear(), - month: (time: number) => (new Date(time)).getMonth(), - date: (time: number) => (new Date(time)).getDate(), - hour: (time: number) => (new Date(time)).getHours(), - minute: (time: number) => (new Date(time)).getMinutes(), - second: (time: number) => (new Date(time)).getSeconds(), - ms: (time: number) => (new Date(time)).getMilliseconds(), + year: (time: number) => new Date(time).getFullYear(), + month: (time: number) => new Date(time).getMonth(), + date: (time: number) => new Date(time).getDate(), + hour: (time: number) => new Date(time).getHours(), + minute: (time: number) => new Date(time).getMinutes(), + second: (time: number) => new Date(time).getSeconds(), + ms: (time: number) => new Date(time).getMilliseconds(), new: (year: number, month: number, day: number, hour = 0, minute = 0, second = 0, ms = 0) => - new Date(year, month, day, hour, minute, second, ms).getTime() -} \ No newline at end of file + new Date(year, month, day, hour, minute, second, ms).getTime(), +} diff --git a/src/prelude/dict.ts b/src/prelude/dict.ts index 15b71f8..659736d 100644 --- a/src/prelude/dict.ts +++ b/src/prelude/dict.ts @@ -3,9 +3,11 @@ import { type Value, toString } from 'reefvm' export const dict = { keys: (dict: Record) => Object.keys(dict), values: (dict: Record) => Object.values(dict), - entries: (dict: Record) => Object.entries(dict).map(([k, v]) => ({ key: k, value: v })), + entries: (dict: Record) => + Object.entries(dict).map(([k, v]) => ({ key: k, value: v })), 'has?': (dict: Record, key: string) => key in dict, - get: (dict: Record, key: string, defaultValue: any = null) => dict[key] ?? defaultValue, + get: (dict: Record, key: string, defaultValue: any = null) => + dict[key] ?? defaultValue, set: (dict: Value, key: Value, value: Value) => { const map = dict.value as Map map.set(toString(key), value) @@ -30,6 +32,6 @@ export const dict = { 'from-entries': (entries: [string, any][]) => Object.fromEntries(entries), } - // raw functions deal directly in Value types, meaning we can modify collection - // careful - they MUST return a Value! - ; (dict.set as any).raw = true +// raw functions deal directly in Value types, meaning we can modify collection +// careful - they MUST return a Value! +;(dict.set as any).raw = true diff --git a/src/prelude/fs.ts b/src/prelude/fs.ts index bb97a60..d0c6326 100644 --- a/src/prelude/fs.ts +++ b/src/prelude/fs.ts @@ -1,23 +1,33 @@ import { join, resolve, basename, dirname, extname } from 'path' import { - readdirSync, mkdirSync, rmdirSync, - readFileSync, writeFileSync, appendFileSync, - rmSync, copyFileSync, - statSync, lstatSync, chmodSync, symlinkSync, readlinkSync, - watch -} from "fs" + readdirSync, + mkdirSync, + rmdirSync, + readFileSync, + writeFileSync, + appendFileSync, + rmSync, + copyFileSync, + statSync, + lstatSync, + chmodSync, + symlinkSync, + readlinkSync, + watch, +} from 'fs' export const fs = { // Directory operations ls: (path: string) => readdirSync(path), mkdir: (path: string) => mkdirSync(path, { recursive: true }), - rmdir: (path: string) => rmdirSync(path === '/' || path === '' ? '/tmp/*' : path, { recursive: true }), + rmdir: (path: string) => + rmdirSync(path === '/' || path === '' ? '/tmp/*' : path, { recursive: true }), pwd: () => process.cwd(), cd: (path: string) => process.chdir(path), // Reading read: (path: string) => readFileSync(path, 'utf-8'), - cat: (path: string) => { }, // added below + cat: (path: string) => {}, // added below 'read-bytes': (path: string) => [...readFileSync(path)], // Writing @@ -26,13 +36,13 @@ export const fs = { // File operations delete: (path: string) => rmSync(path), - rm: (path: string) => { }, // added below + rm: (path: string) => {}, // added below copy: (from: string, to: string) => copyFileSync(from, to), move: (from: string, to: string) => { fs.copy(from, to) fs.rm(from) }, - mv: (from: string, to: string) => { }, // added below + mv: (from: string, to: string) => {}, // added below // Path operations basename: (path: string) => basename(path), @@ -58,39 +68,50 @@ export const fs = { } catch { return {} } - }, 'exists?': (path: string) => { try { statSync(path) return true - } - catch { + } catch { return false } }, 'file?': (path: string) => { - try { return statSync(path).isFile() } - catch { return false } + try { + return statSync(path).isFile() + } catch { + return false + } }, 'dir?': (path: string) => { - try { return statSync(path).isDirectory() } - catch { return false } + try { + return statSync(path).isDirectory() + } catch { + return false + } }, 'symlink?': (path: string) => { - try { return lstatSync(path).isSymbolicLink() } - catch { return false } + try { + return lstatSync(path).isSymbolicLink() + } catch { + return false + } }, 'exec?': (path: string) => { try { const stats = statSync(path) return !!(stats.mode & 0o111) + } catch { + return false } - catch { return false } }, size: (path: string) => { - try { return statSync(path).size } - catch { return 0 } + try { + return statSync(path).size + } catch { + return 0 + } }, // Permissions @@ -114,15 +135,12 @@ export const fs = { return readdirSync(dir) .filter((f) => f.endsWith(ext)) .map((f) => join(dir, f)) - }, watch: (path: string, callback: Function) => watch(path, (event, filename) => callback(event, filename)), } - - - ; (fs as any).cat = fs.read - ; (fs as any).mv = fs.move - ; (fs as any).cp = fs.copy - ; (fs as any).rm = fs.delete \ No newline at end of file +;(fs as any).cat = fs.read +;(fs as any).mv = fs.move +;(fs as any).cp = fs.copy +;(fs as any).rm = fs.delete diff --git a/src/prelude/index.ts b/src/prelude/index.ts index c0fb87b..ba1126b 100644 --- a/src/prelude/index.ts +++ b/src/prelude/index.ts @@ -2,8 +2,12 @@ import { join, resolve } from 'path' import { - type Value, type VM, toValue, - extractParamInfo, isWrapped, getOriginalFunction, + type Value, + type VM, + toValue, + extractParamInfo, + isWrapped, + getOriginalFunction, } from 'reefvm' import { date } from './date' @@ -35,16 +39,18 @@ export const globals: Record = { cwd: process.env.PWD, script: { name: Bun.argv[2] || '(shrimp)', - path: resolve(join('.', Bun.argv[2] ?? '')) + path: resolve(join('.', Bun.argv[2] ?? '')), }, }, // hello echo: (...args: any[]) => { - console.log(...args.map(a => { - const v = toValue(a) - return ['array', 'dict'].includes(v.type) ? formatValue(v, true) : v.value - })) + console.log( + ...args.map((a) => { + const v = toValue(a) + return ['array', 'dict'].includes(v.type) ? formatValue(v, true) : v.value + }), + ) return toValue(null) }, @@ -63,11 +69,10 @@ export const globals: Record = { }, ref: (fn: Function) => fn, import: function (this: VM, atNamed: Record = {}, ...idents: string[]) { - const onlyArray = Array.isArray(atNamed.only) ? atNamed.only : [atNamed.only].filter(a => a) + const onlyArray = Array.isArray(atNamed.only) ? atNamed.only : [atNamed.only].filter((a) => a) const only = new Set(onlyArray) const wantsOnly = only.size > 0 - for (const ident of idents) { const module = this.get(ident) @@ -100,9 +105,13 @@ export const globals: Record = { length: (v: any) => { const value = toValue(v) switch (value.type) { - case 'string': case 'array': return value.value.length - case 'dict': return value.value.size - default: throw new Error(`length: expected string, array, or dict, got ${value.type}`) + case 'string': + case 'array': + return value.value.length + case 'dict': + return value.value.size + default: + throw new Error(`length: expected string, array, or dict, got ${value.type}`) } }, at: (collection: any, index: number | string) => { @@ -110,7 +119,9 @@ export const globals: Record = { if (value.type === 'string' || value.type === 'array') { const idx = typeof index === 'number' ? index : parseInt(index as string) if (idx < 0 || idx >= value.value.length) { - throw new Error(`at: index ${idx} out of bounds for ${value.type} of length ${value.value.length}`) + throw new Error( + `at: index ${idx} out of bounds for ${value.type} of length ${value.value.length}`, + ) } return value.value[idx] } else if (value.type === 'dict') { @@ -137,7 +148,8 @@ export const globals: Record = { 'empty?': (v: any) => { const value = toValue(v) switch (value.type) { - case 'string': case 'array': + case 'string': + case 'array': return value.value.length === 0 case 'dict': return value.value.size === 0 @@ -151,7 +163,6 @@ export const globals: Record = { for (const value of list) await cb(value) return list }, - } export const colors = { @@ -164,7 +175,7 @@ export const colors = { red: '\x1b[31m', blue: '\x1b[34m', magenta: '\x1b[35m', - pink: '\x1b[38;2;255;105;180m' + pink: '\x1b[38;2;255;105;180m', } export function formatValue(value: Value, inner = false): string { @@ -178,15 +189,15 @@ export function formatValue(value: Value, inner = false): string { case 'null': return `${colors.dim}null${colors.reset}` case 'array': { - const items = value.value.map(x => formatValue(x, true)).join(' ') + const items = value.value.map((x) => formatValue(x, true)).join(' ') return `${colors.blue}[${colors.reset}${items}${colors.blue}]${colors.reset}` } case 'dict': { - const entries = Array.from(value.value.entries()).reverse() + const entries = Array.from(value.value.entries()) + .reverse() .map(([k, v]) => `${k.trim()}${colors.blue}=${colors.reset}${formatValue(v, true)}`) .join(' ') - if (entries.length === 0) - return `${colors.blue}[=]${colors.reset}` + if (entries.length === 0) return `${colors.blue}[=]${colors.reset}` return `${colors.blue}[${colors.reset}${entries}${colors.blue}]${colors.reset}` } case 'function': { @@ -206,5 +217,4 @@ export function formatValue(value: Value, inner = false): string { } // add types functions to top-level namespace -for (const [key, value] of Object.entries(types)) - globals[key] = value \ No newline at end of file +for (const [key, value] of Object.entries(types)) globals[key] = value diff --git a/src/prelude/json.ts b/src/prelude/json.ts index c54a908..a510730 100644 --- a/src/prelude/json.ts +++ b/src/prelude/json.ts @@ -2,6 +2,5 @@ export const json = { encode: (s: any) => JSON.stringify(s), decode: (s: string) => JSON.parse(s), } - - ; (json as any).parse = json.decode - ; (json as any).stringify = json.encode \ No newline at end of file +;(json as any).parse = json.decode +;(json as any).stringify = json.encode diff --git a/src/prelude/list.ts b/src/prelude/list.ts index 9f0517d..5bab77b 100644 --- a/src/prelude/list.ts +++ b/src/prelude/list.ts @@ -46,7 +46,7 @@ export const list = { }, 'all?': async (list: any[], cb: Function) => { for (const value of list) { - if (!await cb(value)) return false + if (!(await cb(value))) return false } return true }, @@ -131,7 +131,7 @@ export const list = { } return [truthy, falsy] }, - compact: (list: any[]) => list.filter(x => x != null), + compact: (list: any[]) => list.filter((x) => x != null), 'group-by': async (list: any[], cb: Function) => { const groups: Record = {} for (const value of list) { @@ -143,12 +143,11 @@ export const list = { }, } - - // raw functions deal directly in Value types, meaning we can modify collection - // careful - they MUST return a Value! - ; (list.splice as any).raw = true - ; (list.push as any).raw = true - ; (list.pop as any).raw = true - ; (list.shift as any).raw = true - ; (list.unshift as any).raw = true - ; (list.insert as any).raw = true \ No newline at end of file +// raw functions deal directly in Value types, meaning we can modify collection +// careful - they MUST return a Value! +;(list.splice as any).raw = true +;(list.push as any).raw = true +;(list.pop as any).raw = true +;(list.shift as any).raw = true +;(list.unshift as any).raw = true +;(list.insert as any).raw = true diff --git a/src/prelude/load.ts b/src/prelude/load.ts index cd188a0..25c1bb0 100644 --- a/src/prelude/load.ts +++ b/src/prelude/load.ts @@ -20,12 +20,11 @@ export const load = async function (this: VM, path: string): Promise = {} - for (const [name, value] of this.scope.locals.entries()) - module[name] = value + for (const [name, value] of this.scope.locals.entries()) module[name] = value this.scope = scope this.pc = pc this.stopped = false return module -} \ No newline at end of file +} diff --git a/src/prelude/math.ts b/src/prelude/math.ts index 04518ee..0045388 100644 --- a/src/prelude/math.ts +++ b/src/prelude/math.ts @@ -33,4 +33,4 @@ export const math = { 'positive?': (n: number) => n > 0, 'negative?': (n: number) => n < 0, 'zero?': (n: number) => n === 0, -} \ No newline at end of file +} diff --git a/src/prelude/str.ts b/src/prelude/str.ts index 3c9f17c..de8a086 100644 --- a/src/prelude/str.ts +++ b/src/prelude/str.ts @@ -17,17 +17,23 @@ export const str = { 'last-index-of': (str: string, search: string) => String(str ?? '').lastIndexOf(search), // transformations - replace: (str: string, search: string, replacement: string) => String(str ?? '').replace(search, replacement), - 'replace-all': (str: string, search: string, replacement: string) => String(str ?? '').replaceAll(search, replacement), - slice: (str: string, start: number, end?: number | null) => String(str ?? '').slice(start, end ?? undefined), - substring: (str: string, start: number, end?: number | null) => String(str ?? '').substring(start, end ?? undefined), + replace: (str: string, search: string, replacement: string) => + String(str ?? '').replace(search, replacement), + 'replace-all': (str: string, search: string, replacement: string) => + String(str ?? '').replaceAll(search, replacement), + slice: (str: string, start: number, end?: number | null) => + String(str ?? '').slice(start, end ?? undefined), + substring: (str: string, start: number, end?: number | null) => + String(str ?? '').substring(start, end ?? undefined), repeat: (str: string, count: number) => { if (count < 0) throw new Error(`repeat: count must be non-negative, got ${count}`) if (!Number.isInteger(count)) throw new Error(`repeat: count must be an integer, got ${count}`) return String(str ?? '').repeat(count) }, - 'pad-start': (str: string, length: number, pad: string = ' ') => String(str ?? '').padStart(length, pad), - 'pad-end': (str: string, length: number, pad: string = ' ') => String(str ?? '').padEnd(length, pad), + 'pad-start': (str: string, length: number, pad: string = ' ') => + String(str ?? '').padStart(length, pad), + 'pad-end': (str: string, length: number, pad: string = ' ') => + String(str ?? '').padEnd(length, pad), capitalize: (str: string) => { const s = String(str ?? '') return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase() @@ -44,4 +50,4 @@ export const str = { // regex match: (str: string, regex: RegExp) => String(str ?? '').match(regex), 'test?': (str: string, regex: RegExp) => regex.test(String(str ?? '')), -} \ No newline at end of file +} diff --git a/src/prelude/tests/date.test.ts b/src/prelude/tests/date.test.ts index 9a2f4a5..da85772 100644 --- a/src/prelude/tests/date.test.ts +++ b/src/prelude/tests/date.test.ts @@ -167,4 +167,4 @@ describe('date', () => { future > now `).toEvaluateTo(true) }) -}) \ No newline at end of file +}) diff --git a/src/prelude/tests/fs.test.ts b/src/prelude/tests/fs.test.ts index a6a6fef..74335c4 100644 --- a/src/prelude/tests/fs.test.ts +++ b/src/prelude/tests/fs.test.ts @@ -314,16 +314,18 @@ describe('fs - other', () => { writeFileSync(file, 'initial') let called = false - const watcher = fs.watch(file, () => { called = true }) + const watcher = fs.watch(file, () => { + called = true + }) // Trigger change - await new Promise(resolve => setTimeout(resolve, 100)) + await new Promise((resolve) => setTimeout(resolve, 100)) writeFileSync(file, 'updated') // Wait for watcher - await new Promise(resolve => setTimeout(resolve, 500)) + await new Promise((resolve) => setTimeout(resolve, 500)) expect(called).toBe(true) watcher.close?.() }) -}) \ No newline at end of file +}) diff --git a/src/prelude/tests/json.test.ts b/src/prelude/tests/json.test.ts index 544722e..aa9f9ff 100644 --- a/src/prelude/tests/json.test.ts +++ b/src/prelude/tests/json.test.ts @@ -5,14 +5,22 @@ describe('json', () => { expect(`json.decode '[1,2,3]'`).toEvaluateTo([1, 2, 3]) expect(`json.decode '"heya"'`).toEvaluateTo('heya') expect(`json.decode '[true, false, null]'`).toEvaluateTo([true, false, null]) - expect(`json.decode '{"a": true, "b": false, "c": "yeah"}'`).toEvaluateTo({ a: true, b: false, c: "yeah" }) + expect(`json.decode '{"a": true, "b": false, "c": "yeah"}'`).toEvaluateTo({ + a: true, + b: false, + c: 'yeah', + }) }) test('json.encode', () => { expect(`json.encode [1 2 3]`).toEvaluateTo('[1,2,3]') expect(`json.encode 'heya'`).toEvaluateTo('"heya"') expect(`json.encode [true false null]`).toEvaluateTo('[true,false,null]') - expect(`json.encode [a=true b=false c='yeah'] | json.decode`).toEvaluateTo({ a: true, b: false, c: "yeah" }) + expect(`json.encode [a=true b=false c='yeah'] | json.decode`).toEvaluateTo({ + a: true, + b: false, + c: 'yeah', + }) }) test('edge cases - empty structures', () => { @@ -51,27 +59,31 @@ describe('json', () => { }) test('nested structures - arrays', () => { - expect(`json.decode '[[1,2],[3,4],[5,6]]'`).toEvaluateTo([[1, 2], [3, 4], [5, 6]]) + expect(`json.decode '[[1,2],[3,4],[5,6]]'`).toEvaluateTo([ + [1, 2], + [3, 4], + [5, 6], + ]) expect(`json.decode '[1,[2,[3,[4]]]]'`).toEvaluateTo([1, [2, [3, [4]]]]) }) test('nested structures - objects', () => { expect(`json.decode '{"user":{"name":"Alice","age":30}}'`).toEvaluateTo({ - user: { name: 'Alice', age: 30 } + user: { name: 'Alice', age: 30 }, }) expect(`json.decode '{"a":{"b":{"c":"deep"}}}'`).toEvaluateTo({ - a: { b: { c: 'deep' } } + a: { b: { c: 'deep' } }, }) }) test('nested structures - mixed arrays and objects', () => { expect(`json.decode '[{"id":1,"tags":["a","b"]},{"id":2,"tags":["c"]}]'`).toEvaluateTo([ { id: 1, tags: ['a', 'b'] }, - { id: 2, tags: ['c'] } + { id: 2, tags: ['c'] }, ]) expect(`json.decode '{"items":[1,2,3],"meta":{"count":3}}'`).toEvaluateTo({ items: [1, 2, 3], - meta: { count: 3 } + meta: { count: 3 }, }) }) @@ -81,4 +93,4 @@ describe('json', () => { expect(`json.decode 'undefined'`).toFailEvaluation() expect(`json.decode ''`).toFailEvaluation() }) -}) \ No newline at end of file +}) diff --git a/src/prelude/tests/prelude.test.ts b/src/prelude/tests/prelude.test.ts index a260489..f426cd8 100644 --- a/src/prelude/tests/prelude.test.ts +++ b/src/prelude/tests/prelude.test.ts @@ -277,7 +277,10 @@ describe('collections', () => { }) test('list.zip combines two arrays', async () => { - await expect(`list.zip [1 2] [3 4]`).toEvaluateTo([[1, 3], [2, 4]]) + await expect(`list.zip [1 2] [3 4]`).toEvaluateTo([ + [1, 3], + [2, 4], + ]) }) test('list.first returns first element', async () => { @@ -447,7 +450,10 @@ describe('collections', () => { await expect(` gt-two = do x: x > 2 end list.partition [1 2 3 4 5] gt-two - `).toEvaluateTo([[3, 4, 5], [1, 2]]) + `).toEvaluateTo([ + [3, 4, 5], + [1, 2], + ]) }) test('list.compact removes null values', async () => { diff --git a/src/prelude/types.ts b/src/prelude/types.ts index a92c4c0..823856c 100644 --- a/src/prelude/types.ts +++ b/src/prelude/types.ts @@ -10,7 +10,6 @@ export const types = { 'string?': (v: any) => toValue(v).type === 'string', string: (v: any) => String(v), - 'array?': (v: any) => toValue(v).type === 'array', 'list?': (v: any) => toValue(v).type === 'array', diff --git a/src/server/index.css b/src/server/index.css index 2719f41..d1bb38e 100644 --- a/src/server/index.css +++ b/src/server/index.css @@ -1,50 +1,50 @@ :root { /* Background colors */ --bg-editor: #011627; - --bg-output: #40318D; - --bg-status-bar: #1E2A4A; - --bg-status-border: #0E1A3A; - --bg-selection: #1D3B53; - --bg-variable-def: #1E2A4A; + --bg-output: #40318d; + --bg-status-bar: #1e2a4a; + --bg-status-border: #0e1a3a; + --bg-selection: #1d3b53; + --bg-variable-def: #1e2a4a; /* Text colors */ - --text-editor: #D6DEEB; - --text-output: #7C70DA; - --text-status: #B3A9FF55; - --caret: #80A4C2; + --text-editor: #d6deeb; + --text-output: #7c70da; + --text-status: #b3a9ff55; + --caret: #80a4c2; /* Syntax highlighting colors */ - --color-keyword: #C792EA; - --color-function: #82AAFF; - --color-string: #C3E88D; - --color-number: #F78C6C; - --color-bool: #FF5370; - --color-operator: #89DDFF; - --color-paren: #676E95; - --color-function-call: #FF9CAC; - --color-variable-def: #FFCB6B; - --color-error: #FF6E6E; - --color-regex: #E1ACFF; + --color-keyword: #c792ea; + --color-function: #82aaff; + --color-string: #c3e88d; + --color-number: #f78c6c; + --color-bool: #ff5370; + --color-operator: #89ddff; + --color-paren: #676e95; + --color-function-call: #ff9cac; + --color-variable-def: #ffcb6b; + --color-error: #ff6e6e; + --color-regex: #e1acff; /* ANSI terminal colors */ --ansi-black: #011627; - --ansi-red: #FF5370; - --ansi-green: #C3E88D; - --ansi-yellow: #FFCB6B; - --ansi-blue: #82AAFF; - --ansi-magenta: #C792EA; - --ansi-cyan: #89DDFF; - --ansi-white: #D6DEEB; + --ansi-red: #ff5370; + --ansi-green: #c3e88d; + --ansi-yellow: #ffcb6b; + --ansi-blue: #82aaff; + --ansi-magenta: #c792ea; + --ansi-cyan: #89ddff; + --ansi-white: #d6deeb; /* ANSI bright colors (slightly more vibrant) */ - --ansi-bright-black: #676E95; - --ansi-bright-red: #FF6E90; - --ansi-bright-green: #D4F6A8; - --ansi-bright-yellow: #FFE082; - --ansi-bright-blue: #A8C7FA; - --ansi-bright-magenta: #E1ACFF; - --ansi-bright-cyan: #A8F5FF; - --ansi-bright-white: #FFFFFF; + --ansi-bright-black: #676e95; + --ansi-bright-red: #ff6e90; + --ansi-bright-green: #d4f6a8; + --ansi-bright-yellow: #ffe082; + --ansi-bright-blue: #a8c7fa; + --ansi-bright-magenta: #e1acff; + --ansi-bright-cyan: #a8f5ff; + --ansi-bright-white: #ffffff; } @font-face { @@ -81,4 +81,4 @@ body { background: var(--bg-output); display: flex; flex-direction: column; -} \ No newline at end of file +} diff --git a/src/server/index.html b/src/server/index.html index 2f4d226..eebe715 100644 --- a/src/server/index.html +++ b/src/server/index.html @@ -1,4 +1,4 @@ - + diff --git a/src/testSetup.ts b/src/testSetup.ts index c76471d..ec106d8 100644 --- a/src/testSetup.ts +++ b/src/testSetup.ts @@ -20,7 +20,7 @@ declare module 'bun:test' { toFailEvaluation(): Promise toBeToken(expected: string): T toMatchToken(typeOrValue: string, value?: string): T - toMatchTokens(...tokens: { type: string, value?: string }[]): T + toMatchTokens(...tokens: { type: string; value?: string }[]): T } } @@ -146,7 +146,7 @@ expect.extend({ return { message: () => `Expected token type to be ${expected}, but got ${TokenType[value.type]}`, - pass: value.type === target + pass: value.type === target, } } catch (error) { return { @@ -166,7 +166,8 @@ expect.extend({ if (!token) { return { - message: () => `Expected token to be ${expectedValue.replaceAll('\n', '\\n')}, got ${token}`, + message: () => + `Expected token to be ${expectedValue.replaceAll('\n', '\\n')}, got ${token}`, pass: false, } } @@ -174,13 +175,14 @@ expect.extend({ if (expectedType && TokenType[expectedType as keyof typeof TokenType] !== token.type) { return { message: () => `Expected token to be ${expectedType}, but got ${TokenType[token.type]}`, - pass: false + pass: false, } } return { - message: () => `Expected token to be ${expectedValue.replaceAll('\n', '\\n')}, but got ${token.value}`, - pass: token.value === expectedValue + message: () => + `Expected token to be ${expectedValue.replaceAll('\n', '\\n')}, but got ${token.value}`, + pass: token.value === expectedValue, } } catch (error) { return { @@ -189,11 +191,11 @@ expect.extend({ } } }, - toMatchTokens(received: unknown, ...tokens: { type: string, value?: string }[]) { + toMatchTokens(received: unknown, ...tokens: { type: string; value?: string }[]) { assert(typeof received === 'string', 'toMatchTokens can only be used with string values') try { - const result = tokenize(received).map(t => toHumanToken(t)) + const result = tokenize(received).map((t) => toHumanToken(t)) if (result.length === 0 && tokens.length > 0) { return { @@ -207,7 +209,7 @@ expect.extend({ return { message: () => `Tokens don't match: \n\n${diff(actual, expected)}`, - pass: expected == actual + pass: expected == actual, } } catch (error) { return { @@ -215,18 +217,18 @@ expect.extend({ pass: false, } } - } + }, }) const tokenize = (code: string): Token[] => { - const scanner = new Scanner + const scanner = new Scanner() return scanner.tokenize(code) } -const toHumanToken = (tok: Token): { type: string, value?: string } => { +const toHumanToken = (tok: Token): { type: string; value?: string } => { return { type: TokenType[tok.type], - value: tok.value + value: tok.value, } } @@ -241,7 +243,7 @@ const trimWhitespace = (str: string): string => { if (!line.startsWith(leadingWhitespace)) { let foundWhitespace = line.match(/^(\s*)/)?.[1] || '' throw new Error( - `Line has inconsistent leading whitespace: "${line}"(found "${foundWhitespace}", expected "${leadingWhitespace}")` + `Line has inconsistent leading whitespace: "${line}"(found "${foundWhitespace}", expected "${leadingWhitespace}")`, ) } return line.slice(leadingWhitespace.length) @@ -257,7 +259,7 @@ const diff = (a: string, b: string): string => { if (expected !== actual) { const changes = diffLines(actual, expected) for (const part of changes) { - const sign = part.added ? "+" : part.removed ? "-" : " " + const sign = part.added ? '+' : part.removed ? '-' : ' ' let line = sign + part.value if (part.added) { line = color.green(line) @@ -265,9 +267,9 @@ const diff = (a: string, b: string): string => { line = color.red(line) } - lines.push(line.endsWith("\n") || line.endsWith("\n\u001b[39m") ? line : line + "\n") + lines.push(line.endsWith('\n') || line.endsWith('\n\u001b[39m') ? line : line + '\n') } } return lines.join('\n') -} \ No newline at end of file +} diff --git a/vscode-extension/.vscode/launch.json b/vscode-extension/.vscode/launch.json index b3decc9..af25fad 100644 --- a/vscode-extension/.vscode/launch.json +++ b/vscode-extension/.vscode/launch.json @@ -5,10 +5,7 @@ "name": "Run Extension", "type": "extensionHost", "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}", - "--profile=Shrimp Dev" - ], + "args": ["--extensionDevelopmentPath=${workspaceFolder}", "--profile=Shrimp Dev"], "outFiles": [ "${workspaceFolder}/client/dist/**/*.js", "${workspaceFolder}/server/dist/**/*.js" diff --git a/vscode-extension/client/src/extension.ts b/vscode-extension/client/src/extension.ts index 70d7c6b..e9c71d2 100644 --- a/vscode-extension/client/src/extension.ts +++ b/vscode-extension/client/src/extension.ts @@ -22,7 +22,7 @@ export function activate(context: vscode.ExtensionContext) { 'shrimpLanguageServer', 'Shrimp Language Server', serverOptions, - clientOptions + clientOptions, ) client.start() @@ -46,7 +46,7 @@ export function activate(context: vscode.ExtensionContext) { language: 'text', }) await vscode.window.showTextDocument(doc, { preview: false }) - }) + }), ) // Command: Show Bytecode @@ -67,7 +67,7 @@ export function activate(context: vscode.ExtensionContext) { language: 'text', }) await vscode.window.showTextDocument(doc, { preview: false }) - }) + }), ) // Command: Run File @@ -93,7 +93,7 @@ export function activate(context: vscode.ExtensionContext) { const terminal = vscode.window.createTerminal('Shrimp') terminal.show() terminal.sendText(`${binaryPath} "${filePath}"`) - }) + }), ) } diff --git a/vscode-extension/package.json b/vscode-extension/package.json index 1422480..9bee33f 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -94,4 +94,4 @@ "vscode-languageserver": "^9.0.1", "vscode-languageserver-textdocument": "^1.0.12" } -} \ No newline at end of file +} diff --git a/vscode-extension/scripts/generate-prelude-metadata.ts b/vscode-extension/scripts/generate-prelude-metadata.ts index 594a725..79795d6 100644 --- a/vscode-extension/scripts/generate-prelude-metadata.ts +++ b/vscode-extension/scripts/generate-prelude-metadata.ts @@ -113,5 +113,5 @@ console.log(`✓ Generated ${names.length} prelude names to server/src/metadata/ console.log( `✓ Generated completions for ${ Object.keys(moduleMetadata).length - } modules to server/src/metadata/prelude-completions.ts` + } modules to server/src/metadata/prelude-completions.ts`, ) diff --git a/vscode-extension/server/src/completion/completionProvider.ts b/vscode-extension/server/src/completion/completionProvider.ts index 11d11cd..d0bce64 100644 --- a/vscode-extension/server/src/completion/completionProvider.ts +++ b/vscode-extension/server/src/completion/completionProvider.ts @@ -10,7 +10,7 @@ import { analyzeCompletionContext } from './contextAnalyzer' */ export const provideCompletions = ( document: TextDocument, - position: { line: number; character: number } + position: { line: number; character: number }, ): CompletionItem[] => { const context = analyzeCompletionContext(document, position) diff --git a/vscode-extension/server/src/completion/contextAnalyzer.ts b/vscode-extension/server/src/completion/contextAnalyzer.ts index 07d2aff..4a2b67e 100644 --- a/vscode-extension/server/src/completion/contextAnalyzer.ts +++ b/vscode-extension/server/src/completion/contextAnalyzer.ts @@ -14,7 +14,7 @@ export type CompletionContext = */ export const analyzeCompletionContext = ( document: TextDocument, - position: { line: number; character: number } + position: { line: number; character: number }, ): CompletionContext => { const offset = document.offsetAt(position) const text = document.getText() diff --git a/vscode-extension/server/src/metadata/prelude-completions.ts b/vscode-extension/server/src/metadata/prelude-completions.ts index 621bfeb..bc8eb80 100644 --- a/vscode-extension/server/src/metadata/prelude-completions.ts +++ b/vscode-extension/server/src/metadata/prelude-completions.ts @@ -8,789 +8,444 @@ export type CompletionMetadata = { export const completions = { modules: { - "date": { - "now": { - "params": [] + date: { + now: { + params: [], + }, + year: { + params: ['time'], + }, + month: { + params: ['time'], + }, + date: { + params: ['time'], + }, + hour: { + params: ['time'], + }, + minute: { + params: ['time'], + }, + second: { + params: ['time'], + }, + ms: { + params: ['time'], + }, + new: { + params: ['year', 'month', 'day', 'hour', 'minute', 'second', 'ms'], + }, }, - "year": { - "params": [ - "time" - ] + dict: { + keys: { + params: ['dict'], + }, + values: { + params: ['dict'], + }, + entries: { + params: ['dict'], + }, + 'has?': { + params: ['dict', 'key'], + }, + get: { + params: ['dict', 'key', 'defaultValue'], + }, + set: { + params: ['dict', 'key', 'value'], + }, + merge: { + params: ['...dicts'], + }, + 'empty?': { + params: ['dict'], + }, + map: { + params: ['dict', 'cb'], + }, + filter: { + params: ['dict', 'cb'], + }, + 'from-entries': { + params: ['entries'], + }, }, - "month": { - "params": [ - "time" - ] + fs: { + ls: { + params: ['path'], + }, + mkdir: { + params: ['path'], + }, + rmdir: { + params: ['path'], + }, + pwd: { + params: [], + }, + cd: { + params: ['path'], + }, + read: { + params: ['path'], + }, + cat: { + params: ['path'], + }, + 'read-bytes': { + params: ['path'], + }, + write: { + params: ['path', 'content'], + }, + append: { + params: ['path', 'content'], + }, + delete: { + params: ['path'], + }, + rm: { + params: ['path'], + }, + copy: { + params: ['from', 'to'], + }, + move: { + params: ['from', 'to'], + }, + mv: { + params: ['from', 'to'], + }, + basename: { + params: ['path'], + }, + dirname: { + params: ['path'], + }, + extname: { + params: ['path'], + }, + join: { + params: ['...paths'], + }, + resolve: { + params: ['...paths'], + }, + stat: { + params: ['path'], + }, + 'exists?': { + params: ['path'], + }, + 'file?': { + params: ['path'], + }, + 'dir?': { + params: ['path'], + }, + 'symlink?': { + params: ['path'], + }, + 'exec?': { + params: ['path'], + }, + size: { + params: ['path'], + }, + chmod: { + params: ['path', 'mode'], + }, + symlink: { + params: ['target', 'path'], + }, + readlink: { + params: ['path'], + }, + glob: { + params: ['pattern'], + }, + watch: { + params: ['path', 'callback'], + }, + cp: { + params: ['from', 'to'], + }, }, - "date": { - "params": [ - "time" - ] + json: { + encode: { + params: ['s'], + }, + decode: { + params: ['s'], + }, + parse: { + params: ['s'], + }, + stringify: { + params: ['s'], + }, }, - "hour": { - "params": [ - "time" - ] + list: { + slice: { + params: ['list', 'start', 'end'], + }, + map: { + params: ['list', 'cb'], + }, + filter: { + params: ['list', 'cb'], + }, + reject: { + params: ['list', 'cb'], + }, + reduce: { + params: ['list', 'cb', 'initial'], + }, + find: { + params: ['list', 'cb'], + }, + 'empty?': { + params: ['list'], + }, + 'contains?': { + params: ['list', 'item'], + }, + 'includes?': { + params: ['list', 'item'], + }, + 'has?': { + params: ['list', 'item'], + }, + 'any?': { + params: ['list', 'cb'], + }, + 'all?': { + params: ['list', 'cb'], + }, + push: { + params: ['list', 'item'], + }, + pop: { + params: ['list'], + }, + shift: { + params: ['list'], + }, + unshift: { + params: ['list', 'item'], + }, + splice: { + params: ['list', 'start', 'deleteCount', '...items'], + }, + insert: { + params: ['list', 'index', 'item'], + }, + reverse: { + params: ['list'], + }, + sort: { + params: ['list', 'cb'], + }, + concat: { + params: ['...lists'], + }, + flatten: { + params: ['list', 'depth'], + }, + unique: { + params: ['list'], + }, + zip: { + params: ['list1', 'list2'], + }, + first: { + params: ['list'], + }, + last: { + params: ['list'], + }, + rest: { + params: ['list'], + }, + take: { + params: ['list', 'n'], + }, + drop: { + params: ['list', 'n'], + }, + append: { + params: ['list', 'item'], + }, + prepend: { + params: ['list', 'item'], + }, + 'index-of': { + params: ['list', 'item'], + }, + sum: { + params: ['list'], + }, + count: { + params: ['list', 'cb'], + }, + partition: { + params: ['list', 'cb'], + }, + compact: { + params: ['list'], + }, + 'group-by': { + params: ['list', 'cb'], + }, }, - "minute": { - "params": [ - "time" - ] + math: { + abs: { + params: ['n'], + }, + floor: { + params: ['n'], + }, + ceil: { + params: ['n'], + }, + round: { + params: ['n'], + }, + min: { + params: ['...nums'], + }, + max: { + params: ['...nums'], + }, + pow: { + params: ['base', 'exp'], + }, + sqrt: { + params: ['n'], + }, + random: { + params: ['min', 'max'], + }, + clamp: { + params: ['n', 'min', 'max'], + }, + sign: { + params: ['n'], + }, + trunc: { + params: ['n'], + }, + 'even?': { + params: ['n'], + }, + 'odd?': { + params: ['n'], + }, + 'positive?': { + params: ['n'], + }, + 'negative?': { + params: ['n'], + }, + 'zero?': { + params: ['n'], + }, }, - "second": { - "params": [ - "time" - ] + str: { + join: { + params: ['arr', 'sep'], + }, + split: { + params: ['str', 'sep'], + }, + 'to-upper': { + params: ['str'], + }, + 'to-lower': { + params: ['str'], + }, + trim: { + params: ['str'], + }, + 'starts-with?': { + params: ['str', 'prefix'], + }, + 'ends-with?': { + params: ['str', 'suffix'], + }, + 'contains?': { + params: ['str', 'substr'], + }, + 'empty?': { + params: ['str'], + }, + 'index-of': { + params: ['str', 'search'], + }, + 'last-index-of': { + params: ['str', 'search'], + }, + replace: { + params: ['str', 'search', 'replacement'], + }, + 'replace-all': { + params: ['str', 'search', 'replacement'], + }, + slice: { + params: ['str', 'start', 'end'], + }, + substring: { + params: ['str', 'start', 'end'], + }, + repeat: { + params: ['str', 'count'], + }, + 'pad-start': { + params: ['str', 'length', 'pad'], + }, + 'pad-end': { + params: ['str', 'length', 'pad'], + }, + capitalize: { + params: ['str'], + }, + titlecase: { + params: ['s'], + }, + lines: { + params: ['str'], + }, + chars: { + params: ['str'], + }, + match: { + params: ['str', 'regex'], + }, + 'test?': { + params: ['str', 'regex'], + }, }, - "ms": { - "params": [ - "time" - ] - }, - "new": { - "params": [ - "year", - "month", - "day", - "hour", - "minute", - "second", - "ms" - ] - } }, - "dict": { - "keys": { - "params": [ - "dict" - ] - }, - "values": { - "params": [ - "dict" - ] - }, - "entries": { - "params": [ - "dict" - ] - }, - "has?": { - "params": [ - "dict", - "key" - ] - }, - "get": { - "params": [ - "dict", - "key", - "defaultValue" - ] - }, - "set": { - "params": [ - "dict", - "key", - "value" - ] - }, - "merge": { - "params": [ - "...dicts" - ] - }, - "empty?": { - "params": [ - "dict" - ] - }, - "map": { - "params": [ - "dict", - "cb" - ] - }, - "filter": { - "params": [ - "dict", - "cb" - ] - }, - "from-entries": { - "params": [ - "entries" - ] - } - }, - "fs": { - "ls": { - "params": [ - "path" - ] - }, - "mkdir": { - "params": [ - "path" - ] - }, - "rmdir": { - "params": [ - "path" - ] - }, - "pwd": { - "params": [] - }, - "cd": { - "params": [ - "path" - ] - }, - "read": { - "params": [ - "path" - ] - }, - "cat": { - "params": [ - "path" - ] - }, - "read-bytes": { - "params": [ - "path" - ] - }, - "write": { - "params": [ - "path", - "content" - ] - }, - "append": { - "params": [ - "path", - "content" - ] - }, - "delete": { - "params": [ - "path" - ] - }, - "rm": { - "params": [ - "path" - ] - }, - "copy": { - "params": [ - "from", - "to" - ] - }, - "move": { - "params": [ - "from", - "to" - ] - }, - "mv": { - "params": [ - "from", - "to" - ] - }, - "basename": { - "params": [ - "path" - ] - }, - "dirname": { - "params": [ - "path" - ] - }, - "extname": { - "params": [ - "path" - ] - }, - "join": { - "params": [ - "...paths" - ] - }, - "resolve": { - "params": [ - "...paths" - ] - }, - "stat": { - "params": [ - "path" - ] - }, - "exists?": { - "params": [ - "path" - ] - }, - "file?": { - "params": [ - "path" - ] - }, - "dir?": { - "params": [ - "path" - ] - }, - "symlink?": { - "params": [ - "path" - ] - }, - "exec?": { - "params": [ - "path" - ] - }, - "size": { - "params": [ - "path" - ] - }, - "chmod": { - "params": [ - "path", - "mode" - ] - }, - "symlink": { - "params": [ - "target", - "path" - ] - }, - "readlink": { - "params": [ - "path" - ] - }, - "glob": { - "params": [ - "pattern" - ] - }, - "watch": { - "params": [ - "path", - "callback" - ] - }, - "cp": { - "params": [ - "from", - "to" - ] - } - }, - "json": { - "encode": { - "params": [ - "s" - ] - }, - "decode": { - "params": [ - "s" - ] - }, - "parse": { - "params": [ - "s" - ] - }, - "stringify": { - "params": [ - "s" - ] - } - }, - "list": { - "slice": { - "params": [ - "list", - "start", - "end" - ] - }, - "map": { - "params": [ - "list", - "cb" - ] - }, - "filter": { - "params": [ - "list", - "cb" - ] - }, - "reject": { - "params": [ - "list", - "cb" - ] - }, - "reduce": { - "params": [ - "list", - "cb", - "initial" - ] - }, - "find": { - "params": [ - "list", - "cb" - ] - }, - "empty?": { - "params": [ - "list" - ] - }, - "contains?": { - "params": [ - "list", - "item" - ] - }, - "includes?": { - "params": [ - "list", - "item" - ] - }, - "has?": { - "params": [ - "list", - "item" - ] - }, - "any?": { - "params": [ - "list", - "cb" - ] - }, - "all?": { - "params": [ - "list", - "cb" - ] - }, - "push": { - "params": [ - "list", - "item" - ] - }, - "pop": { - "params": [ - "list" - ] - }, - "shift": { - "params": [ - "list" - ] - }, - "unshift": { - "params": [ - "list", - "item" - ] - }, - "splice": { - "params": [ - "list", - "start", - "deleteCount", - "...items" - ] - }, - "insert": { - "params": [ - "list", - "index", - "item" - ] - }, - "reverse": { - "params": [ - "list" - ] - }, - "sort": { - "params": [ - "list", - "cb" - ] - }, - "concat": { - "params": [ - "...lists" - ] - }, - "flatten": { - "params": [ - "list", - "depth" - ] - }, - "unique": { - "params": [ - "list" - ] - }, - "zip": { - "params": [ - "list1", - "list2" - ] - }, - "first": { - "params": [ - "list" - ] - }, - "last": { - "params": [ - "list" - ] - }, - "rest": { - "params": [ - "list" - ] - }, - "take": { - "params": [ - "list", - "n" - ] - }, - "drop": { - "params": [ - "list", - "n" - ] - }, - "append": { - "params": [ - "list", - "item" - ] - }, - "prepend": { - "params": [ - "list", - "item" - ] - }, - "index-of": { - "params": [ - "list", - "item" - ] - }, - "sum": { - "params": [ - "list" - ] - }, - "count": { - "params": [ - "list", - "cb" - ] - }, - "partition": { - "params": [ - "list", - "cb" - ] - }, - "compact": { - "params": [ - "list" - ] - }, - "group-by": { - "params": [ - "list", - "cb" - ] - } - }, - "math": { - "abs": { - "params": [ - "n" - ] - }, - "floor": { - "params": [ - "n" - ] - }, - "ceil": { - "params": [ - "n" - ] - }, - "round": { - "params": [ - "n" - ] - }, - "min": { - "params": [ - "...nums" - ] - }, - "max": { - "params": [ - "...nums" - ] - }, - "pow": { - "params": [ - "base", - "exp" - ] - }, - "sqrt": { - "params": [ - "n" - ] - }, - "random": { - "params": [ - "min", - "max" - ] - }, - "clamp": { - "params": [ - "n", - "min", - "max" - ] - }, - "sign": { - "params": [ - "n" - ] - }, - "trunc": { - "params": [ - "n" - ] - }, - "even?": { - "params": [ - "n" - ] - }, - "odd?": { - "params": [ - "n" - ] - }, - "positive?": { - "params": [ - "n" - ] - }, - "negative?": { - "params": [ - "n" - ] - }, - "zero?": { - "params": [ - "n" - ] - } - }, - "str": { - "join": { - "params": [ - "arr", - "sep" - ] - }, - "split": { - "params": [ - "str", - "sep" - ] - }, - "to-upper": { - "params": [ - "str" - ] - }, - "to-lower": { - "params": [ - "str" - ] - }, - "trim": { - "params": [ - "str" - ] - }, - "starts-with?": { - "params": [ - "str", - "prefix" - ] - }, - "ends-with?": { - "params": [ - "str", - "suffix" - ] - }, - "contains?": { - "params": [ - "str", - "substr" - ] - }, - "empty?": { - "params": [ - "str" - ] - }, - "index-of": { - "params": [ - "str", - "search" - ] - }, - "last-index-of": { - "params": [ - "str", - "search" - ] - }, - "replace": { - "params": [ - "str", - "search", - "replacement" - ] - }, - "replace-all": { - "params": [ - "str", - "search", - "replacement" - ] - }, - "slice": { - "params": [ - "str", - "start", - "end" - ] - }, - "substring": { - "params": [ - "str", - "start", - "end" - ] - }, - "repeat": { - "params": [ - "str", - "count" - ] - }, - "pad-start": { - "params": [ - "str", - "length", - "pad" - ] - }, - "pad-end": { - "params": [ - "str", - "length", - "pad" - ] - }, - "capitalize": { - "params": [ - "str" - ] - }, - "titlecase": { - "params": [ - "s" - ] - }, - "lines": { - "params": [ - "str" - ] - }, - "chars": { - "params": [ - "str" - ] - }, - "match": { - "params": [ - "str", - "regex" - ] - }, - "test?": { - "params": [ - "str", - "regex" - ] - } - } -}, dollar: { - "args": { - "params": [] + args: { + params: [], + }, + argv: { + params: [], + }, + env: { + params: [], + }, + pid: { + params: [], + }, + cwd: { + params: [], + }, + script: { + params: [], + }, }, - "argv": { - "params": [] - }, - "env": { - "params": [] - }, - "pid": { - "params": [] - }, - "cwd": { - "params": [] - }, - "script": { - "params": [] - } -}, } as const diff --git a/vscode-extension/server/src/metadata/prelude-names.ts b/vscode-extension/server/src/metadata/prelude-names.ts index 5ca2f3e..3bd9436 100644 --- a/vscode-extension/server/src/metadata/prelude-names.ts +++ b/vscode-extension/server/src/metadata/prelude-names.ts @@ -2,44 +2,44 @@ // Do not edit manually - run 'bun run generate-prelude-metadata' to regenerate export const PRELUDE_NAMES = [ - "$", - "array?", - "at", - "bnot", - "boolean", - "boolean?", - "date", - "dec", - "describe", - "dict", - "dict?", - "each", - "echo", - "empty?", - "exit", - "fs", - "function?", - "identity", - "import", - "inc", - "inspect", - "json", - "length", - "list", - "list?", - "load", - "math", - "not", - "null?", - "number", - "number?", - "range", - "ref", - "some?", - "str", - "string", - "string?", - "type", - "var", - "var?" + '$', + 'array?', + 'at', + 'bnot', + 'boolean', + 'boolean?', + 'date', + 'dec', + 'describe', + 'dict', + 'dict?', + 'each', + 'echo', + 'empty?', + 'exit', + 'fs', + 'function?', + 'identity', + 'import', + 'inc', + 'inspect', + 'json', + 'length', + 'list', + 'list?', + 'load', + 'math', + 'not', + 'null?', + 'number', + 'number?', + 'range', + 'ref', + 'some?', + 'str', + 'string', + 'string?', + 'type', + 'var', + 'var?', ] as const diff --git a/vscode-extension/server/src/semanticTokens.ts b/vscode-extension/server/src/semanticTokens.ts index 3cd8079..b8792d5 100644 --- a/vscode-extension/server/src/semanticTokens.ts +++ b/vscode-extension/server/src/semanticTokens.ts @@ -41,14 +41,14 @@ export function buildSemanticTokens(document: TextDocument, tree: Tree): number[ function emitNamedArgPrefix( node: SyntaxNode, document: TextDocument, - builder: SemanticTokensBuilder + builder: SemanticTokensBuilder, ) { const text = document.getText({ start: document.positionAt(node.from), end: document.positionAt(node.to), }) - const nameLength = text.length - 1 // Everything except the = + const nameLength = text.length - 1 // Everything except the = const start = document.positionAt(node.from) // Emit token for the name part (e.g., "color") @@ -57,16 +57,16 @@ function emitNamedArgPrefix( start.character, nameLength, TOKEN_TYPES.indexOf(SemanticTokenTypes.property), - 0 + 0, ) // Emit token for the "=" part builder.push( start.line, start.character + nameLength, - 1, // Just the = character + 1, // Just the = character TOKEN_TYPES.indexOf(SemanticTokenTypes.operator), - 0 + 0, ) } @@ -75,7 +75,7 @@ function walkTree( node: SyntaxNode, document: TextDocument, builder: SemanticTokensBuilder, - scopeTracker: EditorScopeAnalyzer + scopeTracker: EditorScopeAnalyzer, ) { // Special handling for NamedArgPrefix to split "name=" into two tokens if (node.type.id === Terms.NamedArgPrefix) { @@ -102,7 +102,7 @@ type TokenInfo = { type: number; modifiers: number } | undefined function getTokenType( node: SyntaxNode, document: TextDocument, - scopeTracker: EditorScopeAnalyzer + scopeTracker: EditorScopeAnalyzer, ): TokenInfo { const nodeTypeId = node.type.id const parentTypeId = node.parent?.type.id diff --git a/vscode-extension/server/src/server.ts b/vscode-extension/server/src/server.ts index 6e0bc5c..ad6e92c 100644 --- a/vscode-extension/server/src/server.ts +++ b/vscode-extension/server/src/server.ts @@ -124,7 +124,7 @@ function handleCompletion(params: any) { if (contextCompletions.length > 0) { console.log( `✅ Returning ${contextCompletions.length} completions:`, - contextCompletions.map((c) => c.label).join(', ') + contextCompletions.map((c) => c.label).join(', '), ) return contextCompletions } diff --git a/vscode-extension/server/src/signatureHelp.ts b/vscode-extension/server/src/signatureHelp.ts index b356397..253face 100644 --- a/vscode-extension/server/src/signatureHelp.ts +++ b/vscode-extension/server/src/signatureHelp.ts @@ -1,4 +1,8 @@ -import { SignatureHelp, SignatureInformation, ParameterInformation } from 'vscode-languageserver/node' +import { + SignatureHelp, + SignatureInformation, + ParameterInformation, +} from 'vscode-languageserver/node' import { TextDocument } from 'vscode-languageserver-textdocument' import { Tree, SyntaxNode } from '@lezer/common' import { parser } from '../../../src/parser/shrimp' @@ -6,7 +10,7 @@ import { completions } from './metadata/prelude-completions' export const provideSignatureHelp = ( document: TextDocument, - position: { line: number; character: number } + position: { line: number; character: number }, ): SignatureHelp | undefined => { const text = document.getText() const tree = parser.parse(text) @@ -100,6 +104,6 @@ const lookupFunctionParams = (funcName: string): string[] | undefined => { const buildSignature = (funcName: string, params: string[]): SignatureInformation => { const label = `${funcName}(${params.join(', ')})` - const parameters: ParameterInformation[] = params.map(p => ({ label: p })) + const parameters: ParameterInformation[] = params.map((p) => ({ label: p })) return { label, parameters } }