// The prelude creates all the builtin Shrimp functions. import { resolve, parse } from 'path' import { readFileSync } from 'fs' import { Compiler } from '#compiler/compiler' import { VM, Scope, 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 globalFunctions = { // hello echo: (...args: any[]) => { console.log(...args.map(a => { const v = toValue(a) return ['array', 'dict'].includes(v.type) ? formatValue(v, true) : v.value })) return toValue(null) }, // info type: (v: any) => toValue(v).type, inspect: (v: any) => formatValue(toValue(v)), 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: return 0 } }, // strings join: (arr: string[], 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), 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 }, // 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) }, // modules use: async function (this: VM, path: string) { const scope = this.scope const pc = this.pc const fullPath = resolve(path) + '.sh' const code = readFileSync(fullPath, 'utf-8') this.pc = this.instructions.length this.scope = new Scope(scope) const compiled = new Compiler(code) this.appendBytecode(compiled.bytecode) await this.continue() const module: Map = new Map for (const [name, value] of this.scope.locals.entries()) module.set(name, value) this.scope = scope this.pc = pc this.stopped = false this.scope.set(parse(fullPath).name, { type: 'dict', value: module }) } } 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) } }