3.5. kink/CORE

kink/CORE mod provides funs which are commonly used by most programs.

Funs of CORE mod are included in env vals. Thus most programs do not have to require CORE mod explicitly.

Example:

:CORE.require_from('kink/')
stdout.print_line(CORE.true.repr) # => true
stdout.print_line(true.repr)      # => true

3.5.1. CORE.nada

nada returns the nada val, which is identical to the result of the expression “()”.

3.5.2. CORE.true

CORE.true returns the true bool val.

3.5.3. CORE.false

CORE.false returns the false bool val.

3.5.4. CORE.id(Val)

CORE.id returns Val.

Example:

stdout.print_line(id(id(id('foo')))) # => foo

3.5.5. CORE.new_val(Sym1 Val1 Sym2 Val2 ,,,)

CORE.new_val makes a new val, assigning vals to corresponding vars specified by the syms.

The number of args can be 0, 2, 4, or an arbitrary even number.

Sym1, Sym2 ,,, must be strs.

If the same sym is specified multiple times, the last val is assigned to the var.

Example:

:V <- new_val(
  'bark' { 'Bow' }
  'howl' { 'Wow' }
)
stdout.print_line(V.bark) # => Bow
stdout.print_line(V.howl) # => Wow

The program above is equivalent to the following one.

:V <- new_val
V:bark <- { 'Bow' }
V:howl <- { 'Wow' }
stdout.print_line(V.bark) # => Bow
stdout.print_line(V.howl) # => Wow

It is a idiom to store sym-val pairs as a vec, then spread it when calling new_val fun. Such an array is called a trait. Example:

# make a rational val
:new_rational <- {(:Numer :Denom)
  new_val(
    # reuse Rational_trait, which is common to all rational vals
    ... Rational_trait

    # Numer and Denom differ for each rational val
    'Numer' Numer
    'Denom' Denom
  )
}

# trait for rational vals
:Rational_trait <- [
  'numer' {[:R]() R.Numer }
  'denom' {[:R]() R.Denom }
  'repr' {[:R]()
    '{}/{}'.format(R.numer R.denom)
  }
]

stdout.print_line(new_rational(2 3).repr)  # => 2/3
stdout.print_line(new_rational(4 5).repr)  # => 4/5

3.5.6. CORE.require(Mod_name)

CORE.require returns the mod specified by Mod_name, optionally initializing the mod.

If the specified mod does not exist, CORE.require raises an exception.

Example:

:FLAT_SET <- require('kink/container/FLAT_SET')
stdout.print_line(FLAT_SET.of('foo' 'bar' 'baz').repr) # => Flat_set("bar" "baz" "foo")

3.5.7. CORE.reset(Kont_tag $thunk)

CORE.reset delimits the continuation with the specified Kont_tag.

Using the terminology defined in Language specification → Execution chapter, CORE.reset pushes a delimitor frame with Kont_tag as the continuation tag string, then invokes $thunk with no arguments.

Using reset/shift pair, the continuation can be extracted as a fun. Example:

:paren <- reset('Paren'){
  '(' + shift('Paren'){(:k) $k } + ')'
}

stdout.print_line(paren('foo')) # => (foo)
stdout.print_line(paren('bar')) # => (bar)

If CORE.shift is not called, CORE.reset returns the result of $thunk.

:Result <- reset('Not_used'){ 42 }
stdout.print_line(Result.repr) # => 42

Non-local exit can also be implemented using reset/shift pair. Example:

# jump out to the invoation of trap, with the result Val
:jump <- {(:Val)
  shift('Trap'){ Val }
}

# trap the jump
:trap <- {(:thunk)
  reset('Trap'){
    thunk
  }
}

:callee <- {
  jump('aborted')
  stdout.print_line('must not reach here')
}

:caller <- {
  trap{
    callee
    stdout.print_line('must not reach here')
  }
}

stdout.print_line(caller) # => aborted

Caution:

In most cases, use kink/CONTROL.escape for non-local exit, in order to invoke cleanup thunks properly.

3.5.8. CORE.shift(Kont_tag $abort)

CORE.shift takes the continuation delimited by CORE.reset.

Using the terminology defined in Language specification → Execution chapter:

First, CORE.shift performs test-delimitor-existence operation with Kont_tag as the continuation tag string. If no delimitor frame with the continuation tag exists, an exception is raised.

