split up prelude modules

This commit is contained in:
Chris Wanstrath 2025-10-29 12:13:56 -07:00
parent 9e38fa7a44
commit 4fb58483f0
6 changed files with 209 additions and 192 deletions

24
src/prelude/dict.ts Normal file
View File

@ -0,0 +1,24 @@
export const dict = {
keys: (dict: Record<string, any>) => Object.keys(dict),
values: (dict: Record<string, any>) => Object.values(dict),
entries: (dict: Record<string, any>) => Object.entries(dict).map(([k, v]) => ({ key: k, value: v })),
'has?': (dict: Record<string, any>, key: string) => key in dict,
get: (dict: Record<string, any>, key: string, defaultValue: any = null) => dict[key] ?? defaultValue,
merge: (...dicts: Record<string, any>[]) => Object.assign({}, ...dicts),
'empty?': (dict: Record<string, any>) => Object.keys(dict).length === 0,
map: async (dict: Record<string, any>, cb: Function) => {
const result: Record<string, any> = {}
for (const [key, value] of Object.entries(dict)) {
result[key] = await cb(value, key)
}
return result
},
filter: async (dict: Record<string, any>, cb: Function) => {
const result: Record<string, any> = {}
for (const [key, value] of Object.entries(dict)) {
if (await cb(value, key)) result[key] = value
}
return result
},
'from-entries': (entries: [string, any][]) => Object.fromEntries(entries),
}

View File

