-
Notifications
You must be signed in to change notification settings - Fork 0
/
core.js
100 lines (91 loc) · 2.62 KB
/
core.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
const { last, head } = require("ramda")
/**
* Evaluates the given expression and returns the result of it.
* A valid expression might be:
* - a constant value, such as: `1`, `() => 0`, `[1, 2, 3]`
* - a Lisp like list-based expression: `[map, square, [1, 2, 3]]`
*/
const evalExp = exp => {
if (!(exp instanceof Array) || !exp.length) {
return exp
}
// the first element of an expression could itself be
// an expression which has to be evaluated first.
// example:
// const car = [deffun, list => [head, list]]
// evalExp([car, [1, 2, 3]])
const expValues = exp.map(evalExp)
const head = expValues[0]
if (typeof head !== "function") {
// return the original expression as it is
return exp
}
// call the expression head with the resolved expression values
return head.apply(this, expValues.slice(1))
}
/**
* Allows defining one or multiple variables
* Accepts n arguments of values, where the last argument is
* the callback called with the defined values.
* The callback is expected to return an expression.
*
* @example
* [def, 42, "foo", () => 0, (a, b, c) => [
* print(a, b, c) // 42, "foo", Function
* ]]
*/
const def = function() {
const args = Array.prototype.slice.call(arguments)
const values = args.slice(0, -1)
const callback = last(args)
const cbArgs = values.map(evalExp)
return evalExp(callback(...cbArgs))
}
/**
* Curries the given function requesting the provided
* number of arguments.
*
* @param {number} argCount Number of arguments
* @param {Function} fn Function to curry
*
* @example
* const add3 = (a, b, c) => a + b + c
* const curried = curryN(3, add3)
* curried(1)(2)(3) === 6
* curried(1, 2)(3) === 6
* curried(1)(2, 3) === 6
* curried(1, 2, 3) === 6
*/
const curryN = function curryN(argCount, fn) {
return function curried(...args) {
return args.length < argCount
? curryN(argCount - args.length, (...rest) => fn(...args, ...rest))
: fn(...args)
}
}
/**
* Allows defining a function as part of an expression.
* Expects a function body accepting any kind of arguments.
* The provided function body is expected to return an expression
*
* The provided function body is not called,
* until the number of expected arguments is given (auto-curried).
*
* @param {number} number of expected arguments
* @param {Function} body Function body to apply
*
* @example
* caar = [deffun, list => [car, [car, list]]]
*/
const deffun = function deffun(argCount, body) {
const curried = curryN(argCount, body)
return function(...args) {
return evalExp(curried(...args))
}
}
module.exports = {
evalExp,
curryN,
deffun,
def,
}