split up prelude modules
This commit is contained in:
parent
9e38fa7a44
commit
4fb58483f0
24
src/prelude/dict.ts
Normal file
24
src/prelude/dict.ts
Normal 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),
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
// The prelude creates all the builtin Shrimp functions.
|
// The prelude creates all the builtin Shrimp functions.
|
||||||
|
|
||||||
import { resolve, parse } from 'path'
|
|
||||||
import { readFileSync } from 'fs'
|
|
||||||
import { Compiler } from '#compiler/compiler'
|
|
||||||
import {
|
import {
|
||||||
VM, Scope, toValue, type Value,
|
type Value, toValue,
|
||||||
extractParamInfo, isWrapped, getOriginalFunction,
|
extractParamInfo, isWrapped, getOriginalFunction,
|
||||||
} from 'reefvm'
|
} 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 = {
|
export const colors = {
|
||||||
reset: '\x1b[0m',
|
reset: '\x1b[0m',
|
||||||
bright: '\x1b[1m',
|
bright: '\x1b[1m',
|
||||||
|
|
@ -22,6 +25,12 @@ export const colors = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const globals = {
|
export const globals = {
|
||||||
|
dict,
|
||||||
|
load,
|
||||||
|
list,
|
||||||
|
math,
|
||||||
|
str,
|
||||||
|
|
||||||
// hello
|
// hello
|
||||||
echo: (...args: any[]) => {
|
echo: (...args: any[]) => {
|
||||||
console.log(...args.map(a => {
|
console.log(...args.map(a => {
|
||||||
|
|
@ -64,121 +73,6 @@ export const globals = {
|
||||||
dec: (n: number) => n - 1,
|
dec: (n: number) => n - 1,
|
||||||
identity: (v: any) => v,
|
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
|
// collections
|
||||||
at: (collection: any, index: number | string) => collection[index],
|
at: (collection: any, index: number | string) => collection[index],
|
||||||
range: (start: number, end: number | null) => {
|
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
|
// enumerables
|
||||||
each: async (list: any[], cb: Function) => {
|
each: async (list: any[], cb: Function) => {
|
||||||
for (const value of list) await cb(value)
|
for (const value of list) await cb(value)
|
||||||
return list
|
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 {
|
export function formatValue(value: Value, inner = false): string {
|
||||||
|
|
|
||||||
89
src/prelude/list.ts
Normal file
89
src/prelude/list.ts
Normal 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
29
src/prelude/load.ts
Normal 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
21
src/prelude/math.ts
Normal 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
33
src/prelude/str.ts
Normal 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),
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user