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}${tagName}>`)
+ if (SELF_CLOSING.includes(tagName)) buffer.push(`<${tagName}${space}${attrs.join(' ')} />`)
+ else buffer.push(`<${tagName}${space}${attrs.join(' ')}>${children}${tagName}>`)
}
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(
+ ``, 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 }
}