start on a prelude of builtin functions
This commit is contained in:
parent
7a4affd01e
commit
e7352cec2e
93
bin/repl
93
bin/repl
|
|
@ -1,24 +1,12 @@
|
||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
import { Compiler } from '../src/compiler/compiler'
|
import { Compiler } from '../src/compiler/compiler'
|
||||||
import { VM, type Value, Scope, bytecodeToString } from 'reefvm'
|
import { colors, formatValue, nativeFunctions, valueFunctions } from '../src/prelude'
|
||||||
|
import { VM, Scope, bytecodeToString } from 'reefvm'
|
||||||
import * as readline from 'readline'
|
import * as readline from 'readline'
|
||||||
import { readFileSync, writeFileSync } from 'fs'
|
import { readFileSync, writeFileSync } from 'fs'
|
||||||
import { basename } from 'path'
|
import { basename } from 'path'
|
||||||
|
|
||||||
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'
|
|
||||||
}
|
|
||||||
|
|
||||||
async function repl() {
|
async function repl() {
|
||||||
const commands = ['/clear', '/reset', '/vars', '/funcs', '/history', '/bytecode', '/exit', '/save', '/quit']
|
const commands = ['/clear', '/reset', '/vars', '/funcs', '/history', '/bytecode', '/exit', '/save', '/quit']
|
||||||
|
|
||||||
|
|
@ -60,7 +48,7 @@ async function repl() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vm ||= new VM({ instructions: [], constants: [] }, nativeFunctions)
|
vm ||= new VM({ instructions: [], constants: [] }, nativeFunctions, valueFunctions)
|
||||||
|
|
||||||
if (['/exit', 'exit', '/quit', 'quit'].includes(trimmed)) {
|
if (['/exit', 'exit', '/quit', 'quit'].includes(trimmed)) {
|
||||||
console.log(`\n${colors.yellow}Goodbye!${colors.reset}`)
|
console.log(`\n${colors.yellow}Goodbye!${colors.reset}`)
|
||||||
|
|
@ -186,40 +174,6 @@ async function repl() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function formatValue(value: Value, inner = false): string {
|
|
||||||
switch (value.type) {
|
|
||||||
case 'string':
|
|
||||||
return `${colors.green}'${value.value}'${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.join(', ')
|
|
||||||
return `${colors.dim}<fn(${params})>${colors.reset}`
|
|
||||||
}
|
|
||||||
case 'native':
|
|
||||||
return `${colors.dim}<native-fn>${colors.reset}`
|
|
||||||
case 'regex':
|
|
||||||
return `${colors.magenta}${value.value}${colors.reset}`
|
|
||||||
default:
|
|
||||||
return String(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatVariables(scope: Scope, onlyFunctions = false): string {
|
function formatVariables(scope: Scope, onlyFunctions = false): string {
|
||||||
const vars: string[] = []
|
const vars: string[] = []
|
||||||
|
|
||||||
|
|
@ -257,7 +211,7 @@ async function loadFile(filePath: string): Promise<{ vm: VM; codeHistory: string
|
||||||
|
|
||||||
console.log(`${colors.dim}Loading ${basename(filePath)}...${colors.reset}`)
|
console.log(`${colors.dim}Loading ${basename(filePath)}...${colors.reset}`)
|
||||||
|
|
||||||
const vm = new VM({ instructions: [], constants: [] }, nativeFunctions)
|
const vm = new VM({ instructions: [], constants: [] }, nativeFunctions, valueFunctions)
|
||||||
await vm.run()
|
await vm.run()
|
||||||
|
|
||||||
const codeHistory: string[] = []
|
const codeHistory: string[] = []
|
||||||
|
|
@ -313,43 +267,4 @@ function showWelcome() {
|
||||||
console.log()
|
console.log()
|
||||||
}
|
}
|
||||||
|
|
||||||
const nativeFunctions = {
|
|
||||||
echo: (...args: any[]) => {
|
|
||||||
console.log(...args)
|
|
||||||
},
|
|
||||||
len: (value: any) => {
|
|
||||||
if (typeof value === 'string') return value.length
|
|
||||||
if (Array.isArray(value)) return value.length
|
|
||||||
if (value && typeof value === 'object') return Object.keys(value).length
|
|
||||||
return 0
|
|
||||||
},
|
|
||||||
type: (value: any) => {
|
|
||||||
if (value === null) return 'null'
|
|
||||||
if (Array.isArray(value)) return 'array'
|
|
||||||
return typeof value
|
|
||||||
},
|
|
||||||
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
|
|
||||||
},
|
|
||||||
join: (arr: any[], sep: string = ',') => {
|
|
||||||
return arr.join(sep)
|
|
||||||
},
|
|
||||||
split: (str: string, sep: string = ',') => {
|
|
||||||
return str.split(sep)
|
|
||||||
},
|
|
||||||
upper: (str: string) => str.toUpperCase(),
|
|
||||||
lower: (str: string) => str.toLowerCase(),
|
|
||||||
trim: (str: string) => str.trim(),
|
|
||||||
list: (...args: any[]) => args,
|
|
||||||
dict: (atNamed = {}) => atNamed
|
|
||||||
}
|
|
||||||
|
|
||||||
await repl()
|
await repl()
|
||||||
|
|
|
||||||
45
bin/shrimp
45
bin/shrimp
|
|
@ -1,57 +1,18 @@
|
||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
import { Compiler } from '../src/compiler/compiler'
|
import { Compiler } from '../src/compiler/compiler'
|
||||||
import { VM, toValue, fromValue, bytecodeToString } from 'reefvm'
|
import { colors, nativeFunctions, valueFunctions } from '../src/prelude'
|
||||||
|
import { VM, fromValue, bytecodeToString } from 'reefvm'
|
||||||
import { readFileSync, writeFileSync, mkdirSync } from 'fs'
|
import { readFileSync, writeFileSync, mkdirSync } from 'fs'
|
||||||
import { randomUUID } from "crypto"
|
import { randomUUID } from "crypto"
|
||||||
import { spawn } from 'child_process'
|
import { spawn } from 'child_process'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
|
||||||
const colors = {
|
|
||||||
reset: '\x1b[0m',
|
|
||||||
bright: '\x1b[1m',
|
|
||||||
dim: '\x1b[2m',
|
|
||||||
red: '\x1b[31m',
|
|
||||||
yellow: '\x1b[33m',
|
|
||||||
cyan: '\x1b[36m',
|
|
||||||
magenta: '\x1b[35m',
|
|
||||||
pink: '\x1b[38;2;255;105;180m'
|
|
||||||
}
|
|
||||||
|
|
||||||
const nativeFunctions = {
|
|
||||||
echo: (...args: any[]) => console.log(...args),
|
|
||||||
len: (value: any) => {
|
|
||||||
if (typeof value === 'string') return value.length
|
|
||||||
if (Array.isArray(value)) return value.length
|
|
||||||
if (value && typeof value === 'object') return Object.keys(value).length
|
|
||||||
return 0
|
|
||||||
},
|
|
||||||
type: (value: any) => toValue(value).type,
|
|
||||||
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
|
|
||||||
},
|
|
||||||
join: (arr: any[], sep: string = ',') => arr.join(sep),
|
|
||||||
split: (str: string, sep: string = ',') => str.split(sep),
|
|
||||||
upper: (str: string) => str.toUpperCase(),
|
|
||||||
lower: (str: string) => str.toLowerCase(),
|
|
||||||
trim: (str: string) => str.trim(),
|
|
||||||
list: (...args: any[]) => args,
|
|
||||||
dict: (atNamed = {}) => atNamed
|
|
||||||
}
|
|
||||||
|
|
||||||
async function runFile(filePath: string) {
|
async function runFile(filePath: string) {
|
||||||
try {
|
try {
|
||||||
const code = readFileSync(filePath, 'utf-8')
|
const code = readFileSync(filePath, 'utf-8')
|
||||||
const compiler = new Compiler(code)
|
const compiler = new Compiler(code)
|
||||||
const vm = new VM(compiler.bytecode, nativeFunctions)
|
const vm = new VM(compiler.bytecode, nativeFunctions, valueFunctions)
|
||||||
await vm.run()
|
await vm.run()
|
||||||
return vm.stack.length ? fromValue(vm.stack[vm.stack.length - 1]) : null
|
return vm.stack.length ? fromValue(vm.stack[vm.stack.length - 1]) : null
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
|
||||||
110
src/prelude.ts
Normal file
110
src/prelude.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
// The prelude creates all the builtin Shrimp functions.
|
||||||
|
|
||||||
|
import { 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 valueFunctions = {
|
||||||
|
echo: (...args: Value[]) => {
|
||||||
|
console.log(...args.map(a => formatValue(a)))
|
||||||
|
return toValue(null)
|
||||||
|
},
|
||||||
|
|
||||||
|
length: (value: Value) => {
|
||||||
|
switch (value.type) {
|
||||||
|
case 'string': case 'array':
|
||||||
|
return toValue(value.value.length)
|
||||||
|
case 'dict':
|
||||||
|
return toValue(Object.keys(value.value).length)
|
||||||
|
default:
|
||||||
|
return toValue(0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
type: (value: Value) => toValue(value.type),
|
||||||
|
inspect: (value: Value) => toValue(formatValue(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const nativeFunctions = {
|
||||||
|
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
|
||||||
|
},
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user