//: Structured programming
//:
//: Our jump recipes are quite inconvenient to use, so mu provides a
//: lightweight tool called 'transform_braces' to work in a slightly more
//: convenient format with nested braces:
//:
//:   {
//:     some instructions
//:     {
//:       more instructions
//:     }
//:   }
//:
//: Braces are just labels, they require no special parsing. The pseudo
//: recipes 'loop' and 'break' jump to just after the enclosing '{' and '}'
//: respectively.
//:
//: Conditional and unconditional 'loop' and 'break' should give us 80% of the
//: benefits of the control-flow primitives we're used to in other languages,
//: like 'if', 'while', 'for', etc.

:(scenarios transform)
:(scenario brace_conversion)
def main [
  {
    break
    1:number <- copy 0
  }
]
+transform: --- transform braces for recipe main
+transform: jump 1:offset
+transform: copy ...

:(before "End Instruction Modifying Transforms")
Transform.push_back(transform_braces);  // idempotent

:(code)
void transform_braces(const recipe_ordinal r) {
  const int OPEN = 0, CLOSE = 1;
  // use signed integer for step index because we'll be doing arithmetic on it
  list<pair<int/*OPEN/CLOSE*/, /*step*/int> > braces;
  trace(9991, "transform") << "--- transform braces for recipe " << get(Recipe, r).name << end();
//?   cerr << "--- transform braces for recipe " << get(Recipe, r).name << '\n';
  for (int index = 0; index < SIZE(get(Recipe, r).steps); ++index) {
    const instruction& inst = get(Recipe, r).steps.at(index);
    if (inst.label == "{") {
      trace(9993, "transform") << maybe(get(Recipe, r).name) << "push (open, " << index << ")" << end();
      braces.push_back(pair<int,int>(OPEN, index));
    }
    if (inst.label == "}") {
      trace(9993, "transform") << "push (close, " << index << ")" << end();
      braces.push_back(pair<int,int>(CLOSE, index));
    }
  }
  stack</*step*/int> open_braces;
  for (int index = 0; index < SIZE(get(Recipe, r).steps); ++index) {
    instruction& inst = get(Recipe, r).steps.at(index);
    if (inst.label == "{") {
      open_braces.push(index);
      continue;
    }
    if (inst.label == "}") {
      if (open_braces.empty()) {
        raise << "missing '{' in '" << get(Recipe, r).name << "'\n" << end();
        return;
      }
      open_braces.pop();
      continue;
    }
    if (inst.is_label) continue;
    if (inst.old_name != "loop"
         && inst.old_name != "loop-if"
         && inst.old_name != "loop-unless"
         && inst.old_name != "break"
         && inst.old_name != "break-if"
         && inst.old_name != "break-unless") {
      trace(9992, "transform") << inst.old_name << " ..." << end();
      continue;
    }
    // check for errors
    if (inst.old_name.find(
discard """
  output: '''
we
direct
generic
generic
'''
joinable: false
"""

import algorithm, sugar, sequtils, typetraits, asyncdispatch

block tconfusing_arrow:
  type Deck = object
    value: int

  proc sort(h: var seq[Deck]) =
    # works:
    h.sort(proc (x, y: Deck): auto =
      cmp(x.value, y.value))
    # fails:
    h.sort((x, y: Deck) => cmp(ord(x.value), ord(y.value)))

  var player: seq[Deck] = @[]
  player.sort()



block tdictdestruct:
  type
    TDict[TK, TV] = object
      k: TK
      v: TV
    PDict[TK, TV] = ref TDict[TK, TV]

  proc fakeNew[T](x: var ref T, destroy: proc (a: ref T) {.nimcall.}) =
    discard

  proc destroyDict[TK, TV](a: PDict[TK, TV]) =
      return
  proc newDict[TK, TV](a: TK, b: TV): PDict[TK, TV] =
      fakeNew(result, destroyDict[TK, TV])

  # Problem: destroyDict is not instantiated when newDict is instantiated!
  discard newDict("a", "b")



block tgenericdefaults:
  type
    TFoo[T, U, R = int] = object
      x: T
      y: U
      z: R

    TBar[T] = TFoo[T, array[4, T], T]

  var x1: TFoo[int, float]

  static:
    assert type(x1.x) is int
    assert type(x1.y) is float
    assert type(x1.z) is int

  var x2: TFoo[string, R = float, U = seq[int]]

  static:
    assert type(x2.x) is string
    assert type(x2.y) is seq[int]
    assert type(x2.z) is float

  var x3: TBar[float]

  static:
    assert type(x3.x) is float
    assert type(x3.y) is array[4, float]
    assert type(x3.z) is float



block tprop:
  type
    TProperty[T] = object of RootObj
      getProc: proc(property: TProperty[T]): T {.nimcall.}
      setProc: proc(property: TProperty[T], value: T) {.nimcall.}
      value: T

  proc newProperty[T](value: RootObj): TProperty[T] =
    result.getProc = proc (property: TProperty[T]) =
      return property.value



block trefs:
  type
    PA[T] = ref TA[T]
    TA[T] = object
      field: T
  var a: PA[string]
  new(a)
  a.field = "some string"

  proc someOther[T](len: string): seq[T] = discard
  proc someOther[T](len: int): seq[T] = echo "we"

  proc foo[T](x: T) =
    var s = someOther[T](34)
    #newSeq[T](34)

  foo 23

  when false:
    # Compiles unless you use var a: PA[string]
    type
      PA = ref TA
      TA[T] = object

    # Cannot instantiate:
    type
      TA[T] = object
        a: PA[T]
      PA[T] = ref TA[T]

    type
      PA[T] = ref TA[T]
      TA[T] = object



block tsharedcases:
  proc typeNameLen(x: typedesc): int {.compileTime.} =
    result = x.name.len
  macro selectType(a, b: typedesc): typedesc =
    result = a

  type
    Foo[T] = object
      data1: array[T.high, int]
      data2: array[typeNameLen(T), float]
      data3: array[0..T.typeNameLen, selectType(float, int)]
    MyEnum = enum A, B, C, D

  var f1: Foo[MyEnum]
  var f2: Foo[int8]

  doAssert high(f1.data1) == 2 # (D = 3) - 1 == 2
  doAssert high(f1.data2) == 5 # (MyEnum.len = 6) - 1 == 5

  doAssert high(f2.data1) == 126 # 127 - 1 == 126
  doAssert high(f2.data2) == 3 # int8.len - 1 == 3

  static:
    assert high(f1.data1) == ord(C)
    assert high(f1.data2) == 5 # length of MyEnum minus one, because we