@ -1,13 +1,16 @@
// 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,
type Value, toValue,
extractParamInfo, isWrapped, getOriginalFunction,
} from 'reefvm'
import { dict } from './dict'
import { load } from './load'
import { list } from './list'
import { math } from './math'
import { str } from './str'
export const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
@ -22,6 +25,12 @@ export const colors = {
}
export const globals = {
dict,
load,
list,
math,
str,
// hello
echo: (...args: any[]) => {
console.log(...args.map(a => {
@ -64,121 +73,6 @@ export const globals = {
dec: (n: number) => n - 1,
identity: (v: any) => v,
// strings
str: {
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(),
// predicates
'starts-with?': (str: string, prefix: string) => str.startsWith(prefix),
'ends-with?': (str: string, suffix: string) => str.endsWith(suffix),
'contains?': (str: string, substr: string) => str.includes(substr),
'empty?': (str: string) => str.length === 0,
// transformations
replace: (str: string, search: string, replacement: string) => str.replace(search, replacement),
'replace-all': (str: string, search: string, replacement: string) => str.replaceAll(search, replacement),
slice: (str: string, start: number, end?: number | null) => str.slice(start, end ?? undefined),
substring: (str: string, start: number, end?: number | null) => str.substring(start, end ?? undefined),
repeat: (str: string, count: number) => str.repeat(count),
'pad-start': (str: string, length: number, pad: string = ' ') => str.padStart(length, pad),
'pad-end': (str: string, length: number, pad: string = ' ') => str.padEnd(length, pad),
lines: (str: string) => str.split('\n'),
chars: (str: string) => str.split(''),
'index-of': (str: string, search: string) => str.indexOf(search),
'last-index-of': (str: string, search: string) => str.lastIndexOf(search),
match: (str: string, regex: RegExp) => str.match(regex),
'test?': (str: string, regex: RegExp) => regex.test(str),
},
// list
list: {
slice: (list: any[], start: number, end?: number) => list.slice(start, end),
map: async (list: any[], cb: Function) => {
let acc: any[] = []
for (const value of list) acc.push(await cb(value))
return acc
},
filter: async (list: any[], cb: Function) => {
let acc: any[] = []
for (const value of list) {
if (await cb(value)) acc.push(value)
}
return acc
},
reduce: async (list: any[], cb: Function, initial: any) => {
let acc = initial
for (const value of list) acc = await cb(acc, value)
return acc
},
find: async (list: any[], cb: Function) => {
for (const value of list) {
if (await cb(value)) return value
}
return null
},
// predicates
'empty?': (list: any[]) => list.length === 0,
'contains?': (list: any[], item: any) => list.includes(item),
'any?': async (list: any[], cb: Function) => {
for (const value of list) {
if (await cb(value)) return true
}
return false
},
'all?': async (list: any[], cb: Function) => {
for (const value of list) {
if (!await cb(value)) return false
}
return true
},
// sequence operations
reverse: (list: any[]) => list.slice().reverse(),
sort: (list: any[], cb?: (a: any, b: any) => number) => list.slice().sort(cb),
concat: (...lists: any[][]) => lists.flat(1),
flatten: (list: any[], depth: number = 1) => list.flat(depth),
unique: (list: any[]) => Array.from(new Set(list)),
zip: (list1: any[], list2: any[]) => list1.map((item, i) => [item, list2[i]]),
// access
first: (list: any[]) => list[0] ?? null,
last: (list: any[]) => list[list.length - 1] ?? null,
rest: (list: any[]) => list.slice(1),
take: (list: any[], n: number) => list.slice(0, n),
drop: (list: any[], n: number) => list.slice(n),
append: (list: any[], item: any) => [...list, item],
prepend: (list: any[], item: any) => [item, ...list],
'index-of': (list: any[], item: any) => list.indexOf(item),
// utilities
sum: (list: any[]) => list.reduce((acc, x) => acc + x, 0),
count: async (list: any[], cb: Function) => {
let count = 0
for (const value of list) {
if (await cb(value)) count++
}
return count
},
partition: async (list: any[], cb: Function) => {
const truthy: any[] = []
const falsy: any[] = []
for (const value of list) {
if (await cb(value)) truthy.push(value)
else falsy.push(value)
}
return [truthy, falsy]
},
compact: (list: any[]) => list.filter(x => x != null),
'group-by': async (list: any[], cb: Function) => {
const groups: Record<string, any[]> = {}
for (const value of list) {
const key = String(await cb(value))
if (!groups[key]) groups[key] = []
groups[key].push(value)
}
return groups
},
},
// collections
at: (collection: any, index: number | string) => collection[index],
range: (start: number, end: number | null) => {
@ -204,85 +98,12 @@ export const globals = {
}
},
// dict
dict: {
keys: (dict: Record<string, any>) => Object.keys(dict),
values: (dict: Record<string, any>) => Object.values(dict),
entries: (dict: Record<string, any>) => Object.entries(dict).map(([k, v]) => ({ key: k, value: v })),
'has?': (dict: Record<string, any>, key: string) => key in dict,
get: (dict: Record<string, any>, key: string, defaultValue: any = null) => dict[key] ?? defaultValue,
merge: (...dicts: Record<string, any>[]) => Object.assign({}, ...dicts),
'empty?': (dict: Record<string, any>) => Object.keys(dict).length === 0,
map: async (dict: Record<string, any>, cb: Function) => {
const result: Record<string, any> = {}
for (const [key, value] of Object.entries(dict)) {
result[key] = await cb(value, key)
}
return result
},
filter: async (dict: Record<string, any>, cb: Function) => {
const result: Record<string, any> = {}
for (const [key, value] of Object.entries(dict)) {
if (await cb(value, key)) result[key] = value
}
return result
},
'from-entries': (entries: [string, any][]) => Object.fromEntries(entries),
},
// math
math: {
abs: (n: number) => Math.abs(n),
floor: (n: number) => Math.floor(n),
ceil: (n: number) => Math.ceil(n),
round: (n: number) => Math.round(n),
min: (...nums: number[]) => Math.min(...nums),
max: (...nums: number[]) => Math.max(...nums),
pow: (base: number, exp: number) => Math.pow(base, exp),
sqrt: (n: number) => Math.sqrt(n),
random: () => Math.random(),
clamp: (n: number, min: number, max: number) => Math.min(Math.max(n, min), max),
sign: (n: number) => Math.sign(n),
trunc: (n: number) => Math.trunc(n),
// predicates
'even?': (n: number) => n % 2 === 0,
'odd?': (n: number) => n % 2 !== 0,
'positive?': (n: number) => n > 0,
'negative?': (n: number) => n < 0,
'zero?': (n: number) => n === 0,
},
// enumerables
each: async (list: any[], cb: Function) => {
for (const value of list) await cb(value)
return list
},
// modules
load: async function (this: VM, path: string): Promise<Record<string, Value>> {
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: Record<string, Value> = {}
for (const [name, value] of this.scope.locals.entries())
module[name] = value
this.scope = scope
this.pc = pc
this.stopped = false
return module
},
}
export function formatValue(value: Value, inner = false): string {

89
src/prelude/list.ts Normal file
View File

@ -0,0 +1,89 @@
export const list = {
slice: (list: any[], start: number, end?: number) => list.slice(start, end),
map: async (list: any[], cb: Function) => {
let acc: any[] = []
for (const value of list) acc.push(await cb(value))
return acc
},
filter: async (list: any[], cb: Function) => {
let acc: any[] = []
for (const value of list) {
if (await cb(value)) acc.push(value)
}
return acc
},
reduce: async (list: any[], cb: Function, initial: any) => {
let acc = initial
for (const value of list) acc = await cb(acc, value)
return acc
},
find: async (list: any[], cb: Function) => {
for (const value of list) {
if (await cb(value)) return value
}
return null
},
// predicates
'empty?': (list: any[]) => list.length === 0,
'contains?': (list: any[], item: any) => list.includes(item),
'any?': async (list: any[], cb: Function) => {
for (const value of list) {
if (await cb(value)) return true
}
return false
},
'all?': async (list: any[], cb: Function) => {
for (const value of list) {
if (!await cb(value)) return false
}
return true
},
// sequence operations
reverse: (list: any[]) => list.slice().reverse(),
sort: (list: any[], cb?: (a: any, b: any) => number) => list.slice().sort(cb),
concat: (...lists: any[][]) => lists.flat(1),
flatten: (list: any[], depth: number = 1) => list.flat(depth),
unique: (list: any[]) => Array.from(new Set(list)),
zip: (list1: any[], list2: any[]) => list1.map((item, i) => [item, list2[i]]),
// access
first: (list: any[]) => list[0] ?? null,
last: (list: any[]) => list[list.length - 1] ?? null,
rest: (list: any[]) => list.slice(1),
take: (list: any[], n: number) => list.slice(0, n),
drop: (list: any[], n: number) => list.slice(n),
append: (list: any[], item: any) => [...list, item],
prepend: (list: any[], item: any) => [item, ...list],
'index-of': (list: any[], item: any) => list.indexOf(item),
// utilities
sum: (list: any[]) => list.reduce((acc, x) => acc + x, 0),
count: async (list: any[], cb: Function) => {
let count = 0
for (const value of list) {
if (await cb(value)) count++
}
return count
},
partition: async (list: any[], cb: Function) => {
const truthy: any[] = []
const falsy: any[] = []
for (const value of list) {
if (await cb(value)) truthy.push(value)
else falsy.push(value)
}
return [truthy, falsy]
},
compact: (list: any[]) => list.filter(x => x != null),
'group-by': async (list: any[], cb: Function) => {
const groups: Record<string, any[]> = {}
for (const value of list) {
const key = String(await cb(value))
if (!groups[key]) groups[key] = []
groups[key].push(value)
}
return groups
},
}

29
src/prelude/load.ts Normal file
View File

@ -0,0 +1,29 @@
import { resolve } from 'path'
import { readFileSync } from 'fs'
import { Compiler } from '#compiler/compiler'
import { type Value, VM, Scope } from 'reefvm'
export const load = async function (this: VM, path: string): Promise<Record<string, Value>> {
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: Record<string, Value> = {}
for (const [name, value] of this.scope.locals.entries())
module[name] = value
this.scope = scope
this.pc = pc
this.stopped = false
return module
}

21
src/prelude/math.ts Normal file
View File

@ -0,0 +1,21 @@
export const math = {
abs: (n: number) => Math.abs(n),
floor: (n: number) => Math.floor(n),
ceil: (n: number) => Math.ceil(n),
round: (n: number) => Math.round(n),
min: (...nums: number[]) => Math.min(...nums),
max: (...nums: number[]) => Math.max(...nums),
pow: (base: number, exp: number) => Math.pow(base, exp),
sqrt: (n: number) => Math.sqrt(n),
random: () => Math.random(),
clamp: (n: number, min: number, max: number) => Math.min(Math.max(n, min), max),
sign: (n: number) => Math.sign(n),
trunc: (n: number) => Math.trunc(n),
// predicates
'even?': (n: number) => n % 2 === 0,
'odd?': (n: number) => n % 2 !== 0,
'positive?': (n: number) => n > 0,
'negative?': (n: number) => n < 0,
'zero?': (n: number) => n === 0,
}

33
src/prelude/str.ts Normal file
View File

@ -0,0 +1,33 @@
// strings
export const str = {
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(),
// predicates
'starts-with?': (str: string, prefix: string) => str.startsWith(prefix),
'ends-with?': (str: string, suffix: string) => str.endsWith(suffix),
'contains?': (str: string, substr: string) => str.includes(substr),
'empty?': (str: string) => str.length === 0,
// inspection
'index-of': (str: string, search: string) => str.indexOf(search),
'last-index-of': (str: string, search: string) => str.lastIndexOf(search),
// transformations
replace: (str: string, search: string, replacement: string) => str.replace(search, replacement),
'replace-all': (str: string, search: string, replacement: string) => str.replaceAll(search, replacement),
slice: (str: string, start: number, end?: number | null) => str.slice(start, end ?? undefined),
substring: (str: string, start: number, end?: number | null) => str.substring(start, end ?? undefined),
repeat: (str: string, count: number) => str.repeat(count),
'pad-start': (str: string, length: number, pad: string = ' ') => str.padStart(length, pad),
'pad-end': (str: string, length: number, pad: string = ' ') => str.padEnd(length, pad),
lines: (str: string) => str.split('\n'),
chars: (str: string) => str.split(''),
// regex
match: (str: string, regex: RegExp) => str.match(regex),
'test?': (str: string, regex: RegExp) => regex.test(str),
}