Prelude of builtin functions #7
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.
|
||||
|
||||
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
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
Why use
thishere instead ofvm?