-
Notifications
You must be signed in to change notification settings - Fork 65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mini-LISP - unification of errors in C and JS implementations #426
Comments
This means in particular, very brief, and not so informational messages; no worry, eventually someone will do the bells and whistles. Not for now please. |
Ok
…On Wed, 28 Jul 2021 at 17:59, Yossi Gil ***@***.***> wrote:
This means in particular, very brief, and not so informational messages;
no worry, eventually someone will do the bells and whistles. Not for now
please.
—
You are receiving this because you were assigned.
Reply to this email directly, view it on GitHub
<#426 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/APCETIGFO3VEFYYTCL4YHPLT2ALNZANCNFSM5BD7KAVQ>
.
|
Mini-LISP C project, file S.h, line 182: inline bool die(S s) { throw BUG.cons(s); } Shouldn't it be "s.cons(BUG)", since "BUG" is the kind? This is the convention for all other error kinds. |
Mini-LISP C project, file eval.cc, lines 50-57: S only(S s) {
s.pair() || die(s);
s.cdr().cdr().t() && s.error(REDUNDANT).t();
S a = evaluate_list(s.cdr());
a.null() && s.error(MISSING).t();
a.atom() && s.error(INVALID).t();
return a.car();
} This function seems wrong to me in a few levels:
a.null() && s.error(MISSING).t();
a.atom() && s.error(INVALID).t(); are redundant - |
Mini-LISP C project, file eval.cc, lines 59-74: void checkNumberOfArgs(S s) {
S f = s.car();
if (f.eq(QUOTE) || f.eq(CAR) || f.eq(CDR) || // 1 Args:
f.eq(ATOM) || f.eq(NULL) || f.eq(EVAL)) {
s.more2() && s.error(REDUNDANT).t();
s.less2() && s.error(MISSING).t();
}
if (f.eq(CONS) || f.eq(SET) || f.eq(EQ) || f.eq(LAMBDA) || f.eq(NLAMBDA)) { // 2 Args:
s.more3() && s.error(REDUNDANT).t();
s.less3() && s.error(MISSING).t();
}
if (f.eq(NDEFUN) || f.eq(DEFUN)) { // 3 Args:
s.more4() && s.error(REDUNDANT).t();
s.less4() && s.error(MISSING).t();
}
} This function doesn't make sure that |
This combined behaviors of the S evaluate_atomic_function(S s) { M(s);
checkNumberOfArgs(s);
const S f = s.car();
// Atomic functions:
if (f.eq(CAR)) return only(s).car();
if (f.eq(CONS)) return s.$2$().eval().cons(s.$3$().eval());
if (f.eq(SET)) return set(s.$2$().eval(), s.$3$().eval());
if (f.eq(EQ)) return s.$2$().eval().eq(s.$3$().eval()) ? T : NIL;
if (f.eq(COND)) return evaluate_cond(s.cdr());
if (f.eq(CDR)) return only(s).cdr();
if (f.eq(ATOM)) return only(s).atom() ? T : NIL;
if (f.eq(EVAL)) return only(s).eval();
if (f.eq(ERROR)) return s.error(s.cdr().eval());
if (f.eq(NULL)) return only(s).null() ? T : NIL;
if (f.eq(QUOTE)) return s.cdr().car();
if (f.eq(NLAMBDA)) return list(NLAMBDA, s.$2$(), s.$3$());
if (f.eq(LAMBDA)) return list(LAMBDA, s.$2$(), s.$3$());
if (f.eq(NDEFUN)) {
ndefun(s.$2$(), s.$3$(), s.$4$());
return s.$2$();
}
if (f.eq(DEFUN)) {
defun(s.$2$(), s.$3$(), s.$4$());
return s.$2$();
}
return bug(s);
} As you can see, the single arg atomics are called with I'm referring you to this problems because I don't want to blindly copy what seems to me as an odd behavior, without making sure this is what you intended. |
What do you say to this? |
@OfirMarkowitz1 You can see also here: (defun evaluate−atomic (S−expression a−list)
(apply−atomic (car S−expression) (cdr S−expression) a−list)) That |
@DorYeheskel @yossigil And in any case, the if (f.eq(CAR)) return s.$2$().eval(); or if (f.eq(CAR)) return evaluate_list(s.cdr()).car(); |
I will let you guys sort it out a little... |
@DorYeheskel |
In the function if (f.eq(ERROR)) return s.error(s.cdr().eval()); This isn't working properly since EXPECT_EXCEPTION(s.eval(), list(b_2, x.q()), INVALID); I think the correct behavior is throwing an S expression containing the actuals passed to error, after their evaluation: if (f.eq(ERROR)) return s.error(evaluate_list(s.cdr())); I guess that's what was meant to be written but eval was called instead of evaluate_list. |
I've resolved issue #433, e.g., finished the error handling in JS. Perhaps I will need to make very few changes in order to close the current issue. I'm waiting for @DorYeheskel and @yossigil final take on this, to corroborate my work. Same with #427 |
@DorYeheskel are you sure that we do not have pairs in the implementation, I remember vividly supporting these in my parser. |
@OfirMarkowitz1 About pairs, see the following examples, using the mini-lisp interpreter: Pairs: As you can see, (a.b) is a pair in a list (as written here too) and thus If pair was not supported, then |
@DorYeheskel
A.B is not a pair, but rather (A.B). The first line makes it clear.
Also, A.B shouldn't be an atom, but rather rejected by the parser, see @yossigil remark from #439:
In both cases, the fact that the tests don't reflect this behavior means the tests are wrong, not the other way around.
There should be an additional "token" character, ';', which should be used only for line comments, so 7 reserved characters in total. This character should be added in Tokenizer.h, in line 6: |
Seems like an error in the book. Should be fixed. |
Comments are not tokens; never... |
I still need to unify these errors. Necessary error example - fake lambda tag: ('(fake_lambda_tag (x) (car x)) '[a.b]) This should be an error (and is, both in C and JS implementations), because it doesn't make sense for a wrong tag to act like lambda/nlambda, i.e., have a default behavior. An existing error example, which I'm not sure is necessary: ((lambda (x) (car x)) '[a.b] 'c) Currently, this raises a "REDUNDANT" error, but we can decide just to ignore the second variable. I think it's simpler. Another existing error example: ((lambda (x y) (cons x y)) 'a) Currently, this raises a "MISSING" error, but I think it also doesn't have to be an error because we can implement currying instead, i.e., this expression evaluation can return I think a discussion like this would be much more fruitful than for me to blindly copy the C implementation errors. Also, I think it would make a nice addition to the book, which currently contains only some of the present C implementation errors. |
I think there are the following errors:
This is all I could think of. |
If it boils down to a different error, this means there shouldn't be a REDUNDANT and MISSING error when applying lambda or primitive, and there are: S bind(S formals, S actuals, S list) {
if (!formals.null() && actuals.null()) return formals.error(MISSING);
if (formals.null() && !actuals.null()) return actuals.error(REDUNDANT);
if (formals.null()) return list;
return formals.car().cons(actuals.car()).cons(bind(formals.cdr(), actuals.cdr(), list));
} Before applying primitive, in eval.cc: void checkNumberOfArgs(S s) {
S f = s.car();
push(ARGUMENT, s.cdr());
if (f.eq(QUOTE) || f.eq(CAR) || f.eq(CDR) || // 1 Args:
f.eq(ATOM) || f.eq(NULL) || f.eq(EVAL)) {
s.more2() && s.error(REDUNDANT).t();
s.less2() && s.error(MISSING).t();
}
if (f.eq(CONS) || f.eq(SET) || f.eq(EQ) || f.eq(LAMBDA) || f.eq(NLAMBDA)) { // 2 Args:
s.more3() && s.error(REDUNDANT).t();
s.less3() && s.error(MISSING).t();
}
if (f.eq(NDEFUN) || f.eq(DEFUN)) { // 3 Args:
s.more4() && s.error(REDUNDANT).t();
s.less4() && s.error(MISSING).t();
}
remove_element(ARGUMENT);
}
S apply(S f, S args) {
D(f,args);
f.n3() || f.cons(args).error(INVALID).t();
D(f.$1$(),f.$2$(),f.$3$(),args,f.$1$().eq(NLAMBDA));
push(ARGUMENT, args);
const auto actuals = f.$1$().eq(NLAMBDA)? args : f.$1$().eq(LAMBDA) ? evaluate_list(args) : f.cons(args).error(INVALID);
remove_element(ARGUMENT);
alist() = bind(f.$2$(), actuals, alist());
push(ARGUMENT, actuals);
const auto result = f.$3$().eval();
remove_element(ARGUMENT);
remove_local_binding(f.$2$());
return result;
} There's a check that the S expression is a list of length 3, and that the tag is valid. There's no check that the formals are a valid list.
FUN(S, evaluate_cond, S) IS(
_.null() ? NIL:
_.car().atom() ? _.car().error(COND):
_.car().car().eval().t() ? _.car().cdr().car().eval():
$$(_.cdr()) ;
) It can easily be boiled down to CAR error.
if (f.eq(ERROR)) {
if (! s.less2()) {
args_1 = eval_argument(s.$2$());
}
push(ARGUMENT, args_1);
return s.error(args_1); // throw
}
S eval(S s) {
D(s);
inc_eval_counter();
S res = NIL;
D(s);
if (s.atom()) {
res = lookup(s);
D(s, res);
} else {
D(s, s.car(), s.cdr());
if (atomic(s.car())) {
D(s.car());
push(RESCUE, s.car());
res = evaluate_atomic_function(s);
remove_element(RESCUE);
D(s, res);
} else {
S f_name = s.car();
S f_body = s.car().eval();
push(RESCUE, f_name);
res = apply_defined_function(f_body, s.cdr());
remove_element(RESCUE);
D(s, res);
}
}
D(s,res);
dec_eval_counter();
if (get_eval_counter() == 0) reset_set_counter();
return res;
} This means that |
This boils down to the evaluation order: with gnu common lisp, we obtain:
which means that the evaluation algorithm in CL first evaluates all arguments; it then binds them and determines whether their number is correct. Check out also |
No description provided.
The text was updated successfully, but these errors were encountered: