dg
dg copied to clipboard
implementing case-of
Looking at this example
factorial = n -> if
n < 0 => None
n < 2 => 1
otherwise => n * factorial (n - 1)
we see that n is there a little too often. I think one could add a case-of like this
factorial = n -> case n of
(< 0) => None
(< 2) => 1
otherwise => n * factorial (n - 1)
where this would compile to
factorial = n -> if
(< 0) n => None
(< 2) n => 1
(otherwise) n => n * factorial (n - 1)
Otherwise would need to be changed to (const True
) instead of True
which is a function which still evaluates to true-ish which should be no issue for existing code.
I think I can implement this myself, I just need a few hints/pointers.
As I see it, I need to redefine otherwise
(runtime.dg). Add case
to FLAGS
and case
and of
to STRENGTH
(both ast.dg) and add the case
to both symscan
and prefix
in compiler.dg. Right?
I don't know if it's possible to make case x of
work with the current parser. (It's really bad, isn't it? Not like you'd expect much from a glorified infix-to-prefix converter, though.) Either x of
would be parsed as a function call, or of
forced to have a different meaning absolutely everywhere by making it an infix operator, and I'm sure that would break something. (BTW, adding a name that is not infix to STRENGTH
is pointless. It is only consulted to resolve ambiguity in infix expressions. Alphanumeric names are not infix operators unless you add them to INVERT
. Punctuation, on the other hand, is infix unless mentioned in the same array.)
I'd recommend dropping the of
:
factorial = n -> case n
(< 0) => None
(< 2) => 1
otherwise => n * factorial (n - 1)
Then with no changes to the parser at all the above code would parse as you'd expect: the body of factorial
would be a call to (a compiler-defined function) case
with arguments n
, (< 0) => None
, (< 2) => 1
, and otherwise => n * factorial (n - 1)
.
There is, however, a problem: what if n
is replaced by a more complex expression? Since case
is parsed as a normal function call, the space between case
and its first argument would have higher precedence than most operators within that argument:
>>> dg.parse 'case n + 2\n x\n y'
((case n)+(2 x y))
>>> # expected:
>>> dg.parse 'case (n + 2)\n x\n y'
(case (n+2) x y)
This can be fixed by adding case
to FLAGS
, yes. If you assign it the same mode as if
, it will consume all tokens until EOL (the lowest-priority operator) and combine them into a single expression:
>>> dg.FLAGS !! 'case' = dg.FLAGS !! 'if'
>>> dg.parse 'case n + 2\n x\n y'
(case (n+2) x y)
Although I'm not sure this won't break the compiler. (Maybe it uses a variable named case
somewhere. I dunno. Oh, and check this out: I'm changing the language right in its own REPL! Whatever commands you enter after modifying the dg
module will be parsed and compiled using the new rules. It's a feature.)
Anyway, adding case
to symscan
should be unnecessary since it creates new variables if and only if any of its arguments do. This is the default behavior of the symbol scanner; there's no need to override it. And if you want to replace otherwise
with a function, you'd also have to remove it from the list of compile-time constants in emitter.dg
-- it is replaced with True
at compile time to allow CPython's optimizer to perform some constant folding.
One more thing: it's not necessary to wrap (< 0)
in parentheses when it's an LHS of =>
. =>
has a really low precedence. Not as low as \n
, but still not high enough to capture <
's RHS.