141 lines
4.2 KiB
TypeScript
141 lines
4.2 KiB
TypeScript
// 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<string, Value> = 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}<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)
|
|
}
|
|
} |