Skip to content

Commit

Permalink
merge with try_catch
Browse files Browse the repository at this point in the history
  • Loading branch information
jcubic committed Jan 19, 2024
2 parents a68d5b3 + 713af75 commit 0f01c6b
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 78 deletions.
108 changes: 79 additions & 29 deletions src/lips.js
Original file line number Diff line number Diff line change
Expand Up @@ -3394,7 +3394,9 @@ function macro_expand(single) {
if (node instanceof Pair) {
return node.car.valueOf();
}
throw new Error('macroexpand: Invalid let binding');
const t = type(node);
const msg = `macroexpand: Invalid let binding expectig pair got ${t}`;
throw new Error(msg);
})];
}
function is_macro(name, value) {
Expand Down Expand Up @@ -4295,7 +4297,7 @@ function is_promise(o) {
if (o instanceof Promise) {
return true;
}
return o && is_function(o.then);
return !!o && is_function(o.then);
}
// ----------------------------------------------------------------------
function is_undef(value) {
Expand Down Expand Up @@ -6687,6 +6689,11 @@ function LipsError(message, args) {
LipsError.prototype = new Error();
LipsError.prototype.constructor = LipsError;
// -------------------------------------------------------------------------
// :: Fake exception to handle try catch to break the execution
// :: of body expression #163
// -------------------------------------------------------------------------
class IgnoreException extends Error { }
// -------------------------------------------------------------------------
// :: Environment constructor (parent and name arguments are optional)
// -------------------------------------------------------------------------
function Environment(obj, parent, name) {
Expand Down Expand Up @@ -9014,7 +9021,7 @@ var global_env = new Environment({
// ------------------------------------------------------------------
'try': doc(new Macro('try', function(code, { use_dynamic, error }) {
return new Promise((resolve, reject) => {
var catch_clause, finally_clause;
let catch_clause, finally_clause, body_error;
if (LSymbol.is(code.cdr.car.car, 'catch')) {
catch_clause = code.cdr.car;
if (code.cdr.cdr instanceof Pair &&
Expand All @@ -9027,11 +9034,20 @@ var global_env = new Environment({
if (!(finally_clause || catch_clause)) {
throw new Error('try: invalid syntax');
}
let next = resolve;
function finalize(result) {
resolve(result);
throw new IgnoreException('[CATCH]');
}
let next = (result, next) => {
next(result);
}
if (finally_clause) {
next = function(result, cont) {
// prevent infinite loop when finally throw exception
next = reject;
args.error = (e) => {
throw e;
};
unpromise(evaluate(new Pair(
new LSymbol('begin'),
finally_clause.cdr
Expand All @@ -9045,33 +9061,45 @@ var global_env = new Environment({
use_dynamic,
dynamic_env: this,
error: (e) => {
body_error = true;
var env = this.inherit('try');
if (catch_clause) {
env.set(catch_clause.cdr.car.car, e);
const name = catch_clause.cdr.car.car;
if (!(name instanceof LSymbol)) {
throw new Error('try: invalid syntax: catch require variable name');
}
env.set(name, e);
let catch_error;
var args = {
env,
error
use_dynamic,
dynamic_env: this,
error: (e) => {
catch_error = true;
reject(e);
throw new IgnoreException('[CATCH]');
}
};
args.dynamic_env = this;
unpromise(evaluate(new Pair(
const value = evaluate(new Pair(
new LSymbol('begin'),
catch_clause.cdr.cdr
), args), function(result) {
next(result, resolve);
), args);
unpromise(value, function handler(result) {
if (!catch_error) {
next(result, finalize);
}
});
} else {
next(e, error);
next(undefined, () => {
throw e;
});
}
}
};
let result = evaluate(code.car, args);
if (is_promise(result)) {
result.then(result => {
next(result, resolve);
}).catch(args.error);
} else {
const value = evaluate(code.car, args);
unpromise(value, function(result) {
next(result, resolve);
}
}, args.error);
});
}), `(try expr (catch (e) code))
(try expr (catch (e) code) (finally code))
Expand Down Expand Up @@ -9731,7 +9759,8 @@ function nodeModuleFind(dir) {
function is_node() {
return typeof global !== 'undefined' && global.global === global;
}

// -------------------------------------------------------------------------
const noop = () => {};
// -------------------------------------------------------------------------
async function node_specific() {
const { createRequire } = await import('mod' + 'ule');
Expand Down Expand Up @@ -9780,6 +9809,15 @@ async function node_specific() {
}, `(require module)
Function used inside Node.js to import a module.`));

// ignore exceptions that are catched elsewhere. This is needed to fix AVA

Check failure on line 9813 in src/lips.js

View workflow job for this annotation

GitHub Actions / Check for spelling errors

catched ==> caught
// reporting unhandled rejections for try..catch
// see: https://github.com/avajs/ava/discussions/3289
process.on('unhandledRejection', (reason, promise) => {
if (reason instanceof IgnoreException) {
promise.catch(noop);
}
});
}
// -------------------------------------------------------------------------
/* c8 ignore next 11 */
Expand Down Expand Up @@ -10025,15 +10063,26 @@ function evaluate_macro(macro, code, eval_args) {
}
return quote(result);
}
var value = macro.invoke(code, eval_args);
return unpromise(resolve_promises(value), function ret(value) {
if (!value || value && value[__data__] || self_evaluated(value)) {
return value;
} else {
return unpromise(evaluate(value, eval_args), finalize);
try {
var value = macro.invoke(code, eval_args);
return unpromise(resolve_promises(value), function ret(value) {
if (!value || value && value[__data__] || self_evaluated(value)) {
return value;
} else {
return unpromise(evaluate(value, eval_args), finalize);
}
}, error => {
if (!(error instanceof IgnoreException)) {
throw error;
}
});
} catch (error) {
if (!(error instanceof IgnoreException)) {
throw error;
}
});
}
}

// -------------------------------------------------------------------------
function prepare_fn_args(fn, args) {
if (is_bound(fn) && !is_object_bound(fn) &&
Expand Down Expand Up @@ -10198,8 +10247,7 @@ class Continuation {
}
}
}
// -------------------------------------------------------------------------
const noop = () => {};

// -------------------------------------------------------------------------
function evaluate(code, { env, dynamic_env, use_dynamic, error = noop, ...rest } = {}) {
try {
Expand Down Expand Up @@ -10335,7 +10383,9 @@ function exec_collect(collect_callback) {
e.__code__.push(code.toString(true));
}
}
throw e;
if (!(e instanceof IgnoreException)) {
throw e;
}
}
});
results.push(collect_callback(code, await value));
Expand Down
115 changes: 77 additions & 38 deletions tests/core.scm
Original file line number Diff line number Diff line change
Expand Up @@ -281,14 +281,17 @@
(t.is result 10))))
(t.is (await p) 10))))

(test "core: quoted promise repr"
(test "core: quoted resolved promise repr"
(lambda (t)
(let ((resolve))
(define promise '>(new Promise (lambda (r) (set! resolve r))))
(t.is (repr promise) "#<js-promise (pending)>")
(resolve "xx")
(t.is (await promise) "xx")
(t.is (repr promise) "#<js-promise resolved (string)>"))
(t.is (repr promise) "#<js-promise resolved (string)>"))))

(test "core: quoted rejected promise repr"
(lambda (t)
(let ((reject))
(define promise '>(new Promise (lambda (_ r) (set! reject r))))
(t.is (repr promise) "#<js-promise (pending)>")
Expand All @@ -297,6 +300,30 @@
(t.is (repr promise) "#<js-promise (rejected)>")
(t.is (not (null? (promise.__reason__.message.match #/ZONK/))) true))))

(test "core: quoted promise + lexical scope"
(lambda (t)
(let ((x (await (let ((x 2))
(--> '>(Promise.resolve (let ((y 4))
(+ x y)))
(then (lambda (x)
(* x x))))))))
(t.is x 36))))

(test "core: resolving promises in quoted promise realm"
(lambda (t)
(t.is (await (let ((x 2))
(--> '>(let ((y (Promise.resolve 4)))
(+ x y))
(then (lambda (x)
(* x x))))))
36)))

(test "core: promise + let"
(lambda (t)
(let ((x (Promise.resolve 2))
(y (Promise.resolve 4)))
(t.is (* x y) (Promise.resolve 8)))))

(test "core: Promise.all on quoted promises"
(lambda (t)
(let ((expected #(10 20))
Expand Down Expand Up @@ -326,53 +353,58 @@

(test "core: try..catch"
(lambda (t)
(let ((x))
(t.is (try 10 (finally (set! x 10))) 10)
(t.is x 10))
(begin
(let ((x))
(t.is (try 10 (finally (set! x 10))) 10)
(t.is x 10))

(let ((x))
(t.is (try aa (catch (e) false) (finally (set! x 10))) false)
(t.is x 10))

(let ((x))
(t.is (try aa (catch (e) false) (finally (set! x 10))) false)
(t.is x 10))
(let ((x 10))
(t.is (to.throw (try 10 (finally (throw "error") (set! x 20)))) true)
(t.is x 10))

(t.is (to.throw (try bb (catch (e) (throw e)))) true)
(t.is (to.throw (try bb (catch (e) (throw e)))) true)

(let ((x))
(t.is (to.throw (try cc (finally (set! x 10)))) true)
(t.is x 10))
(let ((x))
(t.is (to.throw (try cc (finally (set! x 10)))) true)
(t.is x 10))

(let ((x))
(t.is (try (new Promise (lambda (r) (r 10))) (finally (set! x 10))) 10)
(t.is x 10))
(let ((x))
(t.is (try (new Promise (lambda (r) (r 10))) (finally (set! x 10))) 10)
(t.is x 10))

(let ((x))
(t.is (to.throw (try (Promise.reject 10) (catch (e) (set! x 10) (throw e)))) true)
(t.is x 10))
(let ((x))
(t.is (to.throw (try (Promise.reject 10) (catch (e) (set! x 10) (throw e)))) true)
(t.is x 10))

(t.is (try xx (catch (e) false)) false)
(t.is (try xx (catch (e) false)) false)

(let ((x))
(t.is (try (Promise.reject 10) (catch (e) e) (finally (set! x 10))) 10)
(t.is x 10))
(let ((x))
(t.is (try (Promise.reject 10) (catch (e) e) (finally (set! x 10))) 10)
(t.is x 10))

(t.is (try (Promise.reject 10) (catch (e) e)) 10)
(t.is (try (Promise.reject 10) (catch (e) e)) 10)

(t.is (to.throw (try (Promise.reject 10) (catch (e) (throw e)))) true)
(t.is (to.throw (try (Promise.reject 10) (catch (e) (throw e)))) true)

(let ((x))
(t.is (to.throw (try (Promise.reject 10) (finally (set! x 10))))true)
(t.is x 10))))
(let ((x))
(t.is (to.throw (try (Promise.reject 10) (finally (set! x 10)))) true)
(t.is x 10)))))

(test.failing "core: try..catch should stop execution"
(lambda (t)
(let ((result #f))
(try
(begin
(set! result 1)
(throw 'ZONK)
(set! result 2))
(catch (e)
(set! result 3)))
(t.is result 3))))
(test "core: try..catch should stop execution #163"
(lambda (t)
(let ((result #f))
(try
(begin
(set! result 1)
(throw 'ZONK)
(set! result 2))
(catch (e)
(set! result 3)))
(t.is result 3))))

(test "core: chain of promises"
(lambda (t)
Expand Down Expand Up @@ -469,6 +501,13 @@
(lambda (t)
(t.is (to.throw ((Promise.resolve 'list) 1 2 3)) true)))

(test "core: should catch quoted promise rejection"
(lambda (t)
(t.is (await (--> '>(Promise.reject 10)
(catch (lambda (e)
#t))))
#t)))

(test "core: should clone list"
(lambda (t)
(let* ((a '(1 2 3)) (b (clone a)))
Expand Down
22 changes: 11 additions & 11 deletions tests/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,16 @@ get_files().then(filenames => {
return Promise.all(filenames.map(function(file) {
return readFile(`tests/${file}`, 'utf8');
})).then(async function (files) {
await exec(`
(let-env lips.env.__parent__
(load "./dist/std.xcb")
(load "./tests/helpers/helpers.scm"))
(define test (require "ava"))
`);
return exec(files.join('\n\n'));
}).catch(e => {
console.error(e.message);
console.error(e.stack);
});
await exec(`
(let-env lips.env.__parent__
(load "./dist/std.xcb")
(load "./tests/helpers/helpers.scm"))
(define test (require "ava"))
`);
return exec(files.join('\n\n'));
});
}).catch(e => {
console.error(e.message);
console.error(e.stack);
});

0 comments on commit 0f01c6b

Please sign in to comment.