shrimp/src/prelude/index.ts
2025-10-28 13:10:25 -07:00

117 lines
3.6 KiB
TypeScript

// The prelude creates all the builtin Shrimp functions.
import { readFileSync } from 'fs'
import { VM, isValue, 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 nativeFunctions = {
// 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: 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),
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: function (this: VM, path: string) {
const file = readFileSync(path + '.sh')
}
}
export function formatValue(value: Value | any, inner = false): string {
if (!isValue(value)) value = toValue(value)
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}<function${params}>${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}<native${params}>${colors.reset}`
case 'regex':
return `${colors.magenta}${value.value}${colors.reset}`
default:
return String(value)
}
}