Skip to content

Commit 430cc4e

Browse files
author
Asbjørn Hegdahl
committed
Adds modules
1 parent 44262a8 commit 430cc4e

File tree

7 files changed

+98
-19
lines changed

7 files changed

+98
-19
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/code

env.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ module.exports = function environment(parent) {
2020
vars[name] = value;
2121
};
2222

23-
const env = { extend, get, lookup, parent, set };
23+
const env = { extend, get, lookup, parent, set, vars };
2424

2525
return env;
2626
};

eval.js

+44-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
const deepEq = require("./util/deep-equal");
2+
const error = require("./util/error");
23
const environment = require("./env");
4+
const globals = require("./globals");
5+
const importModule = require("./util/import-module");
36

47
const catchWithNode = (node, fn) => {
58
try {
@@ -18,17 +21,14 @@ const throwWithNode = (node, msg) => {
1821
};
1922

2023
const getIdName = node => {
21-
if (node.type !== "id") {
22-
throwWithNode(
23-
node,
24-
`Type error: Cannot assign to node of type '${node.type}'`
25-
);
26-
}
24+
if (node.type !== "id")
25+
throwWithNode(node, `Type error: Unexpected '${node.type}'`);
26+
2727
return node.value;
2828
};
2929

30-
module.exports = function evaluate(node, env) {
31-
const eval = node => evaluate(node, env);
30+
function evaluate(node, cwd, env, expEnv) {
31+
const eval = node => evaluate(node, cwd, env);
3232

3333
const assertType = type => node => {
3434
const value = eval(node);
@@ -85,7 +85,7 @@ module.exports = function evaluate(node, env) {
8585
throwWithNode(args.slice(-1)[0], "Too many arguments");
8686
}
8787
args.forEach((arg, index) => scope.set(node.args[index].value, arg));
88-
return evaluate(node.body, scope);
88+
return evaluate(node.body, cwd, scope);
8989
};
9090

9191
const evalDestructuring = (left, right) => {
@@ -131,11 +131,42 @@ module.exports = function evaluate(node, env) {
131131
return eval(node.condition) ? eval(node.then) : eval(node.else);
132132
case "block":
133133
const blockEnv = env.extend();
134-
return node.body.reduce((_, node) => evaluate(node, blockEnv), null);
134+
return node.body.reduce((_, node) => evaluate(node, cwd, blockEnv), null);
135+
case "export":
136+
if (!expEnv) throwWithNode(node.left, "Cannot export in block scope");
137+
return expEnv.set(getIdName(node.left), eval(node.right));
138+
case "import":
139+
const moduleExports = catchWithNode(node.source, () =>
140+
importModule(eval(node.source), cwd, tryEvaluate)
141+
);
142+
const name = getIdName(node.id);
143+
if (name in moduleExports) return env.set(name, moduleExports[name]);
144+
else throwWithNode(node.id, `No export named '${name}'`);
135145
case "program":
136146
const globalEnv = environment();
137-
return node.body.reduce((_, node) => evaluate(node, globalEnv), null);
147+
Object.entries(globals).forEach(([name, value]) => {
148+
globalEnv.set(name, value);
149+
});
150+
const moduleEnv = globalEnv.extend();
151+
const exportEnv = environment();
152+
const body = node.body.reduce(
153+
(_, node) => evaluate(node, cwd, moduleEnv, exportEnv),
154+
null
155+
);
156+
return Object.keys(exportEnv.vars).length > 0
157+
? { __module: exportEnv.vars }
158+
: body;
138159
default:
139-
throw Error(`Missing implementation for '${node.type}'`);
160+
throw Error(`Eval: Missing implementation for '${node.type}'`);
140161
}
141-
};
162+
}
163+
164+
function tryEvaluate(ast, source, cwd) {
165+
try {
166+
return evaluate(ast, cwd);
167+
} catch (e) {
168+
error(e.message, source, e.node && e.node.loc, e.node && e.node.value);
169+
}
170+
}
171+
172+
module.exports = tryEvaluate;

globals.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
print: (...messages) => console.log(...messages)
3+
};

lexer.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
const error = require("./util/error");
22

3-
const keywords = ["let", "true", "false", "if", "else", "then"];
3+
const keywords = [
4+
"let",
5+
"true",
6+
"false",
7+
"if",
8+
"else",
9+
"then",
10+
"import",
11+
"from",
12+
"export"
13+
];
414
/* prettier-ignore */
515
const operators = [
616
"=>",

parser.js

+14-4
Original file line numberDiff line numberDiff line change
@@ -169,26 +169,36 @@ const parse = (source, ts) => {
169169
};
170170
};
171171

172-
const parseAssign = () => {
173-
ts.next();
172+
const parseDeclaration = (kw, type) => {
173+
skipKw(kw);
174174
if (isPunc("(")) return parseDestructuring();
175175
const tokens = parseWhile(isId, parseAtom);
176176
skipOp("=");
177177
const [id, ...ids] = tokens;
178178
return {
179-
type: "assign",
179+
type,
180180
left: id,
181181
right: ids.length
182182
? { type: "fun", args: ids, body: parseExpression() }
183183
: parseExpression()
184184
};
185185
};
186186

187+
const parseImport = () => {
188+
skipKw("import");
189+
const id = isId() ? ts.next() : error();
190+
skipKw("from");
191+
const source = isStr() ? ts.next() : error();
192+
return { type: "import", id, source };
193+
};
194+
187195
const parseAtom = () => {
188196
if (isPunc("{")) return parseBlock();
189197
if (isPunc("(")) return parseParenthesized(parseExpression);
190198
if (isArray()) return parseArray();
191-
if (isKw("let")) return parseAssign();
199+
if (isKw("import")) return parseImport();
200+
if (isKw("export")) return parseDeclaration("export", "export");
201+
if (isKw("let")) return parseDeclaration("let", "assign");
192202
if (isKw("if")) return parseIf();
193203
if (isBool()) return { type: "bool", value: ts.next().value === "true" };
194204
if (isNum()) return parseNum();

util/import-module.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
4+
const parse = require("../parser");
5+
6+
const lib = {};
7+
8+
module.exports = function importModule(sourcePath, cwd, eval) {
9+
const errorMessage = `Module '${sourcePath}' not found.`;
10+
const isLib = !sourcePath.startsWith(".") && !sourcePath.startsWith("/");
11+
if (isLib) {
12+
const libModule = lib[sourcePath];
13+
if (!libModule) throw Error(errorMessage);
14+
return libModule;
15+
} else {
16+
const filePath = path.resolve(cwd, sourcePath);
17+
if (!fs.existsSync(filePath)) throw Error(errorMessage);
18+
const source = fs.readFileSync(filePath, "utf8");
19+
const file = eval(parse(source), source, cwd);
20+
if (!file || typeof file !== "object" || !file.__module)
21+
throw Error(`No modules exported from '${sourcePath}'`);
22+
return file.__module;
23+
}
24+
};

0 commit comments

Comments
 (0)