From 787d2f26110a7a408695a3b23d11ac8026185df6 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Sat, 25 Oct 2025 20:41:13 -0700 Subject: [PATCH] start on a prelude of builtin functions --- bin/repl | 93 ++--------------------------------------- bin/shrimp | 45 ++------------------ src/prelude.ts | 110 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 131 deletions(-) create mode 100644 src/prelude.ts diff --git a/bin/repl b/bin/repl index b614e26..8855e2d 100755 --- a/bin/repl +++ b/bin/repl @@ -1,24 +1,12 @@ #!/usr/bin/env bun import { Compiler } from '../src/compiler/compiler' -import { VM, type Value, Scope, bytecodeToString } from 'reefvm' +import { colors, formatValue, nativeFunctions, valueFunctions } from '../src/prelude' +import { VM, Scope, bytecodeToString } from 'reefvm' import * as readline from 'readline' import { readFileSync, writeFileSync } from 'fs' import { basename } from 'path' -const colors = { - reset: '\x1b[0m', - bright: '\x1b[1m', - dim: '\x1b[2m', - cyan: '\x1b[36m', - yellow: '\x1b[33m', - green: '\x1b[32m', - red: '\x1b[31m', - blue: '\x1b[34m', - magenta: '\x1b[35m', - pink: '\x1b[38;2;255;105;180m' -} - async function repl() { const commands = ['/clear', '/reset', '/vars', '/funcs', '/history', '/bytecode', '/exit', '/save', '/quit'] @@ -60,7 +48,7 @@ async function repl() { return } - vm ||= new VM({ instructions: [], constants: [] }, nativeFunctions) + vm ||= new VM({ instructions: [], constants: [] }, nativeFunctions, valueFunctions) if (['/exit', 'exit', '/quit', 'quit'].includes(trimmed)) { console.log(`\n${colors.yellow}Goodbye!${colors.reset}`) @@ -186,40 +174,6 @@ async function repl() { }) } - -function formatValue(value: Value, inner = false): string { - switch (value.type) { - case 'string': - return `${colors.green}'${value.value}'${colors.reset}` - case 'number': - return `${colors.cyan}${value.value}${colors.reset}` - case 'boolean': - return `${colors.yellow}${value.value}${colors.reset}` - case 'null': - return `${colors.dim}null${colors.reset}` - case 'array': { - const items = value.value.map(x => formatValue(x, true)).join(' ') - return `${inner ? '(' : ''}${colors.blue}list${colors.reset} ${items}${inner ? ')' : ''}` - } - case 'dict': { - const entries = Array.from(value.value.entries()) - .map(([k, v]) => `${k}=${formatValue(v, true)}`) - .join(' ') - return `${inner ? '(' : ''}${colors.magenta}dict${colors.reset} ${entries}${inner ? ')' : ''}` - } - case 'function': { - const params = value.params.join(', ') - return `${colors.dim}${colors.reset}` - } - case 'native': - return `${colors.dim}${colors.reset}` - case 'regex': - return `${colors.magenta}${value.value}${colors.reset}` - default: - return String(value) - } -} - function formatVariables(scope: Scope, onlyFunctions = false): string { const vars: string[] = [] @@ -257,7 +211,7 @@ async function loadFile(filePath: string): Promise<{ vm: VM; codeHistory: string console.log(`${colors.dim}Loading ${basename(filePath)}...${colors.reset}`) - const vm = new VM({ instructions: [], constants: [] }, nativeFunctions) + const vm = new VM({ instructions: [], constants: [] }, nativeFunctions, valueFunctions) await vm.run() const codeHistory: string[] = [] @@ -313,43 +267,4 @@ function showWelcome() { console.log() } -const nativeFunctions = { - echo: (...args: any[]) => { - console.log(...args) - }, - len: (value: any) => { - if (typeof value === 'string') return value.length - if (Array.isArray(value)) return value.length - if (value && typeof value === 'object') return Object.keys(value).length - return 0 - }, - type: (value: any) => { - if (value === null) return 'null' - if (Array.isArray(value)) return 'array' - return typeof value - }, - range: (start: number, end: number | null) => { - if (end === null) { - end = start - start = 0 - } - const result: number[] = [] - for (let i = start; i <= end; i++) { - result.push(i) - } - return result - }, - join: (arr: any[], sep: string = ',') => { - return arr.join(sep) - }, - split: (str: string, sep: string = ',') => { - return str.split(sep) - }, - upper: (str: string) => str.toUpperCase(), - lower: (str: string) => str.toLowerCase(), - trim: (str: string) => str.trim(), - list: (...args: any[]) => args, - dict: (atNamed = {}) => atNamed -} - await repl() diff --git a/bin/shrimp b/bin/shrimp index eed1d48..0622700 100755 --- a/bin/shrimp +++ b/bin/shrimp @@ -1,57 +1,18 @@ #!/usr/bin/env bun import { Compiler } from '../src/compiler/compiler' -import { VM, toValue, fromValue, bytecodeToString } from 'reefvm' +import { colors, nativeFunctions, valueFunctions } from '../src/prelude' +import { VM, fromValue, bytecodeToString } from 'reefvm' import { readFileSync, writeFileSync, mkdirSync } from 'fs' import { randomUUID } from "crypto" import { spawn } from 'child_process' import { join } from 'path' -const colors = { - reset: '\x1b[0m', - bright: '\x1b[1m', - dim: '\x1b[2m', - red: '\x1b[31m', - yellow: '\x1b[33m', - cyan: '\x1b[36m', - magenta: '\x1b[35m', - pink: '\x1b[38;2;255;105;180m' -} - -const nativeFunctions = { - echo: (...args: any[]) => console.log(...args), - len: (value: any) => { - if (typeof value === 'string') return value.length - if (Array.isArray(value)) return value.length - if (value && typeof value === 'object') return Object.keys(value).length - return 0 - }, - type: (value: any) => toValue(value).type, - range: (start: number, end: number | null) => { - if (end === null) { - end = start - start = 0 - } - const result: number[] = [] - for (let i = start; i <= end; i++) { - result.push(i) - } - return result - }, - join: (arr: any[], sep: string = ',') => arr.join(sep), - split: (str: string, sep: string = ',') => str.split(sep), - upper: (str: string) => str.toUpperCase(), - lower: (str: string) => str.toLowerCase(), - trim: (str: string) => str.trim(), - list: (...args: any[]) => args, - dict: (atNamed = {}) => atNamed -} - async function runFile(filePath: string) { try { const code = readFileSync(filePath, 'utf-8') const compiler = new Compiler(code) - const vm = new VM(compiler.bytecode, nativeFunctions) + const vm = new VM(compiler.bytecode, nativeFunctions, valueFunctions) await vm.run() return vm.stack.length ? fromValue(vm.stack[vm.stack.length - 1]) : null } catch (error: any) { diff --git a/src/prelude.ts b/src/prelude.ts new file mode 100644 index 0000000..77dc987 --- /dev/null +++ b/src/prelude.ts @@ -0,0 +1,110 @@ +// The prelude creates all the builtin Shrimp functions. + +import { toValue, type Value, extractParamInfo, isWrapped, getOriginalFunction } from 'reefvm' + +export const colors = { + reset: '\x1b[0m', + bright: '\x1b[1m', + dim: '\x1b[2m', + cyan: '\x1b[36m', + yellow: '\x1b[33m', + green: '\x1b[32m', + red: '\x1b[31m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + pink: '\x1b[38;2;255;105;180m' +} + +export const valueFunctions = { + echo: (...args: Value[]) => { + console.log(...args.map(a => formatValue(a))) + return toValue(null) + }, + + length: (value: Value) => { + switch (value.type) { + case 'string': case 'array': + return toValue(value.value.length) + case 'dict': + return toValue(Object.keys(value.value).length) + default: + return toValue(0) + } + }, + + type: (value: Value) => toValue(value.type), + inspect: (value: Value) => toValue(formatValue(value)) +} + +export const nativeFunctions = { + range: (start: number, end: number | null) => { + if (end === null) { + end = start + start = 0 + } + const result: number[] = [] + for (let i = start; i <= end; i++) { + result.push(i) + } + return result + }, + + // strings + join: (arr: any[], sep: string = ',') => arr.join(sep), + split: (str: string, sep: string = ',') => str.split(sep), + 'to-upper': (str: string) => str.toUpperCase(), + 'to-lower': (str: string) => str.toLowerCase(), + trim: (str: string) => str.trim(), + + // collections + at: (collection: any, index: number | string) => collection[index], + list: (...args: any[]) => args, + dict: (atNamed = {}) => atNamed, + slice: (list: any[], start: number, end?: number) => list.slice(start, end), + + // enumerables + map: async (list: any[], cb: Function) => { + let acc: any[] = [] + for (const value of list) acc.push(await cb(value)) + return acc + }, + each: async (list: any[], cb: Function) => { + for (const value of list) await cb(value) + }, +} + +export function formatValue(value: Value, inner = false): string { + switch (value.type) { + case 'string': + return `${colors.green}'${value.value.replaceAll("'", "\\'")}${colors.green}'${colors.reset}` + case 'number': + return `${colors.cyan}${value.value}${colors.reset}` + case 'boolean': + return `${colors.yellow}${value.value}${colors.reset}` + case 'null': + return `${colors.dim}null${colors.reset}` + case 'array': { + const items = value.value.map(x => formatValue(x, true)).join(' ') + return `${inner ? '(' : ''}${colors.blue}list${colors.reset} ${items}${inner ? ')' : ''}` + } + case 'dict': { + const entries = Array.from(value.value.entries()) + .map(([k, v]) => `${k}=${formatValue(v, true)}`) + .join(' ') + return `${inner ? '(' : ''}${colors.magenta}dict${colors.reset} ${entries}${inner ? ')' : ''}` + } + case 'function': { + const params = value.params.length ? '(' + value.params.join(' ') + ')' : '' + return `${colors.dim}${colors.reset}` + } + case 'native': + const fn = isWrapped(value.fn) ? getOriginalFunction(value.fn) : value.fn + const info = extractParamInfo(fn) + const params = info.params.length ? '(' + info.params.join(' ') + ')' : '' + return `${colors.dim}${colors.reset}` + case 'regex': + return `${colors.magenta}${value.value}${colors.reset}` + default: + return String(value) + } +} \ No newline at end of file