Second, if a delimitor frame with the continuation tag exists, CORE.shift performs extract-slice operation, and makes a continuation function with the slice.

Third, CORE.shift performs remove-until-delimitor operation with the continuation tag.

Finally, CORE.shift invokes $abort with the continuation function as the argument.

3.5.9. CORE.can_shift?(Kont_tag)

CORE.can_shift? returns whether CORE.shift can be invoked with the specified Kont_tag.

Using the terminology defined in Language specification → Execution chapter, CORE.can_shift? performs test-delimitor-existence operation with Kont_tag as the continuation tag string. If a delimitor frame with the continuation tag exists, it continues with true value. If no delimitor frame with the continuation tag exists, it continues with false value.

3.5.10. CORE.dyn_wind(Dyn_tag Val $thunk)

CORE.dyn_wind calls $thunk binding Val as a dyn val for Dyn_tag.

Using the terminology defined in Language specification → Execution chapter, CORE.dyn_wind pushes a dyn frame with Dyn_tag as the dyn tag string, and Dyn_val as the dyn value. Then, CORE.dyn_wind invokes $thunk with no arguments.

Dyn_tag must be a str.

Using dyn_wind/dyn_vec pair, thread-local variables can be emulated. Example:

:do_transaction <- {(:Tx_name :thunk)
  dyn_wind('Tx' Tx_name){
    thunk
  }
}
:current_transactions <- {()
  dyn_vec('Tx')
}

do_transaction('Top'){
  do_transaction('Nested'){
    stdout.print_line(current_transactions.repr) # => ["Top" "Nested"]
  }
}

3.5.11. CORE.dyn_vec(Dyn_tag)

CORE.dyn_vec returns a vec of dyn vals bound with Dyn_tag.

Using the terminology defined in Language specification → Execution chapter, CORE.dyn_vec performs extract-dyn operation with Dyn_tag as the dyn tag string. Then, CORE.dyn_vec continues with the constructed vector of dyn values.

3.5.12. CORE.traces

CORE.traces returns a vec of traces in the execution stack, from the bottom to the top.

3.5.13. CORE.raise(Msg)

CORE.raise raises an exception with Msg as the message, and the current traces as the traces.

Msg must be a str.

CORE.raise can be roughly defined in the next pseudo Kink code.

:raise <- {(:Msg)
  reraise(Msg traces)
}

3.5.14. CORE.reraise(Msg Traces)

CORE.reraise raise an exception with Msg as the message, and Traces as the traces.

Msg must be a str.

Traces must be a vec of traces.

CORE.reraise can be roughly defined in the next pseudo Kink code.

:EVALUATOR.require_from('kink/')

:reraise <- {(:Msg :Traces)
  can_shift?('kink/CONTROL-try').if_else(
    { :Candidate_cleanups = dyn_vec('kink/CONTROL-cleanup')
      shift('kink/CONTROL-try'){
        :Base_cleanup_count = dyn_vec('kink/CONTROL-cleanup').size
        :Cleanups = Candidate_cleanups.drop_front(Base_cleanup_count)
        Cleanups.reverse.each{(:cleanup)
          EVALUATOR.run({ cleanup } {} {})
        }
        {(:on_raised)
          on_raised(Msg Traces)
        }
      }
    }
    { :Cleanups = dyn_vec('kink/CONTROL-cleanup')
      Cleanups.reverse.each{(:cleanup)
        EVALUATOR.run({ cleanup } {} {})
      }
      __terminate_failure__(Msg Traces.dup)
    }
  )
}

Where __terminate_failure__ is a function to perform terminate-failure operation defined in Language specification → Execution chapter.

3.5.15. CORE.branch($cond1 $body1 $cond2 $body2 ,,,)

CORE.branch does a multiway branch.

First, CORE.branch calls $cond1, $cond2, ,,, in the order of occurrence with no args, till one of the cond thunks returns true. If no cond thunk returns true, CORE.branch raises an exception.

Second, CORE.branch tail-calls the body thunk corresponding to the cond thunk which returned true.

The number of args can be 0, 2, 4, or an arbitrary even number.

$cond1, $cond2, ,,, must take no args, and return true or false.

$body1, $body2, ,,, must take no args.

Example:

