Adding builtins as native-functions. desugaring symbols as native function application. except ~ which is the new pipe operator, > is now greater than
parent
5b40e9d298
commit
216fe6bd30
@ -0,0 +1,447 @@
|
||||
import type { Value, NativeFunction } from './types'
|
||||
|
||||
function expectInt(v: Value, name: string): number {
|
||||
if (v.kind !== 'int')
|
||||
throw new Error(`${name} expects int, got ${v.kind}`);
|
||||
return v.value;
|
||||
}
|
||||
|
||||
// function expectFloat(v: Value, name: string): number {
|
||||
// if (v.kind !== 'float')
|
||||
// throw new Error(`${name} expects float, got ${v.kind}`);
|
||||
// return v.value;
|
||||
// }
|
||||
|
||||
function expectNumber(v: Value, name: string): number {
|
||||
if (v.kind !== 'float' && v.kind !== 'int')
|
||||
throw new Error(`${name} expects number, got ${v.kind}`);
|
||||
return v.value;
|
||||
}
|
||||
|
||||
// function expectString(v: Value, name: string): string {
|
||||
// if (v.kind !== 'string')
|
||||
// throw new Error(`${name} expects string, got ${v.kind}`);
|
||||
// return v.value;
|
||||
// }
|
||||
|
||||
// function expectList(v: Value, name: string): Value[] {
|
||||
// if (v.kind !== 'list')
|
||||
// throw new Error(`${name} expects list, got ${v.kind}`);
|
||||
// return v.elements;
|
||||
// }
|
||||
|
||||
export const builtins: { [name: string]: NativeFunction } = {
|
||||
// Arithmetic
|
||||
'add': {
|
||||
kind: 'native',
|
||||
name: 'add',
|
||||
arity: 2,
|
||||
fn: (a, b) => {
|
||||
const x = expectNumber(a, 'add');
|
||||
const y = expectNumber(b, 'add');
|
||||
return { kind: 'int', value: x + y };
|
||||
}
|
||||
},
|
||||
|
||||
'sub': {
|
||||
kind: 'native',
|
||||
name: 'sub',
|
||||
arity: 2,
|
||||
fn: (a, b) => {
|
||||
const x = expectNumber(a, 'sub');
|
||||
const y = expectNumber(b, 'sub');
|
||||
return { kind: 'int', value: x - y };
|
||||
}
|
||||
},
|
||||
|
||||
'mul': {
|
||||
kind: 'native',
|
||||
name: 'mul',
|
||||
arity: 2,
|
||||
fn: (a, b) => {
|
||||
const x = expectNumber(a, 'mul');
|
||||
const y = expectNumber(b, 'mul');
|
||||
return { kind: 'int', value: x * y };
|
||||
}
|
||||
},
|
||||
|
||||
'div': {
|
||||
kind: 'native',
|
||||
name: 'div',
|
||||
arity: 2,
|
||||
fn: (a, b) => {
|
||||
const x = expectNumber(a, 'div');
|
||||
const y = expectNumber(b, 'div');
|
||||
return { kind: 'int', value: Math.floor(x / y) };
|
||||
}
|
||||
},
|
||||
|
||||
'mod': {
|
||||
kind: 'native',
|
||||
name: 'mod',
|
||||
arity: 2,
|
||||
fn: (a, b) => {
|
||||
const x = expectInt(a, 'mod');
|
||||
const y = expectInt(b, 'mod');
|
||||
return { kind: 'int', value: x % y };
|
||||
}
|
||||
},
|
||||
|
||||
'pow': {
|
||||
kind: 'native',
|
||||
name: 'pow',
|
||||
arity: 2,
|
||||
fn: (a, b) => {
|
||||
const x = expectNumber(a, 'add');
|
||||
const y = expectNumber(b, 'add');
|
||||
return { kind: 'int', value: Math.pow(x, y) };
|
||||
}
|
||||
},
|
||||
|
||||
// Comparison
|
||||
'eq': {
|
||||
kind: 'native',
|
||||
name: 'eq',
|
||||
arity: 2,
|
||||
fn: (a, b) => {
|
||||
return {
|
||||
kind: 'constructor',
|
||||
name: JSON.stringify(a) === JSON.stringify(b) ? 'True' : 'False',
|
||||
args: []
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
'neq': {
|
||||
kind: 'native',
|
||||
name: 'eq',
|
||||
arity: 2,
|
||||
fn: (a, b) => {
|
||||
return {
|
||||
kind: 'constructor',
|
||||
name: JSON.stringify(a) !== JSON.stringify(b) ? 'True' : 'False',
|
||||
args: []
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
'lt': {
|
||||
kind: 'native',
|
||||
name: 'lt',
|
||||
arity: 2,
|
||||
fn: (a, b) => {
|
||||
const x = expectNumber(a, 'lt');
|
||||
const y = expectNumber(b, 'lt');
|
||||
return {
|
||||
kind: 'constructor',
|
||||
name: x < y ? 'True' : 'False',
|
||||
args: []
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
'gt': {
|
||||
kind: 'native',
|
||||
name: 'gt',
|
||||
arity: 2,
|
||||
fn: (a, b) => {
|
||||
const x = expectNumber(a, 'gt');
|
||||
const y = expectNumber(b, 'gt');
|
||||
return {
|
||||
kind: 'constructor',
|
||||
name: x > y ? 'True' : 'False',
|
||||
args: []
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
'lte': {
|
||||
kind: 'native',
|
||||
name: 'lt',
|
||||
arity: 2,
|
||||
fn: (a, b) => {
|
||||
const x = expectNumber(a, 'lte');
|
||||
const y = expectNumber(b, 'lt');
|
||||
return {
|
||||
kind: 'constructor',
|
||||
name: x <= y ? 'True' : 'False',
|
||||
args: []
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
'gte': {
|
||||
kind: 'native',
|
||||
name: 'gte',
|
||||
arity: 2,
|
||||
fn: (a, b) => {
|
||||
const x = expectNumber(a, 'gte');
|
||||
const y = expectNumber(b, 'gte');
|
||||
return {
|
||||
kind: 'constructor',
|
||||
name: x >= y ? 'True' : 'False',
|
||||
args: []
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// String & List
|
||||
'cat': {
|
||||
kind: 'native',
|
||||
name: 'cat',
|
||||
arity: 2,
|
||||
fn: (a, b) => {
|
||||
if (a.kind === 'string' && b.kind === 'string') {
|
||||
return { kind: 'string', value: a.value + b.value };
|
||||
}
|
||||
if (a.kind === 'list' && b.kind === 'list') {
|
||||
return { kind: 'list', elements: [...a.elements, ...b.elements] };
|
||||
}
|
||||
throw new Error('cat requires 2 lists or 2 strings');
|
||||
}
|
||||
},
|
||||
|
||||
'len': {
|
||||
kind: 'native',
|
||||
name: 'len',
|
||||
arity: 1,
|
||||
fn: (seq) => {
|
||||
if (seq.kind === 'string') {
|
||||
return { kind: 'int', value: seq.value.length };
|
||||
}
|
||||
if (seq.kind === 'list') {
|
||||
return { kind: 'int', value: seq.elements.length };
|
||||
}
|
||||
throw new Error('cat requires a list or a string');
|
||||
}
|
||||
},
|
||||
|
||||
'at': {
|
||||
kind: 'native',
|
||||
name: 'at',
|
||||
arity: 2,
|
||||
fn: (seq, idx) => {
|
||||
const i = expectInt(idx, 'at');
|
||||
|
||||
if (seq.kind === 'string') {
|
||||
return { kind: 'string', value: seq.value[i] || '' };
|
||||
}
|
||||
if (seq.kind === 'list') {
|
||||
return seq.elements[i];
|
||||
}
|
||||
throw new Error('at requires a list or a string');
|
||||
}
|
||||
},
|
||||
|
||||
'slice': {
|
||||
kind: 'native',
|
||||
name: 'slice',
|
||||
arity: 3,
|
||||
fn: (seq, start, end) => {
|
||||
const s = expectInt(start, 'slice');
|
||||
const e = expectInt(end, 'slice');
|
||||
|
||||
if (seq.kind === 'string') {
|
||||
return { kind: 'string', value: seq.value.slice(s, e) };
|
||||
}
|
||||
if (seq.kind === 'list') {
|
||||
return { kind: 'list', elements: seq.elements.slice(s, e) };
|
||||
}
|
||||
throw new Error('slice requires a list or a string');
|
||||
}
|
||||
},
|
||||
|
||||
'head': {
|
||||
kind: 'native',
|
||||
name: 'head',
|
||||
arity: 1,
|
||||
fn: (seq) => {
|
||||
if (seq.kind === 'string') {
|
||||
if (seq.value.length === 0) {
|
||||
return { kind: 'constructor', name: 'None', args: [] };
|
||||
}
|
||||
return { kind: 'constructor', name: 'Some', args: [{ kind: 'string', value: seq.value[0] }] };
|
||||
}
|
||||
if (seq.kind === 'list') {
|
||||
if (seq.elements.length === 0) {
|
||||
return { kind: 'constructor', name: 'None', args: [] };
|
||||
}
|
||||
return { kind: 'constructor', name: 'Some', args: [seq.elements[0]] };
|
||||
}
|
||||
throw new Error('head requires a list or a string');
|
||||
}
|
||||
},
|
||||
|
||||
'tail': {
|
||||
kind: 'native',
|
||||
name: 'tail',
|
||||
arity: 1,
|
||||
fn: (seq) => {
|
||||
if (seq.kind === 'string') {
|
||||
if (seq.value.length === 0) {
|
||||
return { kind: 'constructor', name: 'None', args: [] };
|
||||
}
|
||||
return { kind: 'constructor', name: 'Some', args: [{ kind: 'string', value: seq.value.slice(1) }] };
|
||||
}
|
||||
if (seq.kind === 'list') {
|
||||
if (seq.elements.length === 0) {
|
||||
return { kind: 'constructor', name: 'None', args: [] };
|
||||
}
|
||||
return { kind: 'constructor', name: 'Some', args: [{ kind: 'list', elements: seq.elements.slice(1) }] };
|
||||
}
|
||||
throw new Error('tail requires a list or a string');
|
||||
}
|
||||
},
|
||||
|
||||
// Types
|
||||
'str': {
|
||||
kind: 'native',
|
||||
name: 'str',
|
||||
arity: 1,
|
||||
fn: (val) => {
|
||||
if (val.kind === 'int' || val.kind === 'float')
|
||||
return { kind: 'string', value: val.value.toString() }
|
||||
|
||||
if (val.kind === 'string')
|
||||
return val;
|
||||
|
||||
throw new Error('str: cannot convert to string');
|
||||
}
|
||||
},
|
||||
|
||||
'int': {
|
||||
kind: 'native',
|
||||
name: 'int',
|
||||
arity: 1,
|
||||
fn: (val) => {
|
||||
if (val.kind === 'int')
|
||||
return val;
|
||||
|
||||
if (val.kind === 'float')
|
||||
return { kind: 'int', value: Math.floor(val.value) };
|
||||
|
||||
if (val.kind === 'string') {
|
||||
const parsed = parseInt(val.value, 10);
|
||||
|
||||
if (isNaN(parsed))
|
||||
throw new Error(`int: cannot parse "${val.value}"`);
|
||||
|
||||
return { kind: 'int', value: parsed }
|
||||
}
|
||||
|
||||
throw new Error(`int: cannot convert to int`);
|
||||
}
|
||||
},
|
||||
|
||||
'float': {
|
||||
kind: 'native',
|
||||
name: 'float',
|
||||
arity: 1,
|
||||
fn: (val) => {
|
||||
if (val.kind === 'float')
|
||||
return val;
|
||||
|
||||
if (val.kind === 'int')
|
||||
return { kind: 'float', value: val.value };
|
||||
|
||||
if (val.kind === 'string') {
|
||||
const parsed = parseFloat(val.value, 10);
|
||||
|
||||
if (isNaN(parsed))
|
||||
throw new Error(`float: cannot parse "${val.value}"`);
|
||||
|
||||
return { kind: 'float', value: parsed }
|
||||
}
|
||||
|
||||
throw new Error(`float: cannot convert to float`);
|
||||
}
|
||||
},
|
||||
|
||||
// Math
|
||||
'sqrt': {
|
||||
kind: 'native',
|
||||
name: 'sqrt',
|
||||
arity: 1,
|
||||
fn: (val) => {
|
||||
const x = expectNumber(val, 'sqrt');
|
||||
return { kind: 'float', value: Math.sqrt(x) };
|
||||
}
|
||||
},
|
||||
|
||||
'abs': {
|
||||
kind: 'native',
|
||||
name: 'abs',
|
||||
arity: 1,
|
||||
fn: (val) => {
|
||||
if (val.kind === 'int')
|
||||
return { kind: 'int', value: Math.abs(val.value) };
|
||||
|
||||
if (val.kind === 'float')
|
||||
return { kind: 'float', value: Math.abs(val.value) };
|
||||
|
||||
throw new Error('abs expects a number');
|
||||
}
|
||||
},
|
||||
|
||||
'floor': {
|
||||
kind: 'native',
|
||||
name: 'floor',
|
||||
arity: 1,
|
||||
fn: (val) => {
|
||||
const x = expectNumber(val, 'floor');
|
||||
return { kind: 'int', value: Math.floor(x) };
|
||||
}
|
||||
},
|
||||
|
||||
'ceil': {
|
||||
kind: 'native',
|
||||
name: 'ceil',
|
||||
arity: 1,
|
||||
fn: (val) => {
|
||||
const x = expectNumber(val, 'ceil');
|
||||
return { kind: 'int', value: Math.ceil(x) };
|
||||
}
|
||||
},
|
||||
|
||||
'round': {
|
||||
kind: 'native',
|
||||
name: 'round',
|
||||
arity: 1,
|
||||
fn: (val) => {
|
||||
const x = expectNumber(val, 'round');
|
||||
return { kind: 'int', value: Math.round(x) };
|
||||
}
|
||||
},
|
||||
|
||||
'min': {
|
||||
kind: 'native',
|
||||
name: 'min',
|
||||
arity: 2,
|
||||
fn: (a, b) => {
|
||||
const x = expectNumber(a, 'min');
|
||||
const y = expectNumber(b, 'min');
|
||||
const result = Math.min(x, y);
|
||||
|
||||
if (a.kind === 'float' || b.kind === 'float')
|
||||
return { kind: 'float', value: result };
|
||||
|
||||
return { kind: 'int', value: result };
|
||||
}
|
||||
},
|
||||
|
||||
'max': {
|
||||
kind: 'native',
|
||||
name: 'max',
|
||||
arity: 2,
|
||||
fn: (a, b) => {
|
||||
const x = expectNumber(a, 'max');
|
||||
const y = expectNumber(b, 'max');
|
||||
const result = Math.max(x, y);
|
||||
|
||||
if (a.kind === 'float' || b.kind === 'float')
|
||||
return { kind: 'float', value: result };
|
||||
|
||||
return { kind: 'int', value: result };
|
||||
}
|
||||
},
|
||||
}
|
||||
Loading…
Reference in New Issue