Current section

Files

Jump to
eyg_interpreter src eyg interpreter builtin.gleam
Raw

src/eyg/interpreter/builtin.gleam

import eyg/interpreter/break
import eyg/interpreter/cast
import eyg/interpreter/state
import eyg/interpreter/value as v
import eyg/ir/integer
import gleam/bit_array
import gleam/dict
import gleam/int
import gleam/list
import gleam/order
import gleam/result.{try}
import gleam/string
pub const equal = state.Arity2(do_equal)
fn do_equal(left, right, _meta, env, k) {
let value = case left == right {
True -> v.true()
False -> v.false()
}
Ok(#(state.V(value), env, k))
}
pub const fix = state.Arity1(do_fix)
fn do_fix(builder, meta, env, k) {
state.call(builder, v.Partial(v.Builtin("fixed"), [builder]), meta, env, k)
}
// fixed is not a builtin that is valid in expressions
// it is here so that a builder that only references it's self can be a value.
// technically its an arity 1 or 2 function.
pub const fixed = state.Arity2(do_fixed)
pub fn do_fixed(builder, arg, meta, env, k) {
state.call(
builder,
v.Partial(v.Builtin("fixed"), [builder]),
meta,
env,
state.Stack(state.CallWith(arg, env), meta, k),
)
}
pub const never = state.Arity1(do_never)
pub fn do_never(arg, _meta, _env, _k) {
Error(break.IncorrectTerm(expected: "Never", got: arg))
}
pub const int_compare = state.Arity2(do_int_compare)
fn do_int_compare(left, right, _meta, env, k) {
use left <- try(cast.as_integer(left))
use right <- try(cast.as_integer(right))
let return = case int.compare(left, right) {
order.Lt -> v.Tagged("Lt", v.unit())
order.Eq -> v.Tagged("Eq", v.unit())
order.Gt -> v.Tagged("Gt", v.unit())
}
Ok(#(state.V(return), env, k))
}
pub const add = state.Arity2(do_add)
fn do_add(left_value, right_value, _meta, env, k) {
use left <- try(cast.as_integer(left_value))
use right <- try(cast.as_integer(right_value))
let return = left + right
case integer.is_safe(return) {
True -> Ok(#(state.V(v.Integer(return)), env, k))
False -> Error(break.Unrepresentable("int_add", [left_value, right_value]))
}
}
pub const subtract = state.Arity2(do_subtract)
fn do_subtract(left_value, right_value, _meta, env, k) {
use left <- try(cast.as_integer(left_value))
use right <- try(cast.as_integer(right_value))
let return = left - right
case integer.is_safe(return) {
True -> Ok(#(state.V(v.Integer(return)), env, k))
False ->
Error(break.Unrepresentable("int_subtract", [left_value, right_value]))
}
}
pub const multiply = state.Arity2(do_multiply)
fn do_multiply(left_value, right_value, _meta, env, k) {
use left <- try(cast.as_integer(left_value))
use right <- try(cast.as_integer(right_value))
let return = left * right
case integer.is_safe(return) {
True -> Ok(#(state.V(v.Integer(return)), env, k))
False ->
Error(break.Unrepresentable("int_multiply", [left_value, right_value]))
}
}
pub const divide = state.Arity2(do_divide)
fn do_divide(left, right, _meta, env, k) {
use left <- try(cast.as_integer(left))
use right <- try(cast.as_integer(right))
let value = case right {
0 -> v.error(v.unit())
_ -> v.ok(v.Integer(left / right))
}
Ok(#(state.V(value), env, k))
}
pub const absolute = state.Arity1(do_absolute)
fn do_absolute(x, _meta, env, k) {
use x <- try(cast.as_integer(x))
Ok(#(state.V(v.Integer(int.absolute_value(x))), env, k))
}
pub const int_parse = state.Arity1(do_int_parse)
fn do_int_parse(raw_value, _meta, env, k) {
use raw <- try(cast.as_string(raw_value))
// A malformed string is a normal `Error` result, identical on every target.
// A well-formed number the target can't represent exactly breaks the run.
case int.parse(raw) {
Error(Nil) -> Ok(#(state.V(v.error(v.unit())), env, k))
Ok(i) ->
case integer.is_safe(i) {
True -> Ok(#(state.V(v.ok(v.Integer(i))), env, k))
False -> Error(break.Unrepresentable("int_parse", [raw_value]))
}
}
}
pub const int_to_string = state.Arity1(do_int_to_string)
fn do_int_to_string(x, _meta, env, k) {
use x <- try(cast.as_integer(x))
Ok(#(state.V(v.String(int.to_string(x))), env, k))
}
pub const string_append = state.Arity2(do_string_append)
fn do_string_append(left, right, _meta, env, k) {
use left <- try(cast.as_string(left))
use right <- try(cast.as_string(right))
Ok(#(state.V(v.String(string.append(left, right))), env, k))
}
pub const string_split = state.Arity2(do_string_split)
pub fn do_string_split(s, pattern, _meta, env, k) {
use s <- try(cast.as_string(s))
use pattern <- try(cast.as_string(pattern))
let assert [first, ..parts] = string.split(s, pattern)
let parts = v.LinkedList(list.map(parts, v.String))
let value =
v.Record(dict.from_list([#("head", v.String(first)), #("tail", parts)]))
Ok(#(state.V(value), env, k))
}
pub const string_split_once = state.Arity2(do_string_split_once)
pub fn do_string_split_once(s, pattern, _meta, env, k) {
use s <- try(cast.as_string(s))
use pattern <- try(cast.as_string(pattern))
// gleam_stdlib's string.split_once disagrees across targets on an empty
// pattern: JavaScript yields #("", s) while Erlang yields Error(Nil).
let split = case pattern {
"" -> Ok(#("", s))
_ -> string.split_once(s, pattern)
}
let value = case split {
Ok(#(pre, post)) -> {
let record =
v.Record(
dict.from_list([#("pre", v.String(pre)), #("post", v.String(post))]),
)
v.ok(record)
}
Error(Nil) -> v.error(v.unit())
}
Ok(#(state.V(value), env, k))
}
pub const string_replace = state.Arity3(do_string_replace)
pub fn do_string_replace(in, from, to, _meta, env, k) {
use in <- try(cast.as_string(in))
use from <- try(cast.as_string(from))
use to <- try(cast.as_string(to))
// gleam_stdlib's string.replace disagrees across targets on an empty `from`:
// JavaScript inserts `to` around every grapheme (and at both ends) while
// Erlang leaves the string untouched.
let replaced = case from, in {
"", "" -> to
"", _ -> to <> string.join(string.to_graphemes(in), to) <> to
_, _ -> string.replace(in, from, to)
}
Ok(#(state.V(v.String(replaced)), env, k))
}
pub const string_uppercase = state.Arity1(do_string_uppercase)
pub fn do_string_uppercase(value, _meta, env, k) {
use value <- try(cast.as_string(value))
Ok(#(state.V(v.String(string.uppercase(value))), env, k))
}
pub const string_lowercase = state.Arity1(do_string_lowercase)
pub fn do_string_lowercase(value, _meta, env, k) {
use value <- try(cast.as_string(value))
Ok(#(state.V(v.String(string.lowercase(value))), env, k))
}
pub const string_starts_with = state.Arity2(do_string_starts_with)
pub fn do_string_starts_with(value, t, _meta, env, k) {
use value <- try(cast.as_string(value))
use t <- try(cast.as_string(t))
Ok(#(state.V(bool(string.starts_with(value, t))), env, k))
}
pub const string_ends_with = state.Arity2(do_string_ends_with)
pub fn do_string_ends_with(value, t, _meta, env, k) {
use value <- try(cast.as_string(value))
use t <- try(cast.as_string(t))
Ok(#(state.V(bool(string.ends_with(value, t))), env, k))
}
fn bool(value) {
case value {
True -> v.true()
False -> v.false()
}
}
pub const string_length = state.Arity1(do_string_length)
pub fn do_string_length(value, _meta, env, k) {
use value <- try(cast.as_string(value))
Ok(#(state.V(v.Integer(string.length(value))), env, k))
}
pub const string_to_binary = state.Arity1(do_string_to_binary)
pub fn do_string_to_binary(in, _meta, env, k) {
use in <- try(cast.as_string(in))
Ok(#(state.V(v.Binary(bit_array.from_string(in))), env, k))
}
pub const string_from_binary = state.Arity1(do_string_from_binary)
pub fn do_string_from_binary(in, _meta, env, k) {
use in <- result.try(cast.as_binary(in))
let value = case bit_array.to_string(in) {
Ok(bytes) -> v.ok(v.String(bytes))
Error(Nil) -> v.error(v.unit())
}
Ok(#(state.V(value), env, k))
}
pub const list_pop = state.Arity1(do_list_pop)
fn do_list_pop(term, _meta, env, k) {
use elements <- result.try(cast.as_list(term))
let return = case elements {
[] -> v.error(v.unit())
[head, ..tail] ->
v.ok(
v.Record(
dict.from_list([#("head", head), #("tail", v.LinkedList(tail))]),
),
)
}
Ok(#(state.V(return), env, k))
}
pub const list_fold = state.Arity3(do_list_fold)
fn do_list_fold(list, state, func, meta, env, k) {
use elements <- try(cast.as_list(list))
case elements {
[] -> Ok(#(state.V(state), env, k))
[element, ..rest] -> {
state.call(
func,
element,
meta,
env,
state.Stack(
state.CallWith(state, env),
meta,
state.Stack(
state.Apply(
v.Partial(v.Builtin("list_fold"), [v.LinkedList(rest)]),
env,
),
meta,
state.Stack(state.CallWith(func, env), meta, k),
),
),
)
}
}
}
pub const binary_from_integers = state.Arity1(do_binary_from_integers)
pub fn do_binary_from_integers(term, _meta, env, k) {
use parts <- result.try(cast.as_list(term))
let content =
list.fold(list.reverse(parts), <<>>, fn(acc, el) {
let assert v.Integer(i) = el
<<i, acc:bits>>
})
Ok(#(state.V(v.Binary(content)), env, k))
}
pub const binary_size = state.Arity1(do_binary_size)
pub fn do_binary_size(term, _meta, env, k) {
use bytes <- try(cast.as_binary(term))
Ok(#(state.V(v.Integer(bit_array.byte_size(bytes))), env, k))
}
pub const binary_concat = state.Arity2(do_binary_concat)
pub fn do_binary_concat(left, right, _meta, env, k) {
use left <- try(cast.as_binary(left))
use right <- try(cast.as_binary(right))
Ok(#(state.V(v.Binary(bit_array.append(left, right))), env, k))
}
pub const binary_compare = state.Arity2(do_binary_compare)
pub fn do_binary_compare(left, right, _meta, env, k) {
use left <- try(cast.as_binary(left))
use right <- try(cast.as_binary(right))
let return = case bit_array.compare(left, right) {
order.Lt -> v.Tagged("Lt", v.unit())
order.Eq -> v.Tagged("Eq", v.unit())
order.Gt -> v.Tagged("Gt", v.unit())
}
Ok(#(state.V(return), env, k))
}
pub const binary_fold = state.Arity3(do_binary_fold)
fn do_binary_fold(bytes, state, func, meta, env, k) {
use bytes <- try(cast.as_binary(bytes))
case bytes {
<<>> -> Ok(#(state.V(state), env, k))
<<byte, rest:bytes>> -> {
state.call(
func,
v.Integer(byte),
meta,
env,
state.Stack(
state.CallWith(state, env),
meta,
state.Stack(
state.Apply(
v.Partial(v.Builtin("binary_fold"), [v.Binary(rest)]),
env,
),
meta,
state.Stack(state.CallWith(func, env), meta, k),
),
),
)
}
_ -> panic as "assume full bytes"
}
}
// This assumes only scope needs passing around
pub fn default(scope) {
state.Env(scope: scope, builtins: all())
}
fn all() {
dict.new()
|> dict.insert("equal", equal)
|> dict.insert("fix", fix)
|> dict.insert("fixed", fixed)
|> dict.insert("never", never)
// integer
|> dict.insert("int_compare", int_compare)
|> dict.insert("int_add", add)
|> dict.insert("int_subtract", subtract)
|> dict.insert("int_multiply", multiply)
|> dict.insert("int_divide", divide)
|> dict.insert("int_absolute", absolute)
|> dict.insert("int_parse", int_parse)
|> dict.insert("int_to_string", int_to_string)
// String
|> dict.insert("string_append", string_append)
|> dict.insert("string_split", string_split)
|> dict.insert("string_split_once", string_split_once)
|> dict.insert("string_replace", string_replace)
|> dict.insert("string_uppercase", string_uppercase)
|> dict.insert("string_lowercase", string_lowercase)
|> dict.insert("string_starts_with", string_starts_with)
|> dict.insert("string_ends_with", string_ends_with)
|> dict.insert("string_length", string_length)
|> dict.insert("string_to_binary", string_to_binary)
|> dict.insert("string_from_binary", string_from_binary)
// Binary
|> dict.insert("binary_from_integers", binary_from_integers)
|> dict.insert("binary_size", binary_size)
|> dict.insert("binary_concat", binary_concat)
|> dict.insert("binary_compare", binary_compare)
|> dict.insert("binary_fold", binary_fold)
// List
|> dict.insert("list_pop", list_pop)
|> dict.insert("list_fold", list_fold)
}