dg icon indicating copy to clipboard operation
dg copied to clipboard

implementing case-of

Open maweki opened this issue 9 years ago • 2 comments

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?

maweki avatar Nov 14 '15 15:11 maweki

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.

pyos avatar Nov 14 '15 16:11 pyos

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.

pyos avatar Nov 14 '15 17:11 pyos