tategakibunko/jingoo

{% switch %} statement

sagotch opened this issue · 13 comments

Pattern matching would be really useful in order to avoid long series of {% if foo == ... %} with foo being the same in each elif case. I plan to add a switch statement to jingoo.

{% switch foo.bar %}
  {% case "foo" %}{{ 1 }}
  {% case "bar" %}{{ 2 }}
  {% case baz case toto %}{{ 3 }}
  {% default %}{{ -1 }}
{% endswitch %}

Also, it would be a more efficient way to compare values (if the value to compare needs some calculation for instead, you would need to use a set or every elif would be a waste of ressources.)

Any idea/objection?

EDIT:

{% case baz case toto %}

Would be a problem for the parser if you want to allow 'no statements' as right hand part of the case

{% switch foo.bar %}
  {% case "foo" %}
  {% case "bar" %}{{ 2 }}
{% endswitch %}

Would be the same as

{% switch foo.bar %}
  {% case "foo" case "bar" %}{{ 2 }}
{% endswitch %}

Which is obviously not what is wanted.

Hmm, in my rough understanding, statement structure will be like bellow?

SwitchStatement of expr * (tvalue, ast) list * ast option

and if switch case is like

switch(expr){
  case "foo":
  case "bar": ast1
  case 100: ast2
  default: ast3
}

it becomes

SwitchStatement(
  expr, [
    (Tstr "foo", ast1); (* case label "foo" *)
    (Tstr "bar", ast1); (* case label "bar" *)
    (Tint 100, ast2);   (* case label 100 *)
], Some ast3) (* default *)

then expr is evaluated only once(unlike if else statement), and matched case is searched by

let value = value_of_expr env ctx expr in
if jg_eq_eq value (Tstr "foo") then ast1
else if jg_eq_eq value (Tint 100) then ast2
else match ast3 with Some ast3 -> ast3 | None -> []

Both expr value and label value of case are tvalue, then we can compare them by using jg_eq_eq.

Is it the "pattern matching" you said?

See geneanet@1d220ad for a first draft. It does not handle multiple case evaluating to same value yet.

Thanks for reference, I've read it, compact and beautiful implementation!

With geneanet@c026c28

You can use {% case foo || bar || baz %}. What do you think about this syntax?

After reading code, I can understand the meaning easily, but in first look, foo || bar || baz part seems like boolean value?

if ((foo || bar || baz) === true){ ... }

But it's my own feeling...

I should have written

{% switch x %}
  {% case "foo" || "bar" || "baz" %}{{ ... }}
  {% default %} {{ ... }}
{% endswitch %}

Are case values allowed for direct value only?

In current impl, it seems it can be variable.

SwitchStatement of expression * (expression list * ast) list

If only for direct value, this should be like this?

SwitchStatement of expression * (tvalue list * ast) list

But if it's only allowed for direct values, {% case "foo" || "bar" || "baz" %} seems short and good syntax.

I am actually using it with variable in my code.

{%- set T_ROOT = 0 -%}
...
{%- set T_WITNESS = 7 -%}
{%- set T_GODPARENT = 8 -%}

...

  {%- switch r -%}
    {% case T_ROOT || T_WITNESS || T_GODPARENT %}{{ ... }}
    {%- default -%}{{... }}
  {%- endswitch -%}

Would you say that {% case "foo" case "bar" case "baz" %} would be a better/good choice. Or something else?

What I worry about is confliction like this,

{% set a = false %}
{% set b = true %}
{% set c = false %}

{# good! #}
{% case a || b || c %} {{ ast }} -> Case([a,b,c], {{ ast }})

{# oops! #}
{% case a || b || c %} {{ ast }} -> {% case (a || b || c) %}{{ast}} -> Case([b], {{ast}})

If this kind of reducing never happen, I'm ok, but I've not read all current parser yet, so I can't judge.

What happens here is that it is parsed as:

{% case a || b || c %} {{ ast }} -> {% case (a || b || c) %}{{ast}}

But parsed output is processed (in the parser):

    let rec extract = function
      | OrOpExpr (e1, e2) -> extract e1 @ extract e2
      | e -> [e] in
    SwitchStatement ( e
                    , List.fold_right
                        (fun (a, b) acc -> ([a], b) :: acc) (cases)
                        (match default with None -> [] | Some stmts -> [ ([], stmts) ]))

Thanks for reply. OrOpExpr is not evaluated before building SwitchStatement, and always generates list by extract, then I'm OK.

EDIT: In this argument, I totally forgot about that expr isn't evaluated before statement is evaluated...

Apparently {% case "a" case "b" %} is hard to type, I agree with you.

Edit: I miss pushed the close issue button... but if more points are remain, re-open please.