:Sorted_nums <- [1 2 3 5 8 11 19 30 49 89]

# returns whether Sorted_nums contains N, by binary search algorithm
:contain? <- {(:N)

  # Bool.if_else and CORE.branch tail-calls a body thunk.
  # Thus the loop does not cause a stack overflow
  # even when Sorted_nums contains a large number of elements.
  :in_range? <- {(:From :To)
    (From < To).if_else(
      { :Ind = (From + To) // 2
        :T = Sorted_nums.get(Ind)
        branch(
          { T < N } { in_range?(Ind + 1 To) }
          { T > N } { in_range?(From Ind) }
          $true { true }
        )
      }
      { false }
    )
  }
  in_range?(0 Sorted_nums.size)
}

stdout.print_line(contain?(30).repr) # => true
stdout.print_line(contain?(42).repr) # => false

If you do a two way branch, Bool.if_else may be a better choice.

If you do a one way conditional execution, Bool.if_true may be a better choise.

3.5.16. CORE.stdin

CORE.stdin returns a scanner+input for the standard input of the runtime system.

When the stdin scanner+input is used as a scanner, it decodes the byte stream using the runtime default encoding (kink/RUNTIME.encoding_name).

Example: read all from the stdin then dump it to the stdout

:Bin <- stdin.read_all_bin
stdout.write_bin(Bin)

Example: scan lines from the stdin

:loop <- {(:N)
  :Line = stdin.scan_line
  Line.nonempty?.if_true{
    stdout.print("{}\t{}".format(N Line))
    loop(N + 1)
  }
}
loop(1)

Example: scan lines from the stdin decoding the byte stream as UTF-8

:SCANNER.require_from('kink/io/')
:Scanner <- SCANNER.wrap(stdin 'UTF-8')
:loop <- {(:N)
  :Line = Scanner.scan_line
  Line.nonempty?.if_true{
    stdout.print("{}\t{}".format(N Line))
    loop(N + 1)
  }
}
loop(1)

3.5.17. CORE.stdout

CORE.stdout returns a printer+output for the standard output of the runtime system.

When the stdout printer+output is used as a printer, it terminates lines using the runtime default newline str (kink/RUNTIME.newline), and encodes the str using the runtime default encoding (kink/RUNTIME.encoding_name).

Example: writes bin to the stdout

:BIN.require_from('kink/')
stdout.write_bin(BIN.of(0x66 0x6f 0x6f 0x62 0x61 0x72 0x0a)) # => foobar

Example: prints a line to the stdout

stdout.print_line('foobar') # => foobar

Example: prints a line using UTF-8 and LF

:PRINTER.require_from('kink/io/')
:Printer <- PRINTER.wrap(stdout 'UTF-8' "\n")
Printer.print_line('foobar') # => foobar

3.5.18. CORE.stderr

CORE.stderr returns a printer+output for the standard error output of the runtime system.

When the stderr printer+output is used as a printer, it terminates lines using the runtime default newline str (kink/RUNTIME.newline), and encodes the str using the runtime default encoding (kink/RUNTIME.encoding_name).

3.5.19. CORE.exit(Exit_status)

CORE.exit terminates the process running the runtime system with the Exit_status.

Exit_status must be an int num in the range [-2147483648, 2147483647].

3.5.20. type val

All vals contain the following methods.

V.val_id

V.val_id returns an int num which identifies V.

Invocations of the method for a same val always return the same num. Invocations of the method for different vals always return different num. Therefore, identity of the val can be tested using the result of the method.

Example:

:test_identity <- {(:X :Y)
  (X.val_id == Y.val_id).if_else(
    { stdout.print_line('same') }
    { stdout.print_line('different') }
  )
}
:V <- new_val
test_identity(V V)        # => same
test_identity(V new_val)  # => different

V.var_syms

V.var_syms returns the set of syms of the vars of V.

Example:

:V <- new_val('Data' 42 'fun' {})
stdout.print_line(V.var_syms.repr) # => Flat_set("Data" "fun" "repr" "val_id" "var_syms")

V.repr

V.repr returns the str representation of V, used for debugging purpose.

This method is expected to be redefined for each type of vals.

Example:

stdout.print_line(new_val.repr) # => Val(val_id=5)
stdout.print_line('foo'.repr)   # => "foo"
stdout.print_line(42.repr)      # => 42