From d309ea9a59fe8226fa42dd71b02688d23a4c6fb2 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Wed, 1 Feb 2023 00:11:47 +0000 Subject: [PATCH 01/30] v: step 0 --- Makefile.impls | 15 +++++++++------ impls/.gitignore | 1 + impls/v/run | 2 ++ impls/v/step0_repl.v | 24 ++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 6 deletions(-) create mode 100755 impls/v/run create mode 100644 impls/v/step0_repl.v diff --git a/Makefile.impls b/Makefile.impls index 6ac35b23e5..1bd1b3f58a 100644 --- a/Makefile.impls +++ b/Makefile.impls @@ -34,12 +34,14 @@ wasm_MODE = wasmtime # Implementation specific settings # -IMPLS = ada ada.2 awk bash basic bbc-basic c c.2 chuck clojure coffee common-lisp cpp crystal cs d dart \ - elisp elixir elm erlang es6 factor fantom fennel forth fsharp go groovy gnu-smalltalk \ - guile haskell haxe hy io janet java java-truffle js jq julia kotlin livescript logo lua make mal \ - matlab miniMAL nasm nim objc objpascal ocaml perl perl6 php picolisp pike plpgsql \ - plsql powershell prolog ps purs python python.2 r racket rexx rpython ruby ruby.2 rust scala scheme skew sml \ - swift swift3 swift4 swift5 tcl ts vala vb vhdl vimscript wasm wren yorick xslt zig +IMPLS = ada ada.2 awk bash basic bbc-basic c c.2 chuck clojure coffee \ + common-lisp cpp crystal cs d dart elisp elixir elm erlang es6 factor \ + fantom fennel forth fsharp go groovy gnu-smalltalk guile haskell haxe hy \ + io janet java java-truffle js jq julia kotlin livescript logo lua make mal \ + matlab miniMAL nasm nim objc objpascal ocaml perl perl6 php picolisp pike \ + plpgsql plsql powershell prolog ps purs python python.2 r racket rexx \ + rpython ruby ruby.2 rust scala scheme skew sml swift swift3 swift4 swift5 \ + tcl ts vala v vb vhdl vimscript wasm wren yorick xslt zig step5_EXCLUDES += bash # never completes at 10,000 step5_EXCLUDES += basic # too slow, and limited to ints of 2^16 @@ -189,6 +191,7 @@ swift5_STEP_TO_PROG = impls/swift5/$($(1)) tcl_STEP_TO_PROG = impls/tcl/$($(1)).tcl ts_STEP_TO_PROG = impls/ts/$($(1)).js vala_STEP_TO_PROG = impls/vala/$($(1)) +v_STEP_TO_PROG = impls/v/$($(1)).v vb_STEP_TO_PROG = impls/vb/$($(1)).exe vhdl_STEP_TO_PROG = impls/vhdl/$($(1)) vimscript_STEP_TO_PROG = impls/vimscript/$($(1)).vim diff --git a/impls/.gitignore b/impls/.gitignore index 0e09605289..ab576fd546 100644 --- a/impls/.gitignore +++ b/impls/.gitignore @@ -148,3 +148,4 @@ elm/*.js !elm/bootstrap.js wasm/*.wat wasm/*.wasm +*~ diff --git a/impls/v/run b/impls/v/run new file mode 100755 index 0000000000..8b08d7e5c7 --- /dev/null +++ b/impls/v/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec v run $(dirname $0)/${STEP:-stepA_mal}.v "${@}" diff --git a/impls/v/step0_repl.v b/impls/v/step0_repl.v new file mode 100644 index 0000000000..905539178f --- /dev/null +++ b/impls/v/step0_repl.v @@ -0,0 +1,24 @@ +import readline { read_line } + +fn rep_read(input string) string { + return input +} + +fn rep_eval(ast string) string { + return ast +} + +fn rep_print(ast string) string { + return ast +} + +fn rep(line string) string { + return rep_print(rep_eval(rep_read(line))) +} + +fn main() { + for { + line := read_line('user> ') or { break } + println(rep(line)) + } +} From ba41c1f97742da92115d3b4f3da4698012d6d0e1 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Wed, 1 Feb 2023 00:28:51 +0000 Subject: [PATCH 02/30] v: step 1 --- impls/v/mal/printer.v | 43 ++++++++++++++ impls/v/mal/reader.v | 111 +++++++++++++++++++++++++++++++++++++ impls/v/mal/types.v | 82 +++++++++++++++++++++++++++ impls/v/step1_read_print.v | 33 +++++++++++ 4 files changed, 269 insertions(+) create mode 100644 impls/v/mal/printer.v create mode 100644 impls/v/mal/reader.v create mode 100644 impls/v/mal/types.v create mode 100644 impls/v/step1_read_print.v diff --git a/impls/v/mal/printer.v b/impls/v/mal/printer.v new file mode 100644 index 0000000000..5a6918bf92 --- /dev/null +++ b/impls/v/mal/printer.v @@ -0,0 +1,43 @@ +module mal + +import maps + +pub fn pr_str(ast MalType) string { + return match ast { + MalInt { + ast.val.str() + } + MalFloat { + ast.val.str() + } + MalString { + '"${ast.val}"' + } + MalKeyword { + ':${ast.key}' + } + MalNil { + 'nil' + } + MalTrue { + 'true' + } + MalFalse { + 'false' + } + MalSymbol { + ast.sym + } + MalList { + '(' + ast.list.map(pr_str).join(' ') + ')' + } + MalVector { + '[' + ast.vec.map(pr_str).join(' ') + ']' + } + MalHashmap { + '{' + maps.to_array(ast.hash, fn (k string, v MalType) string { + return '${k} ${pr_str(v)}' + }).join(' ') + '}' + } + } +} diff --git a/impls/v/mal/reader.v b/impls/v/mal/reader.v new file mode 100644 index 0000000000..2a80397815 --- /dev/null +++ b/impls/v/mal/reader.v @@ -0,0 +1,111 @@ +module mal + +import regex + +const ( + re_token = '^[\\s,]*((?:~@)|(?:[\\[\\]{}()\'\`~^@])|("(?:(?:\\\\.)|[^\\\\"])*"?)|(?:;.*)|(?:[^\\s\\[\\\]{}(\'"`,;)]*))' + re_int = '^-?[0-9]+$' + re_float = '^-?[0-9]*\\.[0-9]+$' +) + +type Token = string + +struct Reader { + toks []Token + re_int regex.RE + re_float regex.RE +mut: + pos int +} + +fn (mut r Reader) next() ?Token { + if tok := r.peek() { + r.pos++ + return tok + } + return none +} + +fn (mut r Reader) peek() ?Token { + return if r.pos < r.toks.len { r.toks[r.pos] } else { none } +} + +fn (mut r Reader) read_form() !MalType { + tok := r.peek() or { return error('no form') } + return match true { + tok == '(' { MalList{r.read_list(')')!} } + tok == '[' { MalVector{r.read_list(']')!} } + tok == '{' { mk_hashmap(r.read_list('}')!)! } + else { r.read_atom()! } + } +} + +fn (mut r Reader) read_list(end_paren string) ![]MalType { + _ := r.next() or { panic('token underflow') } // consume open paren + mut list := []MalType{} + for { + tok := r.peek() or { return error('unbalanced parens') } + match true { + tok == end_paren { break } + tok in [')', ']', '}'] { return error('unbalanced parens') } + else { list << r.read_form()! } + } + } + _ := r.next() or { panic('token underflow') } // consume close paren + return list +} + +fn (mut r Reader) read_atom() !MalType { + tok := r.next() or { panic('token underflow') } + return match true { + tok in [')', ']', '}'] { error('unbalanced parens') } + tok == 'nil' { MalNil{} } + tok == 'true' { MalTrue{} } + tok == 'false' { MalFalse{} } + tok[0] == `"` { MalString{tok[1..tok.len - 1]} } + tok[0] == `:` { MalKeyword{tok[1..]} } + r.re_int.matches_string(tok) { MalInt{tok.i64()} } + r.re_float.matches_string(tok) { MalFloat{tok.f64()} } + else { MalSymbol{tok} } + } +} + +pub fn read_str(input string) !MalType { + mut reader := Reader{ + toks: tokenise(input)! + re_int: regex.regex_opt(mal.re_int) or { panic('regex_opt()') } + re_float: regex.regex_opt(mal.re_float) or { panic('regex_opt()') } + } + return reader.read_form()! +} + +fn tokenise(input string) ![]Token { + mut re := regex.regex_opt(mal.re_token) or { panic('regex_opt()') } + mut ret := []Token{} + mut input_ := input + for { + // println(input_) + start, end := re.match_string(input_) + if start < 0 { + break + } + // println("TOKEN: '${input_[ re.groups[0]..re.groups[1] ]}'") + if re.groups[1] > re.groups[0] { + tok := input_[re.groups[0]..re.groups[1]] + if tok[0] == `"` { + if tok.len == 1 || tok[tok.len - 1] != `"` { + return error('unbalanced quotes') + } + } + ret << tok + } + input_ = input_[end..] + if re.groups[1] == re.groups[0] { + break + } + } + if input_.len > 0 { + panic('leftover input') + } + return ret +} diff --git a/impls/v/mal/types.v b/impls/v/mal/types.v new file mode 100644 index 0000000000..1093e7eff6 --- /dev/null +++ b/impls/v/mal/types.v @@ -0,0 +1,82 @@ +module mal + +type MalType = MalFalse + | MalFloat + | MalHashmap + | MalInt + | MalKeyword + | MalList + | MalNil + | MalString + | MalSymbol + | MalTrue + | MalVector + +pub struct MalInt { +pub: + val i64 +} + +pub struct MalFloat { +pub: + val f64 +} + +pub struct MalString { +pub: + val string +} + +pub struct MalKeyword { +pub: + key string +} + +pub struct MalNil {} + +pub struct MalTrue {} + +pub struct MalFalse {} + +pub struct MalSymbol { +pub: + sym string +} + +pub struct MalList { +pub: + list []MalType +} + +pub struct MalVector { +pub: + vec []MalType +} + +pub struct MalHashmap { +pub: + hash map[string]MalType +} + +pub fn mk_hashmap(list []MalType) !MalHashmap { + mut list_ := list[0..] + mut hash := map[string]MalType{} + for { + if list_.len == 0 { + break + } + if list_.len == 1 { + return error('odd number of hashmap args') + } + key, val := list_[0], list_[1] + if key is MalString { + hash['"${key.val}"'] = val + } else if key is MalKeyword { + hash[':${key.key}'] = val + } else { + return error('bad key type in hashmap') + } + list_ = list_[2..] + } + return MalHashmap{hash} +} diff --git a/impls/v/step1_read_print.v b/impls/v/step1_read_print.v new file mode 100644 index 0000000000..cad0e9d9bf --- /dev/null +++ b/impls/v/step1_read_print.v @@ -0,0 +1,33 @@ +import mal +import readline { read_line } + +fn rep_read(input string) !mal.MalType { + return mal.read_str(input)! +} + +fn rep_eval(ast mal.MalType) mal.MalType { + println(ast) + return ast +} + +fn rep_print(ast mal.MalType) string { + return mal.pr_str(ast) +} + +fn rep(line string) string { + if ast := rep_read(line) { + return rep_print(rep_eval(ast)) + } else { + return if err.msg() == 'no form' { '' } else { 'ERROR: ${err}' } + } +} + +fn main() { + for { + line := read_line('user> ') or { break } + out := rep(line) + if out.len > 0 { + println(out) + } + } +} From d1d6a219e5e8fbcefb9ca953349d4d39e98e1046 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Wed, 1 Feb 2023 10:01:09 +0000 Subject: [PATCH 03/30] v: step 2 --- impls/v/mal/printer.v | 5 +- impls/v/mal/reader.v | 30 +++++++++-- impls/v/mal/types.v | 27 ++-------- impls/v/step2_eval.v | 123 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+), 26 deletions(-) create mode 100644 impls/v/step2_eval.v diff --git a/impls/v/mal/printer.v b/impls/v/mal/printer.v index 5a6918bf92..77ef1e7fc4 100644 --- a/impls/v/mal/printer.v +++ b/impls/v/mal/printer.v @@ -35,9 +35,12 @@ pub fn pr_str(ast MalType) string { '[' + ast.vec.map(pr_str).join(' ') + ']' } MalHashmap { - '{' + maps.to_array(ast.hash, fn (k string, v MalType) string { + '{' + maps.to_array(ast.hm, fn (k string, v MalType) string { return '${k} ${pr_str(v)}' }).join(' ') + '}' } + MalFn { + '' + } } } diff --git a/impls/v/mal/reader.v b/impls/v/mal/reader.v index 2a80397815..7c4bd50e5a 100644 --- a/impls/v/mal/reader.v +++ b/impls/v/mal/reader.v @@ -30,12 +30,32 @@ fn (mut r Reader) peek() ?Token { return if r.pos < r.toks.len { r.toks[r.pos] } else { none } } +fn hash_list(list []MalType) !map[string]MalType { + mut list_ := list[0..] + mut hash := map[string]MalType{} + if list_.len % 2 == 1 { + return error('extra hashmap param') + } + for list_.len > 0 { + key, val := list_[0], list_[1] + if key is MalString { + hash['"${key.val}"'] = val + } else if key is MalKeyword { + hash[':${key.key}'] = val + } else { + return error('bad hashmap key') + } + list_ = list_[2..] + } + return hash +} + fn (mut r Reader) read_form() !MalType { tok := r.peek() or { return error('no form') } return match true { tok == '(' { MalList{r.read_list(')')!} } tok == '[' { MalVector{r.read_list(']')!} } - tok == '{' { mk_hashmap(r.read_list('}')!)! } + tok == '{' { MalHashmap{hash_list(r.read_list('}')!)!} } else { r.read_atom()! } } } @@ -84,12 +104,16 @@ fn tokenise(input string) ![]Token { mut ret := []Token{} mut input_ := input for { - // println(input_) + $if tokenise ? { + println('INPUT: [${input_}]') + } start, end := re.match_string(input_) if start < 0 { break } - // println("TOKEN: '${input_[ re.groups[0]..re.groups[1] ]}'") + $if tokenise ? { + println('TOKEN: [${input_[re.groups[0]..re.groups[1]]}]') + } if re.groups[1] > re.groups[0] { tok := input_[re.groups[0]..re.groups[1]] if tok[0] == `"` { diff --git a/impls/v/mal/types.v b/impls/v/mal/types.v index 1093e7eff6..3852debd18 100644 --- a/impls/v/mal/types.v +++ b/impls/v/mal/types.v @@ -2,6 +2,7 @@ module mal type MalType = MalFalse | MalFloat + | MalFn | MalHashmap | MalInt | MalKeyword @@ -55,28 +56,10 @@ pub: pub struct MalHashmap { pub: - hash map[string]MalType + hm map[string]MalType } -pub fn mk_hashmap(list []MalType) !MalHashmap { - mut list_ := list[0..] - mut hash := map[string]MalType{} - for { - if list_.len == 0 { - break - } - if list_.len == 1 { - return error('odd number of hashmap args') - } - key, val := list_[0], list_[1] - if key is MalString { - hash['"${key.val}"'] = val - } else if key is MalKeyword { - hash[':${key.key}'] = val - } else { - return error('bad key type in hashmap') - } - list_ = list_[2..] - } - return MalHashmap{hash} +pub struct MalFn { +pub: + f fn ([]MalType) !MalType } diff --git a/impls/v/step2_eval.v b/impls/v/step2_eval.v new file mode 100644 index 0000000000..2b3086a181 --- /dev/null +++ b/impls/v/step2_eval.v @@ -0,0 +1,123 @@ +import mal +import readline { read_line } + +type RepEnv = map[string]mal.MalType + +fn rep_read(input string) !mal.MalType { + return mal.read_str(input)! +} + +fn rep_eval(ast mal.MalType, env RepEnv) !mal.MalType { + match ast { + mal.MalList { + if ast.list.len == 0 { + return ast + } else { + res := eval_ast(ast, env)! as mal.MalList + res0 := res.list[0] + if res0 is mal.MalFn { + return res0.f(res.list[1..]) + } else { + return error('bad func') + } + } + } + else { + return eval_ast(ast, env)! + } + } + return ast +} + +fn eval_ast(ast mal.MalType, env RepEnv) !mal.MalType { + match ast { + mal.MalSymbol { + // return env[ ast.sym ] or { error( 'unknown: ${ast.sym}' ) } + if res := env[ast.sym] { + return res + } else { + return error('unknown: ${ast.sym}') + } + } + mal.MalList { + return mal.MalList{ast.list.map(rep_eval(it, env)!)} + } + mal.MalVector { + return mal.MalVector{ast.vec.map(rep_eval(it, env)!)} + } + mal.MalHashmap { + mut hm := map[string]mal.MalType{} + for key in ast.hm.keys() { + hm[key] = rep_eval(ast.hm[key] or { panic('') }, env)! + } + return mal.MalHashmap{hm} + } + else { + return ast + } + } +} + +fn rep_print(ast mal.MalType) string { + return mal.pr_str(ast) +} + +fn rep(line string, env RepEnv) string { + if ast := rep_read(line) { + $if tokenise ? { + println('AST:\n${ast}') + } + if res := rep_eval(ast, env) { + return rep_print(res) + } else { + return 'EVAL ERROR: ${err}' + } + } else { + return if err.msg() == 'no form' { '' } else { 'READ ERROR: ${err}' } + } +} + +fn get_args(op string, args []mal.MalType) !(i64, i64) { + if args.len != 2 { + return error('${op}: takes 2 args') + } + arg0, arg1 := args[0], args[1] + if arg0 is mal.MalInt { + if arg1 is mal.MalInt { + return arg0.val, arg1.val + } + } + return error('${op}: int expected') +} + +fn main() { + env := RepEnv({ + '+': mal.MalType(mal.MalFn{fn (args []mal.MalType) !mal.MalType { + a, b := get_args('+', args)! + return mal.MalInt{a + b} + }}) + '-': mal.MalType(mal.MalFn{fn (args []mal.MalType) !mal.MalType { + a, b := get_args('-', args)! + return mal.MalInt{a - b} + }}) + '*': mal.MalType(mal.MalFn{fn (args []mal.MalType) !mal.MalType { + a, b := get_args('*', args)! + return mal.MalInt{a * b} + }}) + '/': mal.MalType(mal.MalFn{fn (args []mal.MalType) !mal.MalType { + a, b := get_args('/', args)! + return mal.MalInt{a / b} + }}) + }) + + for { + line := read_line('user> ') or { + println('') + break + } + out := rep(line, env) + if out.len > 0 { + println(out) + } + } +} From a4591b856021f8bfcb48a94829fd6ea548b70db0 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Wed, 1 Feb 2023 17:57:04 +0000 Subject: [PATCH 04/30] v: step 1 complete deferred --- impls/v/mal/reader.v | 16 ++++++++++++++++ impls/v/step1_read_print.v | 9 +++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/impls/v/mal/reader.v b/impls/v/mal/reader.v index 7c4bd50e5a..0ca7751321 100644 --- a/impls/v/mal/reader.v +++ b/impls/v/mal/reader.v @@ -50,12 +50,28 @@ fn hash_list(list []MalType) !map[string]MalType { return hash } +fn (mut r Reader) macro_wrap(name string, num_forms int) !MalList { + _ := r.next() or { panic('token underflow') } // consume macro + mut list := []MalType{} + for _ in 0 .. num_forms { + list << r.read_form() or { return error('${name}: missing params') } + } + list << MalSymbol{name} + return MalList{list.reverse()} +} + fn (mut r Reader) read_form() !MalType { tok := r.peek() or { return error('no form') } return match true { tok == '(' { MalList{r.read_list(')')!} } tok == '[' { MalVector{r.read_list(']')!} } tok == '{' { MalHashmap{hash_list(r.read_list('}')!)!} } + tok == "'" { r.macro_wrap('quote', 1)! } + tok == '`' { r.macro_wrap('quasiquote', 1)! } + tok == '~' { r.macro_wrap('unquote', 1)! } + tok == '~@' { r.macro_wrap('splice-unquote', 1)! } + tok == '@' { r.macro_wrap('deref', 1)! } + tok == '^' { r.macro_wrap('with-meta', 2)! } else { r.read_atom()! } } } diff --git a/impls/v/step1_read_print.v b/impls/v/step1_read_print.v index cad0e9d9bf..de636b20bd 100644 --- a/impls/v/step1_read_print.v +++ b/impls/v/step1_read_print.v @@ -6,7 +6,6 @@ fn rep_read(input string) !mal.MalType { } fn rep_eval(ast mal.MalType) mal.MalType { - println(ast) return ast } @@ -16,6 +15,9 @@ fn rep_print(ast mal.MalType) string { fn rep(line string) string { if ast := rep_read(line) { + $if tokenise ? { + println('AST:\n${ast}') + } return rep_print(rep_eval(ast)) } else { return if err.msg() == 'no form' { '' } else { 'ERROR: ${err}' } @@ -24,7 +26,10 @@ fn rep(line string) string { fn main() { for { - line := read_line('user> ') or { break } + line := read_line('user> ') or { + println('') + break + } out := rep(line) if out.len > 0 { println(out) From 9284c823a2386577fcf3d6e0ee2f8697409b8f9e Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Thu, 2 Feb 2023 00:06:40 +0000 Subject: [PATCH 05/30] v: step 3 --- impls/v/mal/env.v | 51 ++++++++++++++ impls/v/step3_env.v | 166 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 impls/v/mal/env.v create mode 100644 impls/v/step3_env.v diff --git a/impls/v/mal/env.v b/impls/v/mal/env.v new file mode 100644 index 0000000000..7cd71c8328 --- /dev/null +++ b/impls/v/mal/env.v @@ -0,0 +1,51 @@ +module mal + +pub struct Env { +pub: + outer &Env +pub mut: + data map[string]MalType = map[string]MalType{} +} + +pub fn mk_outer_env() Env { + return Env{ + outer: unsafe { nil } + } +} + +pub fn mk_env(outer Env) Env { + return Env{ + outer: &outer + } +} + +pub fn (mut e Env) set(sym string, val MalType) MalType { + e.data[sym] = val + return val +} + +pub fn (e Env) find(sym string) ?MalType { + $if env ? { + println('ENV: looking for [${sym}] in\n${e.data}') + } + if res := e.data[sym] { + $if env ? { + println('...found') + } + return res + } + if e.outer != unsafe { nil } { + $if env ? { + println('...checking outer') + } + return e.outer.find(sym) + } + $if env ? { + println('...not found') + } + return none +} + +pub fn (e Env) get(sym string) !MalType { + return e.find(sym) or { error('"${sym} not found') } +} diff --git a/impls/v/step3_env.v b/impls/v/step3_env.v new file mode 100644 index 0000000000..82be5ff4d9 --- /dev/null +++ b/impls/v/step3_env.v @@ -0,0 +1,166 @@ +import mal +import readline { read_line } + +fn rep_read(input string) !mal.MalType { + return mal.read_str(input)! +} + +fn rep_eval(ast mal.MalType, mut env mal.Env) !mal.MalType { + match ast { + mal.MalList { + if ast.list.len < 1 { + return ast + } + list0 := ast.list[0] + special := if list0 is mal.MalSymbol { list0.sym } else { '' } + match special { + 'def!' { + if ast.list.len < 3 { + return error('def!: missing param') + } + key := ast.list[1] + if key is mal.MalSymbol { + res := rep_eval(ast.list[2], mut env)! + return env.set(key.sym, res) + } else { + return error('def!: bad symbol') + } + } + 'let*' { + mut new_env := mal.mk_env(env) + if ast.list.len < 2 { + return error('let*: missing bindings') + } + bindings := ast.list[1] + mut list := match bindings { + mal.MalList { bindings.list } + mal.MalVector { bindings.vec } + else { return error('let*: bad bindings') } + } + list = list[0..] // shallow copy + if list.len % 2 == 1 { + return error('let*: extra binding param') + } + for list.len > 0 { + key := list[0] + if key is mal.MalSymbol { + res := rep_eval(list[1], mut new_env)! + new_env.set(key.sym, res) + } else { + return error('let*: bad binding symbol') + } + list = list[2..] + } + if ast.list.len > 2 { + return rep_eval(ast.list[2], mut new_env)! + } else { + return error('let*: missing exp') + } + } + else { // list apply + // BUG: https://github.com/vlang/v/issues/17156 + // res := eval_ast(ast, env)! as mal.MalList + res_tmp := eval_ast(ast, mut env)! + res := res_tmp as mal.MalList + res0 := res.list[0] + if res0 is mal.MalFn { + return res0.f(res.list[1..]) + } else { + return error('bad func') + } + } + } + } + else { + return eval_ast(ast, mut env)! + } + } + return ast +} + +fn eval_ast(ast mal.MalType, mut env mal.Env) !mal.MalType { + match ast { + mal.MalSymbol { + return env.get(ast.sym)! + } + mal.MalList { + return mal.MalList{ast.list.map(rep_eval(it, mut env)!)} + } + mal.MalVector { + return mal.MalVector{ast.vec.map(rep_eval(it, mut env)!)} + } + mal.MalHashmap { + mut hm := map[string]mal.MalType{} + for key in ast.hm.keys() { + hm[key] = rep_eval(ast.hm[key] or { panic('') }, mut env)! + } + return mal.MalHashmap{hm} + } + else { + return ast + } + } +} + +fn rep_print(ast mal.MalType) string { + return mal.pr_str(ast) +} + +fn rep(line string, mut env mal.Env) string { + if ast := rep_read(line) { + $if tokenise ? { + println('AST:\n${ast}') + } + if res := rep_eval(ast, mut env) { + return rep_print(res) + } else { + return 'EVAL ERROR: ${err}' + } + } else { + return if err.msg() == 'no form' { '' } else { 'READ ERROR: ${err}' } + } +} + +fn get_args(op string, args []mal.MalType) !(i64, i64) { + if args.len != 2 { + return error('${op}: takes 2 args') + } + arg0, arg1 := args[0], args[1] + if arg0 is mal.MalInt { + if arg1 is mal.MalInt { + return arg0.val, arg1.val + } + } + return error('${op}: int expected') +} + +fn main() { + mut env := mal.mk_outer_env() + env.set('+', mal.MalType(mal.MalFn{fn (args []mal.MalType) !mal.MalType { + a, b := get_args('+', args)! + return mal.MalInt{a + b} + }})) + env.set('-', mal.MalType(mal.MalFn{fn (args []mal.MalType) !mal.MalType { + a, b := get_args('-', args)! + return mal.MalInt{a - b} + }})) + env.set('*', mal.MalType(mal.MalFn{fn (args []mal.MalType) !mal.MalType { + a, b := get_args('*', args)! + return mal.MalInt{a * b} + }})) + env.set('/', mal.MalType(mal.MalFn{fn (args []mal.MalType) !mal.MalType { + a, b := get_args('/', args)! + return mal.MalInt{a / b} + }})) + + for { + line := read_line('user> ') or { + println('') + break + } + out := rep(line, mut env) + if out.len > 0 { + println(out) + } + } +} From a5d9f46e4ce4d385d1813e0da5c4969242c60802 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Thu, 2 Feb 2023 16:42:27 +0000 Subject: [PATCH 06/30] v updates for steps 1-3 --- impls/v/mal/env.v | 12 ++--- impls/v/mal/printer.v | 28 +++++++--- impls/v/mal/reader.v | 2 +- impls/v/mal/types.v | 88 +++++++++++++++++++++++++++++- impls/v/step1_read_print.v | 6 +-- impls/v/step2_eval.v | 53 +++++++++--------- impls/v/step3_env.v | 107 +++++++++++++------------------------ 7 files changed, 175 insertions(+), 121 deletions(-) diff --git a/impls/v/mal/env.v b/impls/v/mal/env.v index 7cd71c8328..8846d94d65 100644 --- a/impls/v/mal/env.v +++ b/impls/v/mal/env.v @@ -2,20 +2,14 @@ module mal pub struct Env { pub: - outer &Env + outer &Env = unsafe { nil } pub mut: data map[string]MalType = map[string]MalType{} } -pub fn mk_outer_env() Env { +pub fn mk_env(outer &Env) Env { return Env{ - outer: unsafe { nil } - } -} - -pub fn mk_env(outer Env) Env { - return Env{ - outer: &outer + outer: unsafe { outer } } } diff --git a/impls/v/mal/printer.v b/impls/v/mal/printer.v index 77ef1e7fc4..93c85acb4c 100644 --- a/impls/v/mal/printer.v +++ b/impls/v/mal/printer.v @@ -2,7 +2,7 @@ module mal import maps -pub fn pr_str(ast MalType) string { +pub fn pr_str(ast MalType, readable bool) string { return match ast { MalInt { ast.val.str() @@ -11,7 +11,7 @@ pub fn pr_str(ast MalType) string { ast.val.str() } MalString { - '"${ast.val}"' + if readable { '"${escape(ast.val)}"' } else { ast.val } } MalKeyword { ':${ast.key}' @@ -29,18 +29,32 @@ pub fn pr_str(ast MalType) string { ast.sym } MalList { - '(' + ast.list.map(pr_str).join(' ') + ')' + '(' + ast.list.map(pr_str(it, readable)).join(' ') + ')' } MalVector { - '[' + ast.vec.map(pr_str).join(' ') + ']' + '[' + ast.vec.map(pr_str(it, readable)).join(' ') + ']' } MalHashmap { - '{' + maps.to_array(ast.hm, fn (k string, v MalType) string { - return '${k} ${pr_str(v)}' + '{' + maps.to_array(ast.hm, fn [readable] (k string, v MalType) string { + return '${k} ${pr_str(v, readable)}' }).join(' ') + '}' } MalFn { - '' + '#' } } } + +fn escape( str string ) string { + return str + .replace('\\', '\\\\') + .replace('\n', '\\n') + .replace('"', '\\"') +} + +fn unescape( str string ) string { + return str + .replace('\\n', '\n') + .replace('\\"', '"') + .replace('\\\\', '\\') +} diff --git a/impls/v/mal/reader.v b/impls/v/mal/reader.v index 0ca7751321..6a7bde417d 100644 --- a/impls/v/mal/reader.v +++ b/impls/v/mal/reader.v @@ -98,7 +98,7 @@ fn (mut r Reader) read_atom() !MalType { tok == 'nil' { MalNil{} } tok == 'true' { MalTrue{} } tok == 'false' { MalFalse{} } - tok[0] == `"` { MalString{tok[1..tok.len - 1]} } + tok[0] == `"` { MalString{unescape(tok[1..tok.len - 1])} } tok[0] == `:` { MalKeyword{tok[1..]} } r.re_int.matches_string(tok) { MalInt{tok.i64()} } r.re_float.matches_string(tok) { MalFloat{tok.f64()} } diff --git a/impls/v/mal/types.v b/impls/v/mal/types.v index 3852debd18..07a487aacc 100644 --- a/impls/v/mal/types.v +++ b/impls/v/mal/types.v @@ -13,53 +13,139 @@ type MalType = MalFalse | MalTrue | MalVector +pub fn (t MalType) truthy() bool { + return !t.falsey() +} + +pub fn (t MalType) falsey() bool { + return t is MalFalse || t is MalNil +} + +pub fn (t MalType) sym() !string { + return if t is MalSymbol { t.sym } else { error('symbol expected') } +} + +type MalFnFn = fn (args MalList) !MalType + +pub fn (t MalType) fn_() !MalFnFn { + // BUG: https://github.com/vlang/v/issues/17204 + // return if t is MalFn { t.f } else { error( 'function expected' ) } + if t is MalFn { + return t.f + } else { + return error('function expected') + } +} + +pub fn (t MalType) int_() !i64 { + return if t is MalInt { t.val } else { error('integer expected') } +} + +pub fn (t MalType) list() ![]MalType { + return if t is MalList { t.list } else { error('list expected') } +} + +pub fn (t MalType) list_or_vec() ![]MalType { + return match t { + MalList { t.list } + MalVector { t.vec } + else { error('list/vector expected') } + } +} + +// -- + pub struct MalInt { pub: val i64 } +// -- + pub struct MalFloat { pub: val f64 } +// -- + pub struct MalString { pub: val string } +// - + pub struct MalKeyword { pub: key string } +// -- + pub struct MalNil {} +// -- + pub struct MalTrue {} +// -- + pub struct MalFalse {} +// -- + pub struct MalSymbol { pub: sym string } +// -- + pub struct MalList { pub: list []MalType } +pub fn (l MalList) first() !MalType { + return if l.list.len > 0 { l.list[0] } else { error('list: empty') } +} + +pub fn (l MalList) last() !MalType { + return if l.list.len > 0 { l.list.last() } else { error('list: empty') } +} + +pub fn (l MalList) rest() MalList { + return if l.list.len > 0 { MalList{l.list[1..]} } else { MalList{} } +} + +pub fn (l MalList) len() int { + return l.list.len +} + +pub fn (l MalList) nth(n int) !MalType { + return if n < l.list.len { l.list[n] } else { error('list: index oob') } +} + +// -- + pub struct MalVector { pub: vec []MalType } +// -- + pub struct MalHashmap { pub: hm map[string]MalType } +// -- + pub struct MalFn { pub: - f fn ([]MalType) !MalType + // mut: + // env Env + f MalFnFn } diff --git a/impls/v/step1_read_print.v b/impls/v/step1_read_print.v index de636b20bd..92c696eb6c 100644 --- a/impls/v/step1_read_print.v +++ b/impls/v/step1_read_print.v @@ -5,12 +5,12 @@ fn rep_read(input string) !mal.MalType { return mal.read_str(input)! } -fn rep_eval(ast mal.MalType) mal.MalType { +fn eval(ast mal.MalType) mal.MalType { return ast } fn rep_print(ast mal.MalType) string { - return mal.pr_str(ast) + return mal.pr_str(ast, true) } fn rep(line string) string { @@ -18,7 +18,7 @@ fn rep(line string) string { $if tokenise ? { println('AST:\n${ast}') } - return rep_print(rep_eval(ast)) + return rep_print(eval(ast)) } else { return if err.msg() == 'no form' { '' } else { 'ERROR: ${err}' } } diff --git a/impls/v/step2_eval.v b/impls/v/step2_eval.v index 2b3086a181..ef7a28a8b7 100644 --- a/impls/v/step2_eval.v +++ b/impls/v/step2_eval.v @@ -7,19 +7,18 @@ fn rep_read(input string) !mal.MalType { return mal.read_str(input)! } -fn rep_eval(ast mal.MalType, env RepEnv) !mal.MalType { +fn eval(ast mal.MalType, env RepEnv) !mal.MalType { match ast { mal.MalList { if ast.list.len == 0 { return ast } else { - res := eval_ast(ast, env)! as mal.MalList - res0 := res.list[0] - if res0 is mal.MalFn { - return res0.f(res.list[1..]) - } else { - return error('bad func') - } + // BUG: https://github.com/vlang/v/issues/17156 + // res := eval_ast(ast, env)! as mal.MalList + res_tmp := eval_ast(ast, env)! + res := res_tmp as mal.MalList + fn_ := res.list[0].fn_()! + return fn_(res.rest()) } } else { @@ -40,15 +39,15 @@ fn eval_ast(ast mal.MalType, env RepEnv) !mal.MalType { } } mal.MalList { - return mal.MalList{ast.list.map(rep_eval(it, env)!)} + return mal.MalList{ast.list.map(eval(it, env)!)} } mal.MalVector { - return mal.MalVector{ast.vec.map(rep_eval(it, env)!)} + return mal.MalVector{ast.vec.map(eval(it, env)!)} } mal.MalHashmap { mut hm := map[string]mal.MalType{} for key in ast.hm.keys() { - hm[key] = rep_eval(ast.hm[key] or { panic('') }, env)! + hm[key] = eval(ast.hm[key] or { panic('') }, env)! } return mal.MalHashmap{hm} } @@ -59,29 +58,25 @@ fn eval_ast(ast mal.MalType, env RepEnv) !mal.MalType { } fn rep_print(ast mal.MalType) string { - return mal.pr_str(ast) + return mal.pr_str(ast, true) } fn rep(line string, env RepEnv) string { - if ast := rep_read(line) { - $if tokenise ? { - println('AST:\n${ast}') - } - if res := rep_eval(ast, env) { - return rep_print(res) - } else { - return 'EVAL ERROR: ${err}' - } - } else { + ast := rep_read(line) or { return if err.msg() == 'no form' { '' } else { 'READ ERROR: ${err}' } + } + $if tokenise ? { + println('AST:\n${ast}') } + res := eval(ast, env) or { return 'EVAL ERROR: ${err}' } + return rep_print(res) } -fn get_args(op string, args []mal.MalType) !(i64, i64) { - if args.len != 2 { +fn get_args(op string, args mal.MalList) !(i64, i64) { + if args.len() != 2 { return error('${op}: takes 2 args') } - arg0, arg1 := args[0], args[1] + arg0, arg1 := args.list[0], args.list[1] if arg0 is mal.MalInt { if arg1 is mal.MalInt { return arg0.val, arg1.val @@ -92,19 +87,19 @@ fn get_args(op string, args []mal.MalType) !(i64, i64) { fn main() { env := RepEnv({ - '+': mal.MalType(mal.MalFn{fn (args []mal.MalType) !mal.MalType { + '+': mal.MalType(mal.MalFn{fn (args mal.MalList) !mal.MalType { a, b := get_args('+', args)! return mal.MalInt{a + b} }}) - '-': mal.MalType(mal.MalFn{fn (args []mal.MalType) !mal.MalType { + '-': mal.MalType(mal.MalFn{fn (args mal.MalList) !mal.MalType { a, b := get_args('-', args)! return mal.MalInt{a - b} }}) - '*': mal.MalType(mal.MalFn{fn (args []mal.MalType) !mal.MalType { + '*': mal.MalType(mal.MalFn{fn (args mal.MalList) !mal.MalType { a, b := get_args('*', args)! return mal.MalInt{a * b} }}) - '/': mal.MalType(mal.MalFn{fn (args []mal.MalType) !mal.MalType { + '/': mal.MalType(mal.MalFn{fn (args mal.MalList) !mal.MalType { a, b := get_args('/', args)! return mal.MalInt{a / b} }}) diff --git a/impls/v/step3_env.v b/impls/v/step3_env.v index 82be5ff4d9..85150210ed 100644 --- a/impls/v/step3_env.v +++ b/impls/v/step3_env.v @@ -5,69 +5,38 @@ fn rep_read(input string) !mal.MalType { return mal.read_str(input)! } -fn rep_eval(ast mal.MalType, mut env mal.Env) !mal.MalType { +fn eval(ast mal.MalType, mut env mal.Env) !mal.MalType { match ast { mal.MalList { - if ast.list.len < 1 { - return ast - } - list0 := ast.list[0] - special := if list0 is mal.MalSymbol { list0.sym } else { '' } - match special { + first := ast.first() or { return ast } // return empty list + match first.sym() or { '' } { 'def!' { - if ast.list.len < 3 { - return error('def!: missing param') - } - key := ast.list[1] - if key is mal.MalSymbol { - res := rep_eval(ast.list[2], mut env)! - return env.set(key.sym, res) - } else { - return error('def!: bad symbol') - } + ast.nth(2) or { return error('def!: missing param') } + sym := ast.list[1].sym() or { return error('def!: ${err}') } + return env.set(sym, eval(ast.list[2], mut env)!) } 'let*' { + ast.nth(2) or { return error('let*: missing param') } mut new_env := mal.mk_env(env) - if ast.list.len < 2 { - return error('let*: missing bindings') - } - bindings := ast.list[1] - mut list := match bindings { - mal.MalList { bindings.list } - mal.MalVector { bindings.vec } - else { return error('let*: bad bindings') } - } - list = list[0..] // shallow copy - if list.len % 2 == 1 { + mut pairs := ast.list[1].list_or_vec() or { return error('let*: ${err}') } + pairs = pairs[0..] // copy + if pairs.len % 2 == 1 { return error('let*: extra binding param') } - for list.len > 0 { - key := list[0] - if key is mal.MalSymbol { - res := rep_eval(list[1], mut new_env)! - new_env.set(key.sym, res) - } else { - return error('let*: bad binding symbol') - } - list = list[2..] - } - if ast.list.len > 2 { - return rep_eval(ast.list[2], mut new_env)! - } else { - return error('let*: missing exp') + for pairs.len > 0 { + sym := pairs[0].sym() or { return error('let*: ${err}') } + new_env.set(sym, eval(pairs[1], mut new_env)!) + pairs = pairs[2..] } + return eval(ast.list[2], mut new_env)! } - else { // list apply + else { // regular list apply // BUG: https://github.com/vlang/v/issues/17156 // res := eval_ast(ast, env)! as mal.MalList res_tmp := eval_ast(ast, mut env)! res := res_tmp as mal.MalList - res0 := res.list[0] - if res0 is mal.MalFn { - return res0.f(res.list[1..]) - } else { - return error('bad func') - } + fn_ := res.list[0].fn_()! + return fn_(res.rest()) } } } @@ -84,15 +53,15 @@ fn eval_ast(ast mal.MalType, mut env mal.Env) !mal.MalType { return env.get(ast.sym)! } mal.MalList { - return mal.MalList{ast.list.map(rep_eval(it, mut env)!)} + return mal.MalList{ast.list.map(eval(it, mut env)!)} } mal.MalVector { - return mal.MalVector{ast.vec.map(rep_eval(it, mut env)!)} + return mal.MalVector{ast.vec.map(eval(it, mut env)!)} } mal.MalHashmap { mut hm := map[string]mal.MalType{} for key in ast.hm.keys() { - hm[key] = rep_eval(ast.hm[key] or { panic('') }, mut env)! + hm[key] = eval(ast.hm[key] or { panic('') }, mut env)! } return mal.MalHashmap{hm} } @@ -103,29 +72,25 @@ fn eval_ast(ast mal.MalType, mut env mal.Env) !mal.MalType { } fn rep_print(ast mal.MalType) string { - return mal.pr_str(ast) + return mal.pr_str(ast, true) } fn rep(line string, mut env mal.Env) string { - if ast := rep_read(line) { - $if tokenise ? { - println('AST:\n${ast}') - } - if res := rep_eval(ast, mut env) { - return rep_print(res) - } else { - return 'EVAL ERROR: ${err}' - } - } else { + ast := rep_read(line) or { return if err.msg() == 'no form' { '' } else { 'READ ERROR: ${err}' } + } + $if tokenise ? { + println('AST:\n${ast}') } + res := eval(ast, mut env) or { return 'EVAL ERROR: ${err}' } + return rep_print(res) } -fn get_args(op string, args []mal.MalType) !(i64, i64) { - if args.len != 2 { +fn get_args(op string, args mal.MalList) !(i64, i64) { + if args.len() != 2 { return error('${op}: takes 2 args') } - arg0, arg1 := args[0], args[1] + arg0, arg1 := args.list[0], args.list[1] if arg0 is mal.MalInt { if arg1 is mal.MalInt { return arg0.val, arg1.val @@ -135,20 +100,20 @@ fn get_args(op string, args []mal.MalType) !(i64, i64) { } fn main() { - mut env := mal.mk_outer_env() - env.set('+', mal.MalType(mal.MalFn{fn (args []mal.MalType) !mal.MalType { + mut env := mal.Env{} + env.set('+', mal.MalType(mal.MalFn{fn (args mal.MalList) !mal.MalType { a, b := get_args('+', args)! return mal.MalInt{a + b} }})) - env.set('-', mal.MalType(mal.MalFn{fn (args []mal.MalType) !mal.MalType { + env.set('-', mal.MalType(mal.MalFn{fn (args mal.MalList) !mal.MalType { a, b := get_args('-', args)! return mal.MalInt{a - b} }})) - env.set('*', mal.MalType(mal.MalFn{fn (args []mal.MalType) !mal.MalType { + env.set('*', mal.MalType(mal.MalFn{fn (args mal.MalList) !mal.MalType { a, b := get_args('*', args)! return mal.MalInt{a * b} }})) - env.set('/', mal.MalType(mal.MalFn{fn (args []mal.MalType) !mal.MalType { + env.set('/', mal.MalType(mal.MalFn{fn (args mal.MalList) !mal.MalType { a, b := get_args('/', args)! return mal.MalInt{a / b} }})) From c377ea16ac15ee9a80d4974f194e8e47840b8858 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Fri, 3 Feb 2023 09:39:20 +0000 Subject: [PATCH 07/30] v: renamed types --- impls/v/mal/env.v | 10 ++-- impls/v/mal/printer.v | 54 ++++++++++--------- impls/v/mal/reader.v | 50 ++++++++--------- impls/v/mal/types.v | 106 +++++++++++++++++-------------------- impls/v/step1_read_print.v | 8 +-- impls/v/step2_eval.v | 64 +++++++++++----------- impls/v/step3_env.v | 62 +++++++++++----------- 7 files changed, 176 insertions(+), 178 deletions(-) diff --git a/impls/v/mal/env.v b/impls/v/mal/env.v index 8846d94d65..452a06a316 100644 --- a/impls/v/mal/env.v +++ b/impls/v/mal/env.v @@ -4,7 +4,7 @@ pub struct Env { pub: outer &Env = unsafe { nil } pub mut: - data map[string]MalType = map[string]MalType{} + data map[string]Type = map[string]Type{} } pub fn mk_env(outer &Env) Env { @@ -13,12 +13,12 @@ pub fn mk_env(outer &Env) Env { } } -pub fn (mut e Env) set(sym string, val MalType) MalType { +pub fn (mut e Env) set(sym string, val Type) Type { e.data[sym] = val return val } -pub fn (e Env) find(sym string) ?MalType { +pub fn (e Env) find(sym string) ?Type { $if env ? { println('ENV: looking for [${sym}] in\n${e.data}') } @@ -40,6 +40,6 @@ pub fn (e Env) find(sym string) ?MalType { return none } -pub fn (e Env) get(sym string) !MalType { - return e.find(sym) or { error('"${sym} not found') } +pub fn (e Env) get(sym string) !Type { + return e.find(sym) or { error("'${sym}' not found") } } diff --git a/impls/v/mal/printer.v b/impls/v/mal/printer.v index 93c85acb4c..65888cef2e 100644 --- a/impls/v/mal/printer.v +++ b/impls/v/mal/printer.v @@ -2,59 +2,63 @@ module mal import maps -pub fn pr_str(ast MalType, readable bool) string { +pub fn pr_str(ast Type, readable bool) string { return match ast { - MalInt { + Int { ast.val.str() } - MalFloat { + Float { ast.val.str() } - MalString { - if readable { '"${escape(ast.val)}"' } else { ast.val } + String { + if readable { + '"${escape(ast.val)}"' + } else { + ast.val + } } - MalKeyword { + Keyword { ':${ast.key}' } - MalNil { + Nil { 'nil' } - MalTrue { + True { 'true' } - MalFalse { + False { 'false' } - MalSymbol { + Symbol { ast.sym } - MalList { + List { '(' + ast.list.map(pr_str(it, readable)).join(' ') + ')' } - MalVector { + Vector { '[' + ast.vec.map(pr_str(it, readable)).join(' ') + ']' } - MalHashmap { - '{' + maps.to_array(ast.hm, fn [readable] (k string, v MalType) string { + Hashmap { + '{' + maps.to_array(ast.hm, fn [readable] (k string, v Type) string { return '${k} ${pr_str(v, readable)}' }).join(' ') + '}' } - MalFn { + Fn { '#' } } } -fn escape( str string ) string { - return str - .replace('\\', '\\\\') - .replace('\n', '\\n') - .replace('"', '\\"') +fn escape(str string) string { + return str + .replace('\\', '\\\\') + .replace('\n', '\\n') + .replace('"', '\\"') } -fn unescape( str string ) string { - return str - .replace('\\n', '\n') - .replace('\\"', '"') - .replace('\\\\', '\\') +fn unescape(str string) string { + return str + .replace('\\n', '\n') + .replace('\\"', '"') + .replace('\\\\', '\\') } diff --git a/impls/v/mal/reader.v b/impls/v/mal/reader.v index 6a7bde417d..a3f5b0003a 100644 --- a/impls/v/mal/reader.v +++ b/impls/v/mal/reader.v @@ -30,17 +30,17 @@ fn (mut r Reader) peek() ?Token { return if r.pos < r.toks.len { r.toks[r.pos] } else { none } } -fn hash_list(list []MalType) !map[string]MalType { +fn hash_list(list []Type) !map[string]Type { mut list_ := list[0..] - mut hash := map[string]MalType{} + mut hash := map[string]Type{} if list_.len % 2 == 1 { return error('extra hashmap param') } for list_.len > 0 { key, val := list_[0], list_[1] - if key is MalString { + if key is String { hash['"${key.val}"'] = val - } else if key is MalKeyword { + } else if key is Keyword { hash[':${key.key}'] = val } else { return error('bad hashmap key') @@ -50,22 +50,22 @@ fn hash_list(list []MalType) !map[string]MalType { return hash } -fn (mut r Reader) macro_wrap(name string, num_forms int) !MalList { +fn (mut r Reader) macro_wrap(name string, num_forms int) !List { _ := r.next() or { panic('token underflow') } // consume macro - mut list := []MalType{} + mut list := []Type{} for _ in 0 .. num_forms { - list << r.read_form() or { return error('${name}: missing params') } + list << r.read_form() or { return error('${name}: missing param') } } - list << MalSymbol{name} - return MalList{list.reverse()} + list << Symbol{name} + return List{list.reverse()} } -fn (mut r Reader) read_form() !MalType { +fn (mut r Reader) read_form() !Type { tok := r.peek() or { return error('no form') } return match true { - tok == '(' { MalList{r.read_list(')')!} } - tok == '[' { MalVector{r.read_list(']')!} } - tok == '{' { MalHashmap{hash_list(r.read_list('}')!)!} } + tok == '(' { List{r.read_list(')')!} } + tok == '[' { Vector{r.read_list(']')!} } + tok == '{' { Hashmap{hash_list(r.read_list('}')!)!} } tok == "'" { r.macro_wrap('quote', 1)! } tok == '`' { r.macro_wrap('quasiquote', 1)! } tok == '~' { r.macro_wrap('unquote', 1)! } @@ -76,9 +76,9 @@ fn (mut r Reader) read_form() !MalType { } } -fn (mut r Reader) read_list(end_paren string) ![]MalType { +fn (mut r Reader) read_list(end_paren string) ![]Type { _ := r.next() or { panic('token underflow') } // consume open paren - mut list := []MalType{} + mut list := []Type{} for { tok := r.peek() or { return error('unbalanced parens') } match true { @@ -91,22 +91,22 @@ fn (mut r Reader) read_list(end_paren string) ![]MalType { return list } -fn (mut r Reader) read_atom() !MalType { +fn (mut r Reader) read_atom() !Type { tok := r.next() or { panic('token underflow') } return match true { tok in [')', ']', '}'] { error('unbalanced parens') } - tok == 'nil' { MalNil{} } - tok == 'true' { MalTrue{} } - tok == 'false' { MalFalse{} } - tok[0] == `"` { MalString{unescape(tok[1..tok.len - 1])} } - tok[0] == `:` { MalKeyword{tok[1..]} } - r.re_int.matches_string(tok) { MalInt{tok.i64()} } - r.re_float.matches_string(tok) { MalFloat{tok.f64()} } - else { MalSymbol{tok} } + tok == 'nil' { Nil{} } + tok == 'true' { True{} } + tok == 'false' { False{} } + tok[0] == `"` { String{unescape(tok[1..tok.len - 1])} } + tok[0] == `:` { Keyword{tok[1..]} } + r.re_int.matches_string(tok) { Int{tok.i64()} } + r.re_float.matches_string(tok) { Float{tok.f64()} } + else { Symbol{tok} } } } -pub fn read_str(input string) !MalType { +pub fn read_str(input string) !Type { mut reader := Reader{ toks: tokenise(input)! re_int: regex.regex_opt(mal.re_int) or { panic('regex_opt()') } diff --git a/impls/v/mal/types.v b/impls/v/mal/types.v index 07a487aacc..0f1e979079 100644 --- a/impls/v/mal/types.v +++ b/impls/v/mal/types.v @@ -1,151 +1,145 @@ module mal -type MalType = MalFalse - | MalFloat - | MalFn - | MalHashmap - | MalInt - | MalKeyword - | MalList - | MalNil - | MalString - | MalSymbol - | MalTrue - | MalVector - -pub fn (t MalType) truthy() bool { +type Type = False + | Float + | Fn + | Hashmap + | Int + | Keyword + | List + | Nil + | String + | Symbol + | True + | Vector + +pub fn (t Type) truthy() bool { return !t.falsey() } -pub fn (t MalType) falsey() bool { - return t is MalFalse || t is MalNil +pub fn (t Type) falsey() bool { + return t is False || t is Nil } -pub fn (t MalType) sym() !string { - return if t is MalSymbol { t.sym } else { error('symbol expected') } +pub fn (t Type) sym() !string { + return if t is Symbol { t.sym } else { error('symbol expected') } } -type MalFnFn = fn (args MalList) !MalType +type FnFn = fn (args List) !Type -pub fn (t MalType) fn_() !MalFnFn { - // BUG: https://github.com/vlang/v/issues/17204 - // return if t is MalFn { t.f } else { error( 'function expected' ) } - if t is MalFn { - return t.f - } else { - return error('function expected') - } +pub fn (t Type) fn_() !FnFn { + return if t is Fn { t.f } else { error('function expected') } } -pub fn (t MalType) int_() !i64 { - return if t is MalInt { t.val } else { error('integer expected') } +pub fn (t Type) int_() !i64 { + return if t is Int { t.val } else { error('integer expected') } } -pub fn (t MalType) list() ![]MalType { - return if t is MalList { t.list } else { error('list expected') } +pub fn (t Type) list() ![]Type { + return if t is List { t.list } else { error('list expected') } } -pub fn (t MalType) list_or_vec() ![]MalType { +pub fn (t Type) list_or_vec() ![]Type { return match t { - MalList { t.list } - MalVector { t.vec } + List { t.list } + Vector { t.vec } else { error('list/vector expected') } } } // -- -pub struct MalInt { +pub struct Int { pub: val i64 } // -- -pub struct MalFloat { +pub struct Float { pub: val f64 } // -- -pub struct MalString { +pub struct String { pub: val string } // - -pub struct MalKeyword { +pub struct Keyword { pub: key string } // -- -pub struct MalNil {} +pub struct Nil {} // -- -pub struct MalTrue {} +pub struct True {} // -- -pub struct MalFalse {} +pub struct False {} // -- -pub struct MalSymbol { +pub struct Symbol { pub: sym string } // -- -pub struct MalList { +pub struct List { pub: - list []MalType + list []Type } -pub fn (l MalList) first() !MalType { +pub fn (l List) first() !Type { return if l.list.len > 0 { l.list[0] } else { error('list: empty') } } -pub fn (l MalList) last() !MalType { +pub fn (l List) last() !Type { return if l.list.len > 0 { l.list.last() } else { error('list: empty') } } -pub fn (l MalList) rest() MalList { - return if l.list.len > 0 { MalList{l.list[1..]} } else { MalList{} } +pub fn (l List) rest() List { + return if l.list.len > 0 { List{l.list[1..]} } else { List{} } } -pub fn (l MalList) len() int { +pub fn (l List) len() int { return l.list.len } -pub fn (l MalList) nth(n int) !MalType { +pub fn (l List) nth(n int) !Type { return if n < l.list.len { l.list[n] } else { error('list: index oob') } } // -- -pub struct MalVector { +pub struct Vector { pub: - vec []MalType + vec []Type } // -- -pub struct MalHashmap { +pub struct Hashmap { pub: - hm map[string]MalType + hm map[string]Type } // -- -pub struct MalFn { +pub struct Fn { pub: // mut: // env Env - f MalFnFn + f FnFn } diff --git a/impls/v/step1_read_print.v b/impls/v/step1_read_print.v index 92c696eb6c..f57d17d929 100644 --- a/impls/v/step1_read_print.v +++ b/impls/v/step1_read_print.v @@ -1,21 +1,21 @@ import mal import readline { read_line } -fn rep_read(input string) !mal.MalType { +fn rep_read(input string) !mal.Type { return mal.read_str(input)! } -fn eval(ast mal.MalType) mal.MalType { +fn eval(ast mal.Type) mal.Type { return ast } -fn rep_print(ast mal.MalType) string { +fn rep_print(ast mal.Type) string { return mal.pr_str(ast, true) } fn rep(line string) string { if ast := rep_read(line) { - $if tokenise ? { + $if ast ? { println('AST:\n${ast}') } return rep_print(eval(ast)) diff --git a/impls/v/step2_eval.v b/impls/v/step2_eval.v index ef7a28a8b7..1e94c97483 100644 --- a/impls/v/step2_eval.v +++ b/impls/v/step2_eval.v @@ -1,22 +1,22 @@ import mal import readline { read_line } -type RepEnv = map[string]mal.MalType +type RepEnv = map[string]mal.Type -fn rep_read(input string) !mal.MalType { +fn rep_read(input string) !mal.Type { return mal.read_str(input)! } -fn eval(ast mal.MalType, env RepEnv) !mal.MalType { +fn eval(ast mal.Type, env RepEnv) !mal.Type { match ast { - mal.MalList { + mal.List { if ast.list.len == 0 { return ast } else { // BUG: https://github.com/vlang/v/issues/17156 - // res := eval_ast(ast, env)! as mal.MalList + // res := eval_ast(ast, env)! as mal.List res_tmp := eval_ast(ast, env)! - res := res_tmp as mal.MalList + res := res_tmp as mal.List fn_ := res.list[0].fn_()! return fn_(res.rest()) } @@ -28,9 +28,9 @@ fn eval(ast mal.MalType, env RepEnv) !mal.MalType { return ast } -fn eval_ast(ast mal.MalType, env RepEnv) !mal.MalType { +fn eval_ast(ast mal.Type, env RepEnv) !mal.Type { match ast { - mal.MalSymbol { + mal.Symbol { // return env[ ast.sym ] or { error( 'unknown: ${ast.sym}' ) } if res := env[ast.sym] { return res @@ -38,18 +38,18 @@ fn eval_ast(ast mal.MalType, env RepEnv) !mal.MalType { return error('unknown: ${ast.sym}') } } - mal.MalList { - return mal.MalList{ast.list.map(eval(it, env)!)} + mal.List { + return mal.List{ast.list.map(eval(it, env)!)} } - mal.MalVector { - return mal.MalVector{ast.vec.map(eval(it, env)!)} + mal.Vector { + return mal.Vector{ast.vec.map(eval(it, env)!)} } - mal.MalHashmap { - mut hm := map[string]mal.MalType{} + mal.Hashmap { + mut hm := map[string]mal.Type{} for key in ast.hm.keys() { hm[key] = eval(ast.hm[key] or { panic('') }, env)! } - return mal.MalHashmap{hm} + return mal.Hashmap{hm} } else { return ast @@ -57,28 +57,28 @@ fn eval_ast(ast mal.MalType, env RepEnv) !mal.MalType { } } -fn rep_print(ast mal.MalType) string { +fn rep_print(ast mal.Type) string { return mal.pr_str(ast, true) } fn rep(line string, env RepEnv) string { - ast := rep_read(line) or { + ast := rep_read(line) or { return if err.msg() == 'no form' { '' } else { 'READ ERROR: ${err}' } - } - $if tokenise ? { + } + $if ast ? { println('AST:\n${ast}') } - res := eval(ast, env) or { return 'EVAL ERROR: ${err}' } - return rep_print(res) + res := eval(ast, env) or { return 'EVAL ERROR: ${err}' } + return rep_print(res) } -fn get_args(op string, args mal.MalList) !(i64, i64) { +fn get_args(op string, args mal.List) !(i64, i64) { if args.len() != 2 { return error('${op}: takes 2 args') } arg0, arg1 := args.list[0], args.list[1] - if arg0 is mal.MalInt { - if arg1 is mal.MalInt { + if arg0 is mal.Int { + if arg1 is mal.Int { return arg0.val, arg1.val } } @@ -87,21 +87,21 @@ fn get_args(op string, args mal.MalList) !(i64, i64) { fn main() { env := RepEnv({ - '+': mal.MalType(mal.MalFn{fn (args mal.MalList) !mal.MalType { + '+': mal.Type(mal.Fn{fn (args mal.List) !mal.Type { a, b := get_args('+', args)! - return mal.MalInt{a + b} + return mal.Int{a + b} }}) - '-': mal.MalType(mal.MalFn{fn (args mal.MalList) !mal.MalType { + '-': mal.Type(mal.Fn{fn (args mal.List) !mal.Type { a, b := get_args('-', args)! - return mal.MalInt{a - b} + return mal.Int{a - b} }}) - '*': mal.MalType(mal.MalFn{fn (args mal.MalList) !mal.MalType { + '*': mal.Type(mal.Fn{fn (args mal.List) !mal.Type { a, b := get_args('*', args)! - return mal.MalInt{a * b} + return mal.Int{a * b} }}) - '/': mal.MalType(mal.MalFn{fn (args mal.MalList) !mal.MalType { + '/': mal.Type(mal.Fn{fn (args mal.List) !mal.Type { a, b := get_args('/', args)! - return mal.MalInt{a / b} + return mal.Int{a / b} }}) }) diff --git a/impls/v/step3_env.v b/impls/v/step3_env.v index 85150210ed..2e73dded96 100644 --- a/impls/v/step3_env.v +++ b/impls/v/step3_env.v @@ -1,13 +1,13 @@ import mal import readline { read_line } -fn rep_read(input string) !mal.MalType { +fn rep_read(input string) !mal.Type { return mal.read_str(input)! } -fn eval(ast mal.MalType, mut env mal.Env) !mal.MalType { +fn eval(ast mal.Type, mut env mal.Env) !mal.Type { match ast { - mal.MalList { + mal.List { first := ast.first() or { return ast } // return empty list match first.sym() or { '' } { 'def!' { @@ -32,9 +32,9 @@ fn eval(ast mal.MalType, mut env mal.Env) !mal.MalType { } else { // regular list apply // BUG: https://github.com/vlang/v/issues/17156 - // res := eval_ast(ast, env)! as mal.MalList + // res := eval_ast(ast, env)! as mal.List res_tmp := eval_ast(ast, mut env)! - res := res_tmp as mal.MalList + res := res_tmp as mal.List fn_ := res.list[0].fn_()! return fn_(res.rest()) } @@ -47,23 +47,23 @@ fn eval(ast mal.MalType, mut env mal.Env) !mal.MalType { return ast } -fn eval_ast(ast mal.MalType, mut env mal.Env) !mal.MalType { +fn eval_ast(ast mal.Type, mut env mal.Env) !mal.Type { match ast { - mal.MalSymbol { + mal.Symbol { return env.get(ast.sym)! } - mal.MalList { - return mal.MalList{ast.list.map(eval(it, mut env)!)} + mal.List { + return mal.List{ast.list.map(eval(it, mut env)!)} } - mal.MalVector { - return mal.MalVector{ast.vec.map(eval(it, mut env)!)} + mal.Vector { + return mal.Vector{ast.vec.map(eval(it, mut env)!)} } - mal.MalHashmap { - mut hm := map[string]mal.MalType{} + mal.Hashmap { + mut hm := map[string]mal.Type{} for key in ast.hm.keys() { hm[key] = eval(ast.hm[key] or { panic('') }, mut env)! } - return mal.MalHashmap{hm} + return mal.Hashmap{hm} } else { return ast @@ -71,28 +71,28 @@ fn eval_ast(ast mal.MalType, mut env mal.Env) !mal.MalType { } } -fn rep_print(ast mal.MalType) string { +fn rep_print(ast mal.Type) string { return mal.pr_str(ast, true) } fn rep(line string, mut env mal.Env) string { - ast := rep_read(line) or { + ast := rep_read(line) or { return if err.msg() == 'no form' { '' } else { 'READ ERROR: ${err}' } - } - $if tokenise ? { + } + $if ast ? { println('AST:\n${ast}') } - res := eval(ast, mut env) or { return 'EVAL ERROR: ${err}' } - return rep_print(res) + res := eval(ast, mut env) or { return 'EVAL ERROR: ${err}' } + return rep_print(res) } -fn get_args(op string, args mal.MalList) !(i64, i64) { +fn get_args(op string, args mal.List) !(i64, i64) { if args.len() != 2 { return error('${op}: takes 2 args') } arg0, arg1 := args.list[0], args.list[1] - if arg0 is mal.MalInt { - if arg1 is mal.MalInt { + if arg0 is mal.Int { + if arg1 is mal.Int { return arg0.val, arg1.val } } @@ -101,21 +101,21 @@ fn get_args(op string, args mal.MalList) !(i64, i64) { fn main() { mut env := mal.Env{} - env.set('+', mal.MalType(mal.MalFn{fn (args mal.MalList) !mal.MalType { + env.set('+', mal.Type(mal.Fn{fn (args mal.List) !mal.Type { a, b := get_args('+', args)! - return mal.MalInt{a + b} + return mal.Int{a + b} }})) - env.set('-', mal.MalType(mal.MalFn{fn (args mal.MalList) !mal.MalType { + env.set('-', mal.Type(mal.Fn{fn (args mal.List) !mal.Type { a, b := get_args('-', args)! - return mal.MalInt{a - b} + return mal.Int{a - b} }})) - env.set('*', mal.MalType(mal.MalFn{fn (args mal.MalList) !mal.MalType { + env.set('*', mal.Type(mal.Fn{fn (args mal.List) !mal.Type { a, b := get_args('*', args)! - return mal.MalInt{a * b} + return mal.Int{a * b} }})) - env.set('/', mal.MalType(mal.MalFn{fn (args mal.MalList) !mal.MalType { + env.set('/', mal.Type(mal.Fn{fn (args mal.List) !mal.Type { a, b := get_args('/', args)! - return mal.MalInt{a / b} + return mal.Int{a / b} }})) for { From 48b22e36000a9ec24dceb235c41c6ea02e2aff88 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Sun, 5 Feb 2023 22:20:25 +0000 Subject: [PATCH 08/30] v: bugs (env, reader, unescape), added type convertion, comparison --- impls/v/mal/env.v | 3 +- impls/v/mal/printer.v | 15 ++++-- impls/v/mal/reader.v | 2 +- impls/v/mal/types.v | 110 ++++++++++++++++++++++++++++++++++++++++-- impls/v/step3_env.v | 2 +- 5 files changed, 121 insertions(+), 11 deletions(-) diff --git a/impls/v/mal/env.v b/impls/v/mal/env.v index 452a06a316..3ef1c6da4c 100644 --- a/impls/v/mal/env.v +++ b/impls/v/mal/env.v @@ -1,5 +1,6 @@ module mal +[heap] pub struct Env { pub: outer &Env = unsafe { nil } @@ -9,7 +10,7 @@ pub mut: pub fn mk_env(outer &Env) Env { return Env{ - outer: unsafe { outer } + outer: outer } } diff --git a/impls/v/mal/printer.v b/impls/v/mal/printer.v index 65888cef2e..e24c6accd1 100644 --- a/impls/v/mal/printer.v +++ b/impls/v/mal/printer.v @@ -1,6 +1,7 @@ module mal import maps +import regex pub fn pr_str(ast Type, readable bool) string { return match ast { @@ -18,7 +19,7 @@ pub fn pr_str(ast Type, readable bool) string { } } Keyword { - ':${ast.key}' + ':${ast.kw}' } Nil { 'nil' @@ -57,8 +58,12 @@ fn escape(str string) string { } fn unescape(str string) string { - return str - .replace('\\n', '\n') - .replace('\\"', '"') - .replace('\\\\', '\\') + mut re := regex.regex_opt('\\\\(.)') or { panic(err) } + return re.replace_by_fn(str, fn (re regex.RE, str string, start int, end int) string { + g := re.get_group_by_id(str, 0) + return match g { + 'n' { '\n' } + else { g } + } + }) } diff --git a/impls/v/mal/reader.v b/impls/v/mal/reader.v index a3f5b0003a..62c1f7f4bf 100644 --- a/impls/v/mal/reader.v +++ b/impls/v/mal/reader.v @@ -41,7 +41,7 @@ fn hash_list(list []Type) !map[string]Type { if key is String { hash['"${key.val}"'] = val } else if key is Keyword { - hash[':${key.key}'] = val + hash[':${key.kw}'] = val } else { return error('bad hashmap key') } diff --git a/impls/v/mal/types.v b/impls/v/mal/types.v index 0f1e979079..79e897af49 100644 --- a/impls/v/mal/types.v +++ b/impls/v/mal/types.v @@ -13,12 +13,42 @@ type Type = False | True | Vector +fn implicit_conv(a Type, b Type) !(Type, Type) { + // same type + if a.type_idx() == b.type_idx() { + return a, b + } + // automatic conversion + if a is Int && b is Float { + return Float{a.val}, b + } + if a is Float && b is Int { + return a, Float{b.val} + } + if a is Vector && b is List { + return List{a.vec}, b + } + if a is List && b is Vector { + return a, List{b.vec} + } + // fail + return error('type mismatch') +} + +fn make_bool(cond bool) Type { + return if cond { True{} } else { False{} } +} + pub fn (t Type) truthy() bool { return !t.falsey() } pub fn (t Type) falsey() bool { - return t is False || t is Nil + return t in [False, Nil] +} + +pub fn (t Type) numeric() bool { + return t in [Int, Float] } pub fn (t Type) sym() !string { @@ -43,10 +73,80 @@ pub fn (t Type) list_or_vec() ![]Type { return match t { List { t.list } Vector { t.vec } + Nil { []Type{} } + //Nil { if allow_nil { []Type{} } else { error('list/vector expected') } } else { error('list/vector expected') } } } +pub fn (t Type) eq(o Type) bool { + a, b := implicit_conv(t, o) or { return false } + match a { + List { + if a.list.len != (b as List).list.len { + return false + } + for i, aa in a.list { + if !aa.eq( (b as List).list[i] ) { + return false + } + } + return true + } + Vector { + if a.vec.len != (b as Vector).vec.len { + return false + } + for i, aa in a.vec { + if !aa.eq( (b as Vector).vec[i] ) { + return false + } + } + return true + } + Int { + return a.val == (b as Int).val + } + Float { + return a.val == (b as Float).val + } + String { + return a.val == (b as String).val + } + True { + return b is True + } + False { + return b is False + } + Nil { + return b is Nil + } + Keyword { + return a.kw == (b as Keyword).kw + } + Symbol { + return a.sym == (b as Symbol).sym + } + Hashmap { + return a.hm == (b as Hashmap).hm + } + Fn { + return a.f == (b as Fn).f + } + } +} + +pub fn (t Type) lt(o Type) !bool { + a, b := implicit_conv(t, o)! + return match a { + Int { a.val < (b as Int).val } + Float { a.val < (b as Float).val } + String { a.val < (b as String).val } + else { error('invalid comparison') } + } +} + // -- pub struct Int { @@ -72,7 +172,7 @@ pub: pub struct Keyword { pub: - key string + kw string } // -- @@ -110,7 +210,11 @@ pub fn (l List) last() !Type { } pub fn (l List) rest() List { - return if l.list.len > 0 { List{l.list[1..]} } else { List{} } + return if l.list.len > 1 { List{l.list[1..]} } else { List{} } +} + +pub fn (l List) from(n int) List { + return if l.list.len > n { List{l.list[n..]} } else { List{} } } pub fn (l List) len() int { diff --git a/impls/v/step3_env.v b/impls/v/step3_env.v index 2e73dded96..7cec7a2031 100644 --- a/impls/v/step3_env.v +++ b/impls/v/step3_env.v @@ -17,7 +17,7 @@ fn eval(ast mal.Type, mut env mal.Env) !mal.Type { } 'let*' { ast.nth(2) or { return error('let*: missing param') } - mut new_env := mal.mk_env(env) + mut new_env := mal.mk_env(&env) mut pairs := ast.list[1].list_or_vec() or { return error('let*: ${err}') } pairs = pairs[0..] // copy if pairs.len % 2 == 1 { From 79ee84ac10c28b7d45a361e8ee79a218cce21ebe Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Sun, 5 Feb 2023 22:22:48 +0000 Subject: [PATCH 09/30] v: step 4 --- impls/v/mal/core.v | 90 ++++++++++++++++++++++++ impls/v/mal/types.v | 126 ++++++++++++++++----------------- impls/v/step4_if_fn_do.v | 146 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 299 insertions(+), 63 deletions(-) create mode 100644 impls/v/mal/core.v create mode 100644 impls/v/step4_if_fn_do.v diff --git a/impls/v/mal/core.v b/impls/v/mal/core.v new file mode 100644 index 0000000000..a5f54cc466 --- /dev/null +++ b/impls/v/mal/core.v @@ -0,0 +1,90 @@ +module mal + +struct NSFn { +pub: + sym string + fn_ FnFn +} + +fn get_args(op string, args List) !(i64, i64) { + args.nth(1) or { return error('${op}: takes 2 args') } + a := args.list[0].int_() or { return error('${op}: ${err}') } + b := args.list[1].int_() or { return error('${op}: ${err}') } + return a, b +} + +pub fn get_core() []NSFn { + return [ + NSFn{'+', fn (args List) !Type { + a, b := get_args('+', args)! + return Int{a + b} + }}, + NSFn{'-', fn (args List) !Type { + a, b := get_args('-', args)! + return Int{a - b} + }}, + NSFn{'*', fn (args List) !Type { + a, b := get_args('*', args)! + return Int{a * b} + }}, + NSFn{'/', fn (args List) !Type { + a, b := get_args('/', args)! + return Int{a / b} + }}, + NSFn{'list', fn (args List) !Type { + return args + }}, + NSFn{'list?', fn (args List) !Type { + first := args.nth(0) or { return False{} } + return if first is List { True{} } else { False{} } + }}, + NSFn{'empty?', fn (args List) !Type { + first := args.nth(0) or { return error('empty?: missing param') } + seq := first.list_or_vec() or { return error('empty?: ${err}') } + return if seq.len == 0 { True{} } else { False{} } + }}, + NSFn{'count', fn (args List) !Type { + first := args.nth(0) or { return error('count: missing param') } + seq := first.list_or_vec() or { return error('count: ${err}') } + return Int{seq.len} + }}, + NSFn{'=', fn (args List) !Type { + b := args.nth(1) or { return error('=: missing param') } + return make_bool(args.list[0].eq(b)) + }}, + NSFn{'<', fn (args List) !Type { + b := args.nth(1) or { return error('<: missing param') } + res := args.list[0].lt(b) or { return error('<: ${err}') } + return make_bool(res) + }}, + NSFn{'<=', fn (args List) !Type { + b := args.nth(1) or { return error('<=: missing param') } + res := args.list[0].lt(b) or { return error('<=: ${err}') } + return make_bool(res || args.list[0].eq(b)) + }}, + NSFn{'>', fn (args List) !Type { + b := args.nth(1) or { return error('>: missing param') } + res := args.list[0].lt(b) or { return error('>: ${err}') } + return make_bool(!res && !args.list[0].eq(b)) + }}, + NSFn{'>=', fn (args List) !Type { + b := args.nth(1) or { return error('>=: missing param') } + res := args.list[0].lt(b) or { return error('>=: ${err}') } + return make_bool(!res) + }}, + NSFn{'pr-str', fn (args List) !Type { + return String{args.list.map(pr_str(it, true)).join(' ')} + }}, + NSFn{'str', fn (args List) !Type { + return String{args.list.map(pr_str(it, false)).join('')} + }}, + NSFn{'prn', fn (args List) !Type { + println(args.list.map(pr_str(it, true)).join(' ')) + return Nil{} + }}, + NSFn{'println', fn (args List) !Type { + println(args.list.map(pr_str(it, false)).join(' ')) + return Nil{} + }}, + ] +} diff --git a/impls/v/mal/types.v b/impls/v/mal/types.v index 79e897af49..509da867dd 100644 --- a/impls/v/mal/types.v +++ b/impls/v/mal/types.v @@ -25,12 +25,12 @@ fn implicit_conv(a Type, b Type) !(Type, Type) { if a is Float && b is Int { return a, Float{b.val} } - if a is Vector && b is List { - return List{a.vec}, b - } - if a is List && b is Vector { - return a, List{b.vec} - } + if a is Vector && b is List { + return List{a.vec}, b + } + if a is List && b is Vector { + return a, List{b.vec} + } // fail return error('type mismatch') } @@ -73,68 +73,68 @@ pub fn (t Type) list_or_vec() ![]Type { return match t { List { t.list } Vector { t.vec } - Nil { []Type{} } - //Nil { if allow_nil { []Type{} } else { error('list/vector expected') } } + Nil { []Type{} } + // Nil { if allow_nil { []Type{} } else { error('list/vector expected') } } else { error('list/vector expected') } } } pub fn (t Type) eq(o Type) bool { - a, b := implicit_conv(t, o) or { return false } - match a { - List { - if a.list.len != (b as List).list.len { - return false - } - for i, aa in a.list { - if !aa.eq( (b as List).list[i] ) { - return false - } - } - return true - } - Vector { - if a.vec.len != (b as Vector).vec.len { - return false - } - for i, aa in a.vec { - if !aa.eq( (b as Vector).vec[i] ) { - return false - } - } - return true - } - Int { - return a.val == (b as Int).val - } - Float { - return a.val == (b as Float).val - } - String { - return a.val == (b as String).val - } - True { - return b is True - } - False { - return b is False - } - Nil { - return b is Nil - } - Keyword { - return a.kw == (b as Keyword).kw - } - Symbol { - return a.sym == (b as Symbol).sym - } - Hashmap { - return a.hm == (b as Hashmap).hm - } - Fn { - return a.f == (b as Fn).f - } - } + a, b := implicit_conv(t, o) or { return false } + match a { + List { + if a.list.len != (b as List).list.len { + return false + } + for i, aa in a.list { + if !aa.eq((b as List).list[i]) { + return false + } + } + return true + } + Vector { + if a.vec.len != (b as Vector).vec.len { + return false + } + for i, aa in a.vec { + if !aa.eq((b as Vector).vec[i]) { + return false + } + } + return true + } + Int { + return a.val == (b as Int).val + } + Float { + return a.val == (b as Float).val + } + String { + return a.val == (b as String).val + } + True { + return b is True + } + False { + return b is False + } + Nil { + return b is Nil + } + Keyword { + return a.kw == (b as Keyword).kw + } + Symbol { + return a.sym == (b as Symbol).sym + } + Hashmap { + return a.hm == (b as Hashmap).hm + } + Fn { + return a.f == (b as Fn).f + } + } } pub fn (t Type) lt(o Type) !bool { diff --git a/impls/v/step4_if_fn_do.v b/impls/v/step4_if_fn_do.v new file mode 100644 index 0000000000..0577c6ab96 --- /dev/null +++ b/impls/v/step4_if_fn_do.v @@ -0,0 +1,146 @@ +import mal +import readline { read_line } + +fn rep_read(input string) !mal.Type { + return mal.read_str(input)! +} + +fn eval(ast mal.Type, mut env mal.Env) !mal.Type { + match ast { + mal.List { + first := ast.first() or { return ast } // return empty list + match first.sym() or { '' } { + 'def!' { + ast.nth(2) or { return error('def!: missing param') } + sym := ast.list[1].sym() or { return error('def!: ${err}') } + return env.set(sym, eval(ast.list[2], mut env)!) + } + 'let*' { + ast.nth(2) or { return error('let*: missing param') } + mut new_env := mal.mk_env(&env) + tmp := ast.list[1].list_or_vec() or { return error('let*: ${err}') } + mut pairs := tmp[0..] // copy + if pairs.len % 2 == 1 { + return error('let*: extra binding param') + } + for pairs.len > 0 { + sym := pairs[0].sym() or { return error('let*: ${err}') } + new_env.set(sym, eval(pairs[1], mut new_env)!) + pairs = pairs[2..] + } + return eval(ast.list[2], mut new_env)! + } + 'do' { + // BUG: https://github.com/vlang/v/issues/17156 + // res := eval_ast(ast.rest(), mut env)! as mal.List + res_tmp := eval_ast(ast.rest(), mut env)! + res := res_tmp as mal.List + return res.last() or { mal.Nil{} } + } + 'if' { + ast.nth(2) or { return error('if: missing param') } + res := eval(ast.list[1], mut env)! + clause_n := if res.truthy() { 2 } else { 3 } + clause := ast.nth(clause_n) or { return mal.Nil{} } + return eval(clause, mut env)! + } + 'fn*' { + ast.nth(2) or { return error('fn*: missing param') } + binds := ast.list[1].list_or_vec() or { return error('fn*: ${err}') } + syms := binds.map(it.sym() or { return error('fn*: ${err}') }) + body := ast.list[2] + for i, sym in syms { + if sym == '&' && syms.len != i + 2 { + return error('fn*: & has 1 arg') + } + } + closure := fn [env, syms, body] (args mal.List) !mal.Type { + mut new_env := mal.mk_env(&env) + for i, sym in syms { + if sym == '&' { + new_env.set(syms[i + 1], args.from(i)) + break + } else { + new_env.set(sym, args.nth(i) or { mal.Nil{} }) + } + } + return eval(body, mut new_env)! + } + return mal.Fn{closure} + } + else { // regular list apply + // BUG: https://github.com/vlang/v/issues/17156 + // res := eval_ast(ast, env)! as mal.List + res_tmp := eval_ast(ast, mut env)! + res := res_tmp as mal.List + fn_ := res.list[0].fn_()! + return fn_(res.rest()) + } + } + } + else { + return eval_ast(ast, mut env)! + } + } + return ast +} + +fn eval_ast(ast mal.Type, mut env mal.Env) !mal.Type { + match ast { + mal.Symbol { + return env.get(ast.sym)! + } + mal.List { + return mal.List{ast.list.map(eval(it, mut env)!)} + } + mal.Vector { + return mal.Vector{ast.vec.map(eval(it, mut env)!)} + } + mal.Hashmap { + mut hm := map[string]mal.Type{} + for key in ast.hm.keys() { + hm[key] = eval(ast.hm[key] or { panic('') }, mut env)! + } + return mal.Hashmap{hm} + } + else { + return ast + } + } +} + +fn rep_print(ast mal.Type) string { + return mal.pr_str(ast, true) +} + +fn rep(line string, mut env mal.Env) string { + ast := rep_read(line) or { + return if err.msg() == 'no form' { '' } else { 'READ ERROR: ${err}' } + } + $if ast ? { + println('AST:\n${ast}') + } + res := eval(ast, mut env) or { return 'EVAL ERROR: ${err}' } + return rep_print(res) +} + +fn main() { + // outer-most env + mut env := mal.Env{} + for nsfn in mal.get_core() { + env.set(nsfn.sym, mal.Fn{nsfn.fn_}) + } + + rep('(def! not (fn* (a) (if a false true)))', mut env) + + for { + line := read_line('user> ') or { + println('') + break + } + out := rep(line, mut env) + if out.len > 0 { + println(out) + } + } +} From de0b5a117b3aa77a1b7d7a39e69be60b1db86148 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Mon, 6 Feb 2023 18:08:50 +0000 Subject: [PATCH 10/30] v: diferentiate between Fn and FnDef, add Closure --- impls/v/mal/core.v | 2 +- impls/v/mal/env.v | 15 ++++- impls/v/mal/printer.v | 2 +- impls/v/mal/types.v | 23 +++++-- impls/v/step2_eval.v | 25 +++----- impls/v/step3_env.v | 61 +++++++++---------- impls/v/step4_if_fn_do.v | 126 ++++++++++++++++++--------------------- 7 files changed, 126 insertions(+), 128 deletions(-) diff --git a/impls/v/mal/core.v b/impls/v/mal/core.v index a5f54cc466..75cc9f453b 100644 --- a/impls/v/mal/core.v +++ b/impls/v/mal/core.v @@ -3,7 +3,7 @@ module mal struct NSFn { pub: sym string - fn_ FnFn + f FnDef } fn get_args(op string, args List) !(i64, i64) { diff --git a/impls/v/mal/env.v b/impls/v/mal/env.v index 3ef1c6da4c..6dbe9e9520 100644 --- a/impls/v/mal/env.v +++ b/impls/v/mal/env.v @@ -8,12 +8,23 @@ pub mut: data map[string]Type = map[string]Type{} } -pub fn mk_env(outer &Env) Env { - return Env{ +pub fn mk_env(outer &Env) &Env { + return &Env{ outer: outer } } +pub fn (mut e Env) bind(syms []string, args List) { + for i, sym in syms { + if sym == '&' { + e.set(syms[i + 1], args.from(i)) + break + } else { + e.set(sym, args.nth(i) or { mal.Nil{} }) + } + } +} + pub fn (mut e Env) set(sym string, val Type) Type { e.data[sym] = val return val diff --git a/impls/v/mal/printer.v b/impls/v/mal/printer.v index e24c6accd1..d1a32d618f 100644 --- a/impls/v/mal/printer.v +++ b/impls/v/mal/printer.v @@ -44,7 +44,7 @@ pub fn pr_str(ast Type, readable bool) string { return '${k} ${pr_str(v, readable)}' }).join(' ') + '}' } - Fn { + Fn, Closure { '#' } } diff --git a/impls/v/mal/types.v b/impls/v/mal/types.v index 509da867dd..8db91b2797 100644 --- a/impls/v/mal/types.v +++ b/impls/v/mal/types.v @@ -3,6 +3,7 @@ module mal type Type = False | Float | Fn + | Closure | Hashmap | Int | Keyword @@ -55,9 +56,7 @@ pub fn (t Type) sym() !string { return if t is Symbol { t.sym } else { error('symbol expected') } } -type FnFn = fn (args List) !Type - -pub fn (t Type) fn_() !FnFn { +pub fn (t Type) fn_() !FnDef { return if t is Fn { t.f } else { error('function expected') } } @@ -134,6 +133,11 @@ pub fn (t Type) eq(o Type) bool { Fn { return a.f == (b as Fn).f } + Closure { + return a.env == (b as Closure).env && + a.ast == (b as Closure).ast && + a.params == (b as Closure).params + } } } @@ -241,9 +245,16 @@ pub: // -- +type FnDef = fn (args List) !Type + pub struct Fn { pub: - // mut: - // env Env - f FnFn + f FnDef +} + +pub struct Closure { +pub: + ast Type + params []string + env &Env } diff --git a/impls/v/step2_eval.v b/impls/v/step2_eval.v index 1e94c97483..3f17b81866 100644 --- a/impls/v/step2_eval.v +++ b/impls/v/step2_eval.v @@ -8,24 +8,17 @@ fn rep_read(input string) !mal.Type { } fn eval(ast mal.Type, env RepEnv) !mal.Type { - match ast { - mal.List { - if ast.list.len == 0 { - return ast - } else { - // BUG: https://github.com/vlang/v/issues/17156 - // res := eval_ast(ast, env)! as mal.List - res_tmp := eval_ast(ast, env)! - res := res_tmp as mal.List - fn_ := res.list[0].fn_()! - return fn_(res.rest()) - } - } - else { - return eval_ast(ast, env)! + if ast is mal.List { + if ast.list.len == 0 { + return ast + } else { + res := eval_ast(ast, env)! as mal.List + f := res.list[0].fn_()! + return f(res.rest()) } + } else { + return eval_ast(ast, env)! } - return ast } fn eval_ast(ast mal.Type, env RepEnv) !mal.Type { diff --git a/impls/v/step3_env.v b/impls/v/step3_env.v index 7cec7a2031..6231c295c2 100644 --- a/impls/v/step3_env.v +++ b/impls/v/step3_env.v @@ -6,45 +6,38 @@ fn rep_read(input string) !mal.Type { } fn eval(ast mal.Type, mut env mal.Env) !mal.Type { - match ast { - mal.List { - first := ast.first() or { return ast } // return empty list - match first.sym() or { '' } { - 'def!' { - ast.nth(2) or { return error('def!: missing param') } - sym := ast.list[1].sym() or { return error('def!: ${err}') } - return env.set(sym, eval(ast.list[2], mut env)!) - } - 'let*' { - ast.nth(2) or { return error('let*: missing param') } - mut new_env := mal.mk_env(&env) - mut pairs := ast.list[1].list_or_vec() or { return error('let*: ${err}') } - pairs = pairs[0..] // copy - if pairs.len % 2 == 1 { - return error('let*: extra binding param') - } - for pairs.len > 0 { - sym := pairs[0].sym() or { return error('let*: ${err}') } - new_env.set(sym, eval(pairs[1], mut new_env)!) - pairs = pairs[2..] - } - return eval(ast.list[2], mut new_env)! + if ast is mal.List { + first := ast.first() or { return ast } // return empty list + match first.sym() or { '' } { + 'def!' { + ast.nth(2) or { return error('def!: missing param') } + sym := ast.list[1].sym() or { return error('def!: ${err}') } + return env.set(sym, eval(ast.list[2], mut env)!) + } + 'let*' { + ast.nth(2) or { return error('let*: missing param') } + mut new_env := mal.mk_env(&env) + mut pairs := ast.list[1].list_or_vec() or { return error('let*: ${err}') } + pairs = pairs[0..] // copy + if pairs.len % 2 == 1 { + return error('let*: extra binding param') } - else { // regular list apply - // BUG: https://github.com/vlang/v/issues/17156 - // res := eval_ast(ast, env)! as mal.List - res_tmp := eval_ast(ast, mut env)! - res := res_tmp as mal.List - fn_ := res.list[0].fn_()! - return fn_(res.rest()) + for pairs.len > 0 { + sym := pairs[0].sym() or { return error('let*: ${err}') } + new_env.set(sym, eval(pairs[1], mut new_env)!) + pairs = pairs[2..] } + return eval(ast.list[2], mut new_env)! + } + else { // regular list apply + res := eval_ast(ast, mut env)! as mal.List + f := res.list[0].fn_()! + return f(res.rest()) } } - else { - return eval_ast(ast, mut env)! - } + } else { + return eval_ast(ast, mut env)! } - return ast } fn eval_ast(ast mal.Type, mut env mal.Env) !mal.Type { diff --git a/impls/v/step4_if_fn_do.v b/impls/v/step4_if_fn_do.v index 0577c6ab96..e22cd9a41f 100644 --- a/impls/v/step4_if_fn_do.v +++ b/impls/v/step4_if_fn_do.v @@ -6,83 +6,73 @@ fn rep_read(input string) !mal.Type { } fn eval(ast mal.Type, mut env mal.Env) !mal.Type { - match ast { - mal.List { - first := ast.first() or { return ast } // return empty list - match first.sym() or { '' } { - 'def!' { - ast.nth(2) or { return error('def!: missing param') } - sym := ast.list[1].sym() or { return error('def!: ${err}') } - return env.set(sym, eval(ast.list[2], mut env)!) - } - 'let*' { - ast.nth(2) or { return error('let*: missing param') } - mut new_env := mal.mk_env(&env) - tmp := ast.list[1].list_or_vec() or { return error('let*: ${err}') } - mut pairs := tmp[0..] // copy - if pairs.len % 2 == 1 { - return error('let*: extra binding param') - } - for pairs.len > 0 { - sym := pairs[0].sym() or { return error('let*: ${err}') } - new_env.set(sym, eval(pairs[1], mut new_env)!) - pairs = pairs[2..] - } - return eval(ast.list[2], mut new_env)! + if ast is mal.List { + first := ast.first() or { return ast } // return empty list + match first.sym() or { '' } { + 'def!' { + ast.nth(2) or { return error('def!: missing param') } + sym := ast.list[1].sym() or { return error('def!: ${err}') } + return env.set(sym, eval(ast.list[2], mut env)!) + } + 'let*' { + ast.nth(2) or { return error('let*: missing param') } + mut new_env := mal.mk_env(&env) + tmp := ast.list[1].list_or_vec() or { return error('let*: ${err}') } + mut pairs := tmp[0..] // copy + if pairs.len % 2 == 1 { + return error('let*: extra binding param') } - 'do' { - // BUG: https://github.com/vlang/v/issues/17156 - // res := eval_ast(ast.rest(), mut env)! as mal.List - res_tmp := eval_ast(ast.rest(), mut env)! - res := res_tmp as mal.List - return res.last() or { mal.Nil{} } + for pairs.len > 0 { + sym := pairs[0].sym() or { return error('let*: ${err}') } + new_env.set(sym, eval(pairs[1], mut new_env)!) + pairs = pairs[2..] } - 'if' { - ast.nth(2) or { return error('if: missing param') } - res := eval(ast.list[1], mut env)! - clause_n := if res.truthy() { 2 } else { 3 } - clause := ast.nth(clause_n) or { return mal.Nil{} } - return eval(clause, mut env)! + return eval(ast.list[2], mut new_env)! + } + 'do' { + res := eval_ast(ast.rest(), mut env)! as mal.List + return res.last() or { mal.Nil{} } + } + 'if' { + ast.nth(2) or { return error('if: missing param') } + res := eval(ast.list[1], mut env)! + clause_n := if res.truthy() { 2 } else { 3 } + clause := ast.nth(clause_n) or { return mal.Nil{} } + return eval(clause, mut env)! + } + 'fn*' { + ast.nth(2) or { return error('fn*: missing param') } + binds := ast.list[1].list_or_vec() or { return error('fn*: ${err}') } + syms := binds.map(it.sym() or { return error('fn*: ${err}') }) + body := ast.list[2] + for i, sym in syms { + if sym == '&' && syms.len != i + 2 { + return error('fn*: & has 1 arg') + } } - 'fn*' { - ast.nth(2) or { return error('fn*: missing param') } - binds := ast.list[1].list_or_vec() or { return error('fn*: ${err}') } - syms := binds.map(it.sym() or { return error('fn*: ${err}') }) - body := ast.list[2] + closure := fn [env, syms, body] (args mal.List) !mal.Type { + mut new_env := mal.mk_env(&env) for i, sym in syms { - if sym == '&' && syms.len != i + 2 { - return error('fn*: & has 1 arg') - } - } - closure := fn [env, syms, body] (args mal.List) !mal.Type { - mut new_env := mal.mk_env(&env) - for i, sym in syms { - if sym == '&' { - new_env.set(syms[i + 1], args.from(i)) - break - } else { - new_env.set(sym, args.nth(i) or { mal.Nil{} }) - } + if sym == '&' { + new_env.set(syms[i + 1], args.from(i)) + break + } else { + new_env.set(sym, args.nth(i) or { mal.Nil{} }) } - return eval(body, mut new_env)! } - return mal.Fn{closure} - } - else { // regular list apply - // BUG: https://github.com/vlang/v/issues/17156 - // res := eval_ast(ast, env)! as mal.List - res_tmp := eval_ast(ast, mut env)! - res := res_tmp as mal.List - fn_ := res.list[0].fn_()! - return fn_(res.rest()) + return eval(body, mut new_env)! } + return mal.Fn{closure} + } + else { // regular list apply + res := eval_ast(ast, mut env)! as mal.List + f := res.list[0].fn_()! + return f(res.rest()) } } - else { - return eval_ast(ast, mut env)! - } + } else { + return eval_ast(ast, mut env)! } - return ast } fn eval_ast(ast mal.Type, mut env mal.Env) !mal.Type { @@ -128,7 +118,7 @@ fn main() { // outer-most env mut env := mal.Env{} for nsfn in mal.get_core() { - env.set(nsfn.sym, mal.Fn{nsfn.fn_}) + env.set(nsfn.sym, mal.Fn{nsfn.f}) } rep('(def! not (fn* (a) (if a false true)))', mut env) From 0f565ea5fa7c7713b179830c707be35b5ef9a69c Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Mon, 6 Feb 2023 18:09:25 +0000 Subject: [PATCH 11/30] v: step 5 --- impls/v/step5_tco.v | 144 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 impls/v/step5_tco.v diff --git a/impls/v/step5_tco.v b/impls/v/step5_tco.v new file mode 100644 index 0000000000..7daf9496a6 --- /dev/null +++ b/impls/v/step5_tco.v @@ -0,0 +1,144 @@ +import mal +import readline { read_line } + +fn rep_read(input string) !mal.Type { + return mal.read_str(input)! +} + +fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { + mut ast := unsafe { &ast_ } + mut env := unsafe { &env_ } + for true { + if ast is mal.List { + node := ast as mal.List + first := node.first() or { return *ast } // return empty list + match first.sym() or { '' } { + 'def!' { + node.nth(2) or { return error('def!: missing param') } + sym := node.list[1].sym() or { return error('def!: ${err}') } + return env.set(sym, eval(node.list[2], mut env)!) + } + 'let*' { + node.nth(2) or { return error('let*: missing param') } + env = unsafe { mal.mk_env(env) } // TCO + tmp := node.list[1].list_or_vec() or { return error('let*: ${err}') } + mut pairs := tmp[0..] // copy + if pairs.len % 2 == 1 { + return error('let*: extra binding param') + } + for pairs.len > 0 { + sym := pairs[0].sym() or { return error('let*: ${err}') } + env.set(sym, eval(pairs[1], mut env)!) + pairs = pairs[2..] + } + ast = unsafe { &node.list[2] } // TCO + } + 'do' { + if node.list.len > 2 { + node.list[1..node.list.len - 1].map(eval(it, mut env)!) + } else if node.list.len < 2 { + return mal.Nil{} + } + ast = unsafe { &node.list[node.list.len - 1] } // TCO + } + 'if' { + node.nth(2) or { return error('if: missing param') } + if eval(node.list[1], mut env)!.truthy() { + ast = unsafe{ &node.list[2] } // TCO + } else if node.list.len > 3 { + ast = unsafe{ &node.list[3] } // TCO + } else { + return mal.Nil{} + } + } + 'fn*' { + node.nth(2) or { return error('fn*: missing param') } + binds := node.list[1].list_or_vec() or { return error('fn*: ${err}') } + syms := binds.map(it.sym() or { return error('fn*: ${err}') }) + for i, sym in syms { + if sym == '&' && syms.len != i + 2 { + return error('fn*: & has 1 arg') + } + } + return mal.Closure{node.list[2], syms, env} + } + else { // regular list apply + res := eval_ast(ast, mut env)! as mal.List + list0 := res.list[0] + if list0 is mal.Fn { + return list0.f(res.rest())! + } else if list0 is mal.Closure { + ast = &list0.ast // TCO + env = mal.mk_env(list0.env) + env.bind(list0.params, res.rest()) + } else { + return error( 'function expected' ) + } + } + } + } else { + return eval_ast(ast, mut env)! + } + } + panic('unreachable code') +} + +fn eval_ast(ast mal.Type, mut env mal.Env) !mal.Type { + match ast { + mal.Symbol { + return env.get(ast.sym)! + } + mal.List { + return mal.List{ast.list.map(eval(it, mut env)!)} + } + mal.Vector { + return mal.Vector{ast.vec.map(eval(it, mut env)!)} + } + mal.Hashmap { + mut hm := map[string]mal.Type{} + for key in ast.hm.keys() { + hm[key] = eval(ast.hm[key] or { panic('') }, mut env)! + } + return mal.Hashmap{hm} + } + else { + return ast + } + } +} + +fn rep_print(ast mal.Type) string { + return mal.pr_str(ast, true) +} + +fn rep(line string, mut env mal.Env) string { + ast := rep_read(line) or { + return if err.msg() == 'no form' { '' } else { 'READ ERROR: ${err}' } + } + $if ast ? { + println('AST:\n${ast}') + } + res := eval(ast, mut env) or { return 'EVAL ERROR: ${err}' } + return rep_print(res) +} + +fn main() { + // outer-most env + mut env := mal.Env{} + for nsfn in mal.get_core() { + env.set(nsfn.sym, mal.Fn{nsfn.f}) + } + + rep('(def! not (fn* (a) (if a false true)))', mut env) + + for { + line := read_line('user> ') or { + println('') + break + } + out := rep(line, mut env) + if out.len > 0 { + println(out) + } + } +} From e95c3996e8f79a4c7c2a15794f06f78da10f5acd Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Tue, 7 Feb 2023 23:23:04 +0000 Subject: [PATCH 12/30] v: refactor env functions and special forms --- impls/v/mal/core.v | 152 ++++++++++++++++++--------------------- impls/v/mal/env.v | 7 +- impls/v/mal/types.v | 34 ++++----- impls/v/step3_env.v | 15 ++-- impls/v/step4_if_fn_do.v | 37 +++++----- impls/v/step5_tco.v | 80 +++++++++++---------- 6 files changed, 163 insertions(+), 162 deletions(-) diff --git a/impls/v/mal/core.v b/impls/v/mal/core.v index 75cc9f453b..530df14e10 100644 --- a/impls/v/mal/core.v +++ b/impls/v/mal/core.v @@ -1,90 +1,76 @@ module mal -struct NSFn { -pub: - sym string - f FnDef +import os + +pub fn check_args(args List, min int, max int) ! { + if args.len() < min { + return error('missing param') + } else if max >= 0 && args.len() > max { + return error('extra param') + } } -fn get_args(op string, args List) !(i64, i64) { - args.nth(1) or { return error('${op}: takes 2 args') } - a := args.list[0].int_() or { return error('${op}: ${err}') } - b := args.list[1].int_() or { return error('${op}: ${err}') } - return a, b +pub fn add_fn(mut env Env, sym string, min int, max int, f FnDef) { + env.set(sym, Fn{fn [sym, min, max, f] (args List) !Type { + check_args(args, min, max) or { return error('${sym}: ${err}') } + return f(args) or { error('${sym}: ${err}') } + }}) } -pub fn get_core() []NSFn { - return [ - NSFn{'+', fn (args List) !Type { - a, b := get_args('+', args)! - return Int{a + b} - }}, - NSFn{'-', fn (args List) !Type { - a, b := get_args('-', args)! - return Int{a - b} - }}, - NSFn{'*', fn (args List) !Type { - a, b := get_args('*', args)! - return Int{a * b} - }}, - NSFn{'/', fn (args List) !Type { - a, b := get_args('/', args)! - return Int{a / b} - }}, - NSFn{'list', fn (args List) !Type { - return args - }}, - NSFn{'list?', fn (args List) !Type { - first := args.nth(0) or { return False{} } - return if first is List { True{} } else { False{} } - }}, - NSFn{'empty?', fn (args List) !Type { - first := args.nth(0) or { return error('empty?: missing param') } - seq := first.list_or_vec() or { return error('empty?: ${err}') } - return if seq.len == 0 { True{} } else { False{} } - }}, - NSFn{'count', fn (args List) !Type { - first := args.nth(0) or { return error('count: missing param') } - seq := first.list_or_vec() or { return error('count: ${err}') } - return Int{seq.len} - }}, - NSFn{'=', fn (args List) !Type { - b := args.nth(1) or { return error('=: missing param') } - return make_bool(args.list[0].eq(b)) - }}, - NSFn{'<', fn (args List) !Type { - b := args.nth(1) or { return error('<: missing param') } - res := args.list[0].lt(b) or { return error('<: ${err}') } - return make_bool(res) - }}, - NSFn{'<=', fn (args List) !Type { - b := args.nth(1) or { return error('<=: missing param') } - res := args.list[0].lt(b) or { return error('<=: ${err}') } - return make_bool(res || args.list[0].eq(b)) - }}, - NSFn{'>', fn (args List) !Type { - b := args.nth(1) or { return error('>: missing param') } - res := args.list[0].lt(b) or { return error('>: ${err}') } - return make_bool(!res && !args.list[0].eq(b)) - }}, - NSFn{'>=', fn (args List) !Type { - b := args.nth(1) or { return error('>=: missing param') } - res := args.list[0].lt(b) or { return error('>=: ${err}') } - return make_bool(!res) - }}, - NSFn{'pr-str', fn (args List) !Type { - return String{args.list.map(pr_str(it, true)).join(' ')} - }}, - NSFn{'str', fn (args List) !Type { - return String{args.list.map(pr_str(it, false)).join('')} - }}, - NSFn{'prn', fn (args List) !Type { - println(args.list.map(pr_str(it, true)).join(' ')) - return Nil{} - }}, - NSFn{'println', fn (args List) !Type { - println(args.list.map(pr_str(it, false)).join(' ')) - return Nil{} - }}, - ] +type EvalFn = fn (Type, mut Env) !Type + +pub fn add_core(mut env Env, eval_fn EvalFn) { + add_fn(mut env, '+', 2, 2, fn (args List) !Type { + return Int{args.nth(0).int_()! + args.nth(1).int_()!} + }) + add_fn(mut env, '-', 2, 2, fn (args List) !Type { + return Int{args.nth(0).int_()! - args.nth(1).int_()!} + }) + add_fn(mut env, '*', 2, 2, fn (args List) !Type { + return Int{args.nth(0).int_()! * args.nth(1).int_()!} + }) + add_fn(mut env, '/', 2, 2, fn (args List) !Type { + return Int{args.nth(0).int_()! / args.nth(1).int_()!} + }) + add_fn(mut env, 'list', -1, -1, fn (args List) !Type { + return args + }) + add_fn(mut env, 'list?', 1, 1, fn (args List) !Type { + return make_bool(args.nth(0) is List) + }) + add_fn(mut env, 'empty?', 1, 1, fn (args List) !Type { + return make_bool(args.nth(0).list_or_vec()!.len == 0) + }) + add_fn(mut env, 'count', 1, 1, fn (args List) !Type { + return Int{args.nth(0).list_or_vec()!.len} + }) + add_fn(mut env, '=', 2, 2, fn (args List) !Type { + return make_bool(args.nth(0).eq(args.nth(1))) + }) + add_fn(mut env, '<', 2, 2, fn (args List) !Type { + return make_bool(args.nth(0).lt(args.nth(1))!) + }) + add_fn(mut env, '<=', 2, 2, fn (args List) !Type { + return make_bool(args.nth(0).lt(args.nth(1))! || args.nth(0).eq(args.nth(1))) + }) + add_fn(mut env, '>', 2, 2, fn (args List) !Type { + return make_bool(!args.nth(0).lt(args.nth(1))! && !args.nth(0).eq(args.nth(1))) + }) + add_fn(mut env, '>=', 2, 2, fn (args List) !Type { + return make_bool(!args.nth(0).lt(args.nth(1))!) + }) + add_fn(mut env, 'pr-str', -1, -1, fn (args List) !Type { + return String{args.list.map(pr_str(it, true)).join(' ')} + }) + add_fn(mut env, 'str', -1, -1, fn (args List) !Type { + return String{args.list.map(pr_str(it, false)).join('')} + }) + add_fn(mut env, 'prn', -1, -1, fn (args List) !Type { + println(args.list.map(pr_str(it, true)).join(' ')) + return Nil{} + }) + add_fn(mut env, 'println', -1, -1, fn (args List) !Type { + println(args.list.map(pr_str(it, false)).join(' ')) + return Nil{} + }) } diff --git a/impls/v/mal/env.v b/impls/v/mal/env.v index 6dbe9e9520..9031b33dbb 100644 --- a/impls/v/mal/env.v +++ b/impls/v/mal/env.v @@ -20,12 +20,15 @@ pub fn (mut e Env) bind(syms []string, args List) { e.set(syms[i + 1], args.from(i)) break } else { - e.set(sym, args.nth(i) or { mal.Nil{} }) + e.set(sym, args.nth(i)) } } } pub fn (mut e Env) set(sym string, val Type) Type { + //$if env ? { + // println('ENV: setting [${sym}] in 0x${voidptr(&e)} (outer 0x${voidptr(e.outer)})') + //} e.data[sym] = val return val } @@ -33,6 +36,8 @@ pub fn (mut e Env) set(sym string, val Type) Type { pub fn (e Env) find(sym string) ?Type { $if env ? { println('ENV: looking for [${sym}] in\n${e.data}') + // println('ENV: finding [${sym}] in 0x${voidptr(&e)} (outer 0x${voidptr(e.outer)}):') + // println("${e.data}") } if res := e.data[sym] { $if env ? { diff --git a/impls/v/mal/types.v b/impls/v/mal/types.v index 8db91b2797..721d373882 100644 --- a/impls/v/mal/types.v +++ b/impls/v/mal/types.v @@ -36,7 +36,7 @@ fn implicit_conv(a Type, b Type) !(Type, Type) { return error('type mismatch') } -fn make_bool(cond bool) Type { +pub fn make_bool(cond bool) Type { return if cond { True{} } else { False{} } } @@ -205,19 +205,19 @@ pub: list []Type } -pub fn (l List) first() !Type { - return if l.list.len > 0 { l.list[0] } else { error('list: empty') } +pub fn (l &List) first() !&Type { + return if l.list.len > 0 { &l.list[0] } else { error('list: empty') } } -pub fn (l List) last() !Type { - return if l.list.len > 0 { l.list.last() } else { error('list: empty') } +pub fn (l &List) last() !&Type { + return if l.list.len > 0 { &l.list[l.list.len - 1] } else { error('list: empty') } } -pub fn (l List) rest() List { - return if l.list.len > 1 { List{l.list[1..]} } else { List{} } +pub fn (l &List) rest() List { + return l.from(1) } -pub fn (l List) from(n int) List { +pub fn (l &List) from(n int) List { return if l.list.len > n { List{l.list[n..]} } else { List{} } } @@ -225,8 +225,8 @@ pub fn (l List) len() int { return l.list.len } -pub fn (l List) nth(n int) !Type { - return if n < l.list.len { l.list[n] } else { error('list: index oob') } +pub fn (l &List) nth(n int) &Type { + return if n < l.list.len { &l.list[n] } else { Nil{} } } // -- @@ -245,16 +245,18 @@ pub: // -- -type FnDef = fn (args List) !Type - pub struct Fn { pub: - f FnDef + f FnDef } pub struct Closure { pub: - ast Type - params []string - env &Env + ast Type + params []string + env &Env +} + +fn (c Closure) str() string { + return 'mal.Closure{\n \n}' } diff --git a/impls/v/step3_env.v b/impls/v/step3_env.v index 6231c295c2..9c90ae8f30 100644 --- a/impls/v/step3_env.v +++ b/impls/v/step3_env.v @@ -8,17 +8,18 @@ fn rep_read(input string) !mal.Type { fn eval(ast mal.Type, mut env mal.Env) !mal.Type { if ast is mal.List { first := ast.first() or { return ast } // return empty list + args := ast.rest() match first.sym() or { '' } { 'def!' { - ast.nth(2) or { return error('def!: missing param') } - sym := ast.list[1].sym() or { return error('def!: ${err}') } - return env.set(sym, eval(ast.list[2], mut env)!) + mal.check_args(args, 2, 2) or { return error('def!: ${err}') } + sym := args.nth(0).sym() or { return error('def!: ${err}') } + return env.set(sym, eval(args.nth(1), mut env)!) } 'let*' { - ast.nth(2) or { return error('let*: missing param') } + mal.check_args(args, 2, 2) or { return error('let*: ${err}') } mut new_env := mal.mk_env(&env) - mut pairs := ast.list[1].list_or_vec() or { return error('let*: ${err}') } - pairs = pairs[0..] // copy + tmp := args.nth(0).list_or_vec() or { return error('let*: ${err}') } + mut pairs := tmp[0..] // copy if pairs.len % 2 == 1 { return error('let*: extra binding param') } @@ -27,7 +28,7 @@ fn eval(ast mal.Type, mut env mal.Env) !mal.Type { new_env.set(sym, eval(pairs[1], mut new_env)!) pairs = pairs[2..] } - return eval(ast.list[2], mut new_env)! + return eval(args.nth(1), mut new_env)! } else { // regular list apply res := eval_ast(ast, mut env)! as mal.List diff --git a/impls/v/step4_if_fn_do.v b/impls/v/step4_if_fn_do.v index e22cd9a41f..a20ddb9551 100644 --- a/impls/v/step4_if_fn_do.v +++ b/impls/v/step4_if_fn_do.v @@ -8,16 +8,17 @@ fn rep_read(input string) !mal.Type { fn eval(ast mal.Type, mut env mal.Env) !mal.Type { if ast is mal.List { first := ast.first() or { return ast } // return empty list + args := ast.rest() match first.sym() or { '' } { 'def!' { - ast.nth(2) or { return error('def!: missing param') } - sym := ast.list[1].sym() or { return error('def!: ${err}') } - return env.set(sym, eval(ast.list[2], mut env)!) + mal.check_args(args, 2, 2) or { return error('def!: ${err}') } + sym := args.nth(0).sym() or { return error('def!: ${err}') } + return env.set(sym, eval(args.nth(1), mut env)!) } 'let*' { - ast.nth(2) or { return error('let*: missing param') } + mal.check_args(args, 2, 2) or { return error('let*: ${err}') } mut new_env := mal.mk_env(&env) - tmp := ast.list[1].list_or_vec() or { return error('let*: ${err}') } + tmp := args.nth(0).list_or_vec() or { return error('let*: ${err}') } mut pairs := tmp[0..] // copy if pairs.len % 2 == 1 { return error('let*: extra binding param') @@ -27,24 +28,23 @@ fn eval(ast mal.Type, mut env mal.Env) !mal.Type { new_env.set(sym, eval(pairs[1], mut new_env)!) pairs = pairs[2..] } - return eval(ast.list[2], mut new_env)! + return eval(args.nth(1), mut new_env)! } 'do' { - res := eval_ast(ast.rest(), mut env)! as mal.List + res := eval_ast(args, mut env)! as mal.List return res.last() or { mal.Nil{} } } 'if' { - ast.nth(2) or { return error('if: missing param') } - res := eval(ast.list[1], mut env)! - clause_n := if res.truthy() { 2 } else { 3 } - clause := ast.nth(clause_n) or { return mal.Nil{} } + mal.check_args(args, 2, 3) or { return error('if: ${err}') } + res := eval(args.nth(0), mut env)! + clause := args.nth(if res.truthy() { 1 } else { 2 }) return eval(clause, mut env)! } 'fn*' { - ast.nth(2) or { return error('fn*: missing param') } - binds := ast.list[1].list_or_vec() or { return error('fn*: ${err}') } + mal.check_args(args, 2, 2) or { return error('fn*: ${err}') } + binds := args.nth(0).list_or_vec() or { return error('fn*: ${err}') } syms := binds.map(it.sym() or { return error('fn*: ${err}') }) - body := ast.list[2] + body := args.nth(1) for i, sym in syms { if sym == '&' && syms.len != i + 2 { return error('fn*: & has 1 arg') @@ -57,7 +57,7 @@ fn eval(ast mal.Type, mut env mal.Env) !mal.Type { new_env.set(syms[i + 1], args.from(i)) break } else { - new_env.set(sym, args.nth(i) or { mal.Nil{} }) + new_env.set(sym, args.nth(i)) } } return eval(body, mut new_env)! @@ -117,10 +117,11 @@ fn rep(line string, mut env mal.Env) string { fn main() { // outer-most env mut env := mal.Env{} - for nsfn in mal.get_core() { - env.set(nsfn.sym, mal.Fn{nsfn.f}) - } + // core env + mal.add_core(mut env, eval) + + // mal defined env rep('(def! not (fn* (a) (if a false true)))', mut env) for { diff --git a/impls/v/step5_tco.v b/impls/v/step5_tco.v index 7daf9496a6..5e786b795b 100644 --- a/impls/v/step5_tco.v +++ b/impls/v/step5_tco.v @@ -10,18 +10,21 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { mut env := unsafe { &env_ } for true { if ast is mal.List { - node := ast as mal.List - first := node.first() or { return *ast } // return empty list + node := ast as mal.List + // BUG: receiver is not correctly passed by reference to first() + // https://github.com/vlang/v/issues/17249 + first := (&node).first() or { return *ast } // return empty list + args := node.rest() match first.sym() or { '' } { 'def!' { - node.nth(2) or { return error('def!: missing param') } - sym := node.list[1].sym() or { return error('def!: ${err}') } - return env.set(sym, eval(node.list[2], mut env)!) + mal.check_args(args, 2, 2) or { return error('def!: ${err}') } + sym := args.nth(0).sym() or { return error('def!: ${err}') } + return env.set(sym, eval(args.nth(1), mut env)!) } 'let*' { - node.nth(2) or { return error('let*: missing param') } + mal.check_args(args, 2, 2) or { return error('let*: ${err}') } env = unsafe { mal.mk_env(env) } // TCO - tmp := node.list[1].list_or_vec() or { return error('let*: ${err}') } + tmp := args.nth(0).list_or_vec() or { return error('let*: ${err}') } mut pairs := tmp[0..] // copy if pairs.len % 2 == 1 { return error('let*: extra binding param') @@ -31,50 +34,52 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { env.set(sym, eval(pairs[1], mut env)!) pairs = pairs[2..] } - ast = unsafe { &node.list[2] } // TCO + ast = unsafe { args.nth(1) } // TCO } 'do' { - if node.list.len > 2 { - node.list[1..node.list.len - 1].map(eval(it, mut env)!) - } else if node.list.len < 2 { + if args.len() > 1 { + args.list[0..args.list.len - 1].map(eval(it, mut env)!) + } else if args.len() == 0 { return mal.Nil{} } - ast = unsafe { &node.list[node.list.len - 1] } // TCO + // BUG: receiver is not correctly passed by reference to last() + // https://github.com/vlang/v/issues/17249 + ast = unsafe { (&args).last()! } // TCO } 'if' { - node.nth(2) or { return error('if: missing param') } - if eval(node.list[1], mut env)!.truthy() { - ast = unsafe{ &node.list[2] } // TCO - } else if node.list.len > 3 { - ast = unsafe{ &node.list[3] } // TCO - } else { - return mal.Nil{} - } + mal.check_args(args, 2, 3) or { return error('if: ${err}') } + if eval(args.nth(0), mut env)!.truthy() { + ast = unsafe { args.nth(1) } // TCO + } else if args.len() == 3 { + ast = unsafe { args.nth(2) } // TCO + } else { + return mal.Nil{} + } } 'fn*' { - node.nth(2) or { return error('fn*: missing param') } - binds := node.list[1].list_or_vec() or { return error('fn*: ${err}') } + mal.check_args(args, 2, 2) or { return error('fn*: ${err}') } + binds := args.nth(0).list_or_vec() or { return error('fn*: ${err}') } syms := binds.map(it.sym() or { return error('fn*: ${err}') }) for i, sym in syms { if sym == '&' && syms.len != i + 2 { return error('fn*: & has 1 arg') } } - return mal.Closure{node.list[2], syms, env} + return mal.Closure{args.nth(1), syms, env} } else { // regular list apply res := eval_ast(ast, mut env)! as mal.List - list0 := res.list[0] - if list0 is mal.Fn { - return list0.f(res.rest())! - } else if list0 is mal.Closure { - ast = &list0.ast // TCO - env = mal.mk_env(list0.env) - env.bind(list0.params, res.rest()) - } else { - return error( 'function expected' ) - } - } + list0 := res.list[0] + if list0 is mal.Fn { + return list0.f(res.rest())! + } else if list0 is mal.Closure { + ast = &list0.ast // TCO + env = mal.mk_env(list0.env) + env.bind(list0.params, res.rest()) + } else { + return error('function expected') + } + } } } else { return eval_ast(ast, mut env)! @@ -125,10 +130,11 @@ fn rep(line string, mut env mal.Env) string { fn main() { // outer-most env mut env := mal.Env{} - for nsfn in mal.get_core() { - env.set(nsfn.sym, mal.Fn{nsfn.f}) - } + // core env + mal.add_core(mut env, eval) + + // mal defined env rep('(def! not (fn* (a) (if a false true)))', mut env) for { From 3bdc244d968c5a371dacdf6f3e5d7e3812fbb2be Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Wed, 8 Feb 2023 10:45:33 +0000 Subject: [PATCH 13/30] v: fix bug in step4, remove v workarounds, rename -d token --- impls/v/mal/reader.v | 6 +++--- impls/v/step4_if_fn_do.v | 2 +- impls/v/step5_tco.v | 8 ++------ 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/impls/v/mal/reader.v b/impls/v/mal/reader.v index 62c1f7f4bf..0b5e55088d 100644 --- a/impls/v/mal/reader.v +++ b/impls/v/mal/reader.v @@ -120,14 +120,14 @@ fn tokenise(input string) ![]Token { mut ret := []Token{} mut input_ := input for { - $if tokenise ? { - println('INPUT: [${input_}]') + $if token ? { + println('${input_}') } start, end := re.match_string(input_) if start < 0 { break } - $if tokenise ? { + $if token ? { println('TOKEN: [${input_[re.groups[0]..re.groups[1]]}]') } if re.groups[1] > re.groups[0] { diff --git a/impls/v/step4_if_fn_do.v b/impls/v/step4_if_fn_do.v index a20ddb9551..7a52831524 100644 --- a/impls/v/step4_if_fn_do.v +++ b/impls/v/step4_if_fn_do.v @@ -32,7 +32,7 @@ fn eval(ast mal.Type, mut env mal.Env) !mal.Type { } 'do' { res := eval_ast(args, mut env)! as mal.List - return res.last() or { mal.Nil{} } + return *res.last()! } 'if' { mal.check_args(args, 2, 3) or { return error('if: ${err}') } diff --git a/impls/v/step5_tco.v b/impls/v/step5_tco.v index 5e786b795b..a6cdc33205 100644 --- a/impls/v/step5_tco.v +++ b/impls/v/step5_tco.v @@ -11,9 +11,7 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { for true { if ast is mal.List { node := ast as mal.List - // BUG: receiver is not correctly passed by reference to first() - // https://github.com/vlang/v/issues/17249 - first := (&node).first() or { return *ast } // return empty list + first := node.first() or { return *ast } // return empty list args := node.rest() match first.sym() or { '' } { 'def!' { @@ -42,9 +40,7 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { } else if args.len() == 0 { return mal.Nil{} } - // BUG: receiver is not correctly passed by reference to last() - // https://github.com/vlang/v/issues/17249 - ast = unsafe { (&args).last()! } // TCO + ast = unsafe { args.last()! } // TCO } 'if' { mal.check_args(args, 2, 3) or { return error('if: ${err}') } From 18c5c1e6165596691f1848875fd54e90a4a229e3 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Wed, 8 Feb 2023 10:47:33 +0000 Subject: [PATCH 14/30] v: step 6 --- impls/v/mal/core.v | 24 ++++++ impls/v/mal/printer.v | 3 + impls/v/mal/reader.v | 3 +- impls/v/mal/types.v | 109 ++++++++++++++++++-------- impls/v/step6_file.v | 176 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 282 insertions(+), 33 deletions(-) create mode 100644 impls/v/step6_file.v diff --git a/impls/v/mal/core.v b/impls/v/mal/core.v index 530df14e10..7f78f5cc91 100644 --- a/impls/v/mal/core.v +++ b/impls/v/mal/core.v @@ -73,4 +73,28 @@ pub fn add_core(mut env Env, eval_fn EvalFn) { println(args.list.map(pr_str(it, false)).join(' ')) return Nil{} }) + add_fn(mut env, 'read-string', 1, 1, fn (args List) !Type { + return read_str(args.nth(0).str_()!)! + }) + add_fn(mut env, 'slurp', 1, 1, fn (args List) !Type { + return String{os.read_file(args.nth(0).str_()!)!} + }) + add_fn(mut env, 'atom', 1, 1, fn (args List) !Type { + return Atom{args.nth(0)} + }) + add_fn(mut env, 'atom?', 1, 1, fn (args List) !Type { + return make_bool(args.nth(0) is Atom) + }) + add_fn(mut env, 'deref', 1, 1, fn (args List) !Type { + return args.nth(0).atom()!.typ + }) + add_fn(mut env, 'reset!', 2, 2, fn (args List) !Type { + return args.nth(0).atom()!.set(args.nth(1)) + }) + add_fn(mut env, 'swap!', 2, -1, fn [eval_fn] (args List) !Type { + atom := args.nth(0).atom()! + mut list := [atom.typ] + list << args.from(2).list + return atom.set(args.nth(1).fn_apply(eval_fn, List{list})!) + }) } diff --git a/impls/v/mal/printer.v b/impls/v/mal/printer.v index d1a32d618f..b2acc05f62 100644 --- a/impls/v/mal/printer.v +++ b/impls/v/mal/printer.v @@ -47,6 +47,9 @@ pub fn pr_str(ast Type, readable bool) string { Fn, Closure { '#' } + Atom { + '(atom ${pr_str(ast.typ, readable)})' + } } } diff --git a/impls/v/mal/reader.v b/impls/v/mal/reader.v index 0b5e55088d..77c5773a5a 100644 --- a/impls/v/mal/reader.v +++ b/impls/v/mal/reader.v @@ -3,7 +3,7 @@ module mal import regex const ( - re_token = '^[\\s,]*((?:~@)|(?:[\\[\\]{}()\'\`~^@])|("(?:(?:\\\\.)|[^\\\\"])*"?)|(?:;.*)|(?:[^\\s\\[\\\]{}(\'"`,;)]*))' + re_token = '^[\\s,]*((?:~@)|(?:[\\[\\]{}()\'\`~^@])|("(?:(?:\\\\.)|[^\\\\"])*"?)|(?:;[^\n]*)|(?:[^\\s\\[\\\]{}(\'"`,;)]*))' re_int = '^-?[0-9]+$' re_float = '^-?[0-9]*\\.[0-9]+$' ) @@ -100,6 +100,7 @@ fn (mut r Reader) read_atom() !Type { tok == 'false' { False{} } tok[0] == `"` { String{unescape(tok[1..tok.len - 1])} } tok[0] == `:` { Keyword{tok[1..]} } + tok[0] == `;` { r.read_form()! } r.re_int.matches_string(tok) { Int{tok.i64()} } r.re_float.matches_string(tok) { Float{tok.f64()} } else { Symbol{tok} } diff --git a/impls/v/mal/types.v b/impls/v/mal/types.v index 721d373882..74a4959e3c 100644 --- a/impls/v/mal/types.v +++ b/impls/v/mal/types.v @@ -1,9 +1,10 @@ module mal -type Type = False +type Type = Atom + | Closure + | False | Float | Fn - | Closure | Hashmap | Int | Keyword @@ -14,7 +15,13 @@ type Type = False | True | Vector -fn implicit_conv(a Type, b Type) !(Type, Type) { +type FnDef = fn (args List) !Type + +// convert one or both arguments, where such an upward, non-destructive +// conversion would enable their comparison +fn implicit_conv(a_ Type, b_ Type) !(Type, Type) { + a := a_.resolve_atom() + b := b_.resolve_atom() // same type if a.type_idx() == b.type_idx() { return a, b @@ -52,30 +59,8 @@ pub fn (t Type) numeric() bool { return t in [Int, Float] } -pub fn (t Type) sym() !string { - return if t is Symbol { t.sym } else { error('symbol expected') } -} - -pub fn (t Type) fn_() !FnDef { - return if t is Fn { t.f } else { error('function expected') } -} - -pub fn (t Type) int_() !i64 { - return if t is Int { t.val } else { error('integer expected') } -} - -pub fn (t Type) list() ![]Type { - return if t is List { t.list } else { error('list expected') } -} - -pub fn (t Type) list_or_vec() ![]Type { - return match t { - List { t.list } - Vector { t.vec } - Nil { []Type{} } - // Nil { if allow_nil { []Type{} } else { error('list/vector expected') } } - else { error('list/vector expected') } - } +pub fn (t Type) resolve_atom() Type { + return if t is Atom { t.typ.resolve_atom() } else { t } } pub fn (t Type) eq(o Type) bool { @@ -133,11 +118,13 @@ pub fn (t Type) eq(o Type) bool { Fn { return a.f == (b as Fn).f } - Closure { - return a.env == (b as Closure).env && - a.ast == (b as Closure).ast && - a.params == (b as Closure).params - } + Closure { + return + a.env == (b as Closure).env && a.ast == (b as Closure).ast && a.params == (b as Closure).params + } + Atom { + panic('unresolved atom') + } } } @@ -151,6 +138,51 @@ pub fn (t Type) lt(o Type) !bool { } } +pub fn (t Type) fn_apply(eval_fn EvalFn, args List) !Type { + if t is Fn { + return t.f(args)! + } else if t is Closure { + mut env := mk_env(t.env) + env.bind(t.params, args) + return eval_fn(t.ast, mut env)! + } else { + return error('function expected') + } +} + +pub fn (t Type) sym() !string { + return if t is Symbol { t.sym } else { error('symbol expected') } +} + +pub fn (t Type) fn_() !FnDef { + return if t is Fn { t.f } else { error('function expected') } +} + +pub fn (t Type) int_() !i64 { + return if t is Int { t.val } else { error('integer expected') } +} + +pub fn (t Type) str_() !string { + return if t is String { t.val } else { error('string expected') } +} + +pub fn (t Type) list() ![]Type { + return if t is List { t.list } else { error('list expected') } +} + +pub fn (t Type) list_or_vec() ![]Type { + return match t { + List { t.list } + Vector { t.vec } + Nil { []Type{} } + else { error('list/vector expected') } + } +} + +pub fn (t &Type) atom() !&Atom { + return if t is Atom { unsafe { &t } } else { error('atom expected') } +} + // -- pub struct Int { @@ -260,3 +292,16 @@ pub: fn (c Closure) str() string { return 'mal.Closure{\n \n}' } + +// -- + +pub struct Atom { +pub mut: + typ Type = Nil{} +} + +fn (a &Atom) set(t Type) Type { + mut mut_a := unsafe { a } + mut_a.typ = t + return t +} diff --git a/impls/v/step6_file.v b/impls/v/step6_file.v new file mode 100644 index 0000000000..0b61fb3ba8 --- /dev/null +++ b/impls/v/step6_file.v @@ -0,0 +1,176 @@ +import mal +import os +import readline { read_line } + +fn rep_read(input string) !mal.Type { + return mal.read_str(input)! +} + +fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { + mut ast := unsafe { &ast_ } + mut env := unsafe { &env_ } + for true { + if ast is mal.List { + node := ast as mal.List + first := node.first() or { return *ast } // return empty list + args := node.rest() + match first.sym() or { '' } { + 'def!' { + mal.check_args(args, 2, 2) or { return error('def!: ${err}') } + sym := args.nth(0).sym() or { return error('def!: ${err}') } + return env.set(sym, eval(args.nth(1), mut env)!) + } + 'let*' { + mal.check_args(args, 2, 2) or { return error('let*: ${err}') } + env = unsafe { mal.mk_env(env) } // TCO + tmp := args.nth(0).list_or_vec() or { return error('let*: ${err}') } + mut pairs := tmp[0..] // copy + if pairs.len % 2 == 1 { + return error('let*: extra binding param') + } + for pairs.len > 0 { + sym := pairs[0].sym() or { return error('let*: ${err}') } + env.set(sym, eval(pairs[1], mut env)!) + pairs = pairs[2..] + } + ast = unsafe { args.nth(1) } // TCO + } + 'do' { + if args.len() > 1 { + args.list[0..args.list.len - 1].map(eval(it, mut env)!) + } else if args.len() == 0 { + return mal.Nil{} + } + ast = unsafe { args.last()! } // TCO + } + 'if' { + mal.check_args(args, 2, 3) or { return error('if: ${err}') } + if eval(args.nth(0), mut env)!.truthy() { + ast = unsafe { args.nth(1) } // TCO + } else if args.len() == 3 { + ast = unsafe { args.nth(2) } // TCO + } else { + return mal.Nil{} + } + } + 'fn*' { + mal.check_args(args, 2, 2) or { return error('fn*: ${err}') } + binds := args.nth(0).list_or_vec() or { return error('fn*: ${err}') } + syms := binds.map(it.sym() or { return error('fn*: ${err}') }) + for i, sym in syms { + if sym == '&' && syms.len != i + 2 { + return error('fn*: & has 1 arg') + } + } + return mal.Closure{args.nth(1), syms, env} + } + else { // regular list apply + res := eval_ast(ast, mut env)! as mal.List + list0 := res.list[0] + if list0 is mal.Fn { + return list0.f(res.rest())! + } else if list0 is mal.Closure { + ast = &list0.ast // TCO + env = mal.mk_env(list0.env) + env.bind(list0.params, res.rest()) + } else { + return error('function expected') + } + } + } + } else { + return eval_ast(ast, mut env)! + } + } + panic('unreachable code') +} + +fn eval_ast(ast mal.Type, mut env mal.Env) !mal.Type { + match ast { + mal.Symbol { + return env.get(ast.sym)! + } + mal.List { + return mal.List{ast.list.map(eval(it, mut env)!)} + } + mal.Vector { + return mal.Vector{ast.vec.map(eval(it, mut env)!)} + } + mal.Hashmap { + mut hm := map[string]mal.Type{} + for key in ast.hm.keys() { + hm[key] = eval(ast.hm[key] or { panic('') }, mut env)! + } + return mal.Hashmap{hm} + } + else { + return ast + } + } +} + +fn rep_print(ast mal.Type) string { + return mal.pr_str(ast, true) +} + +fn rep(line string, mut env mal.Env) string { + ast := rep_read(line) or { + if err.msg() != 'no form' { + println('READ ERROR: ${err}') + } + return '' + } + $if ast ? { + println('AST:\n${ast}') + } + res := eval(ast, mut env) or { + println('EVAL ERROR: ${err}') + return '' + } + return rep_print(res) +} + +fn main() { + // outer-most env + mut env := mal.Env{} + + // add eval + mut envp := &env // workaround no closure references in V + mal.add_fn(mut env, 'eval', 1, 1, fn [mut envp] (args mal.List) !mal.Type { + return eval(args.nth(0), mut envp)! + }) + + // core env + mal.add_core(mut env, eval) + + // mal defined env + rep('(def! not (fn* (a) (if a false true)))', mut env) + rep('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))', mut + env) + + if os.args.len > 1 { + // run script + file := os.args[1] + .replace('\\', '\\\\') + .replace('"', '\\"') + mut args := []mal.Type{} + for i in 2 .. os.args.len { + args << mal.Type(mal.String{os.args[i]}) + } + env.set('*ARGV*', mal.List{args}) + rep('(load-file "${file}")', mut env) + } else { + // repl + env.set('*ARGV*', mal.List{}) + for { + line := read_line('user> ') or { + println('') // newline + break + } + out := rep(line, mut env) + if out.len > 0 { + println(out) + } + } + } +} From bb44d7dc7946a423ed11b7e69ad047165ea09890 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Wed, 8 Feb 2023 15:23:04 +0000 Subject: [PATCH 15/30] v: step 7 --- impls/v/mal/core.v | 21 ++++ impls/v/step7_quote.v | 222 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 impls/v/step7_quote.v diff --git a/impls/v/mal/core.v b/impls/v/mal/core.v index 7f78f5cc91..28c309d560 100644 --- a/impls/v/mal/core.v +++ b/impls/v/mal/core.v @@ -93,8 +93,29 @@ pub fn add_core(mut env Env, eval_fn EvalFn) { }) add_fn(mut env, 'swap!', 2, -1, fn [eval_fn] (args List) !Type { atom := args.nth(0).atom()! + // BUG: << doesn't like templated sumtype array args + // https://github.com/vlang/v/issues/17259 + // list := arrays.concat[Type]([atom.typ], ...args.from(2).list) mut list := [atom.typ] list << args.from(2).list return atom.set(args.nth(1).fn_apply(eval_fn, List{list})!) }) + add_fn(mut env, 'cons', 2, 2, fn (args List) !Type { + // BUG: << doesn't like templated sumtype array args + // https://github.com/vlang/v/issues/17259 + // return List{arrays.concat[Type]([*args.nth(0)], ...args.nth(1).list()!)} + mut list := [*args.nth(0)] + list << args.nth(1).list_or_vec()! + return List{list} + }) + add_fn(mut env, 'concat', 0, -1, fn (args List) !Type { + mut list := []Type{} + for arg in args.list { + list << arg.list_or_vec()! + } + return List{list} + }) + add_fn(mut env, 'vec', 1, 1, fn (args List) !Type { + return Vector{args.nth(0).list_or_vec()!} + }) } diff --git a/impls/v/step7_quote.v b/impls/v/step7_quote.v new file mode 100644 index 0000000000..488769c43e --- /dev/null +++ b/impls/v/step7_quote.v @@ -0,0 +1,222 @@ +import mal +import os +import readline { read_line } + +fn rep_read(input string) !mal.Type { + return mal.read_str(input)! +} + +fn quasiquote_list(list []mal.Type) !mal.Type { + mut res := []mal.Type{} + for elt in list.reverse() { + if elt is mal.List && (elt as mal.List).nth(0).eq(mal.Symbol{'splice-unquote'}) { + res = [mal.Symbol{'concat'}, elt.nth(1), mal.List{res}] + } else { + res = [mal.Symbol{'cons'}, quasiquote(elt)!, mal.List{res}] + } + } + return mal.List{res} +} + +fn quasiquote(ast mal.Type) !mal.Type { + return match ast { + mal.List { + if ast.nth(0).eq(mal.Symbol{'unquote'}) { + *ast.nth(1) + } else { + quasiquote_list(ast.list)! + } + } + mal.Vector { + mal.List{[mal.Symbol{'vec'}, quasiquote_list(ast.vec)!]} + } + mal.Symbol, mal.Hashmap { + mal.List{[mal.Symbol{'quote'}, ast]} + } + else { + ast + } + } +} + +fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { + mut ast := unsafe { &ast_ } + mut env := unsafe { &env_ } + for true { + if ast is mal.List { + node := ast as mal.List + first := node.first() or { return *ast } // return empty list + args := node.rest() + match first.sym() or { '' } { + 'def!' { + mal.check_args(args, 2, 2) or { return error('def!: ${err}') } + sym := args.nth(0).sym() or { return error('def!: ${err}') } + return env.set(sym, eval(args.nth(1), mut env)!) + } + 'let*' { + mal.check_args(args, 2, 2) or { return error('let*: ${err}') } + env = unsafe { mal.mk_env(env) } // TCO + tmp := args.nth(0).list_or_vec() or { return error('let*: ${err}') } + mut pairs := tmp[0..] // copy + if pairs.len % 2 == 1 { + return error('let*: extra binding param') + } + for pairs.len > 0 { + sym := pairs[0].sym() or { return error('let*: ${err}') } + env.set(sym, eval(pairs[1], mut env)!) + pairs = pairs[2..] + } + ast = unsafe { args.nth(1) } // TCO + } + 'do' { + if args.len() > 1 { + args.list[0..args.list.len - 1].map(eval(it, mut env)!) + } else if args.len() == 0 { + return mal.Nil{} + } + ast = unsafe { args.last()! } // TCO + } + 'if' { + mal.check_args(args, 2, 3) or { return error('if: ${err}') } + if eval(args.nth(0), mut env)!.truthy() { + ast = unsafe { args.nth(1) } // TCO + } else if args.len() == 3 { + ast = unsafe { args.nth(2) } // TCO + } else { + return mal.Nil{} + } + } + 'fn*' { + mal.check_args(args, 2, 2) or { return error('fn*: ${err}') } + binds := args.nth(0).list_or_vec() or { return error('fn*: ${err}') } + syms := binds.map(it.sym() or { return error('fn*: ${err}') }) + for i, sym in syms { + if sym == '&' && syms.len != i + 2 { + return error('fn*: & has 1 arg') + } + } + return mal.Closure{args.nth(1), syms, env} + } + 'quote' { + mal.check_args(args, 1, 1) or { return error('quote: ${err}') } + return *args.nth(0) + } + 'quasiquoteexpand' { + mal.check_args(args, 1, 1) or { return error('quasiquoteexpand: ${err}') } + return quasiquote(args.nth(0)) or { return error('quasiquoteexpand: ${err}') } + } + 'quasiquote' { + mal.check_args(args, 1, 1) or { return error('quasiquote: ${err}') } + res := quasiquote(args.nth(0)) or { return error('quasiquote: ${err}') } + ast = unsafe { &res } // TCO + } + else { // regular list apply + res := eval_ast(ast, mut env)! as mal.List + list0 := res.list[0] + if list0 is mal.Fn { + return list0.f(res.rest())! + } else if list0 is mal.Closure { + ast = &list0.ast // TCO + env = mal.mk_env(list0.env) + env.bind(list0.params, res.rest()) + } else { + return error('function expected') + } + } + } + } else { + return eval_ast(ast, mut env)! + } + } + panic('unreachable code') +} + +fn eval_ast(ast mal.Type, mut env mal.Env) !mal.Type { + match ast { + mal.Symbol { + return env.get(ast.sym)! + } + mal.List { + return mal.List{ast.list.map(eval(it, mut env)!)} + } + mal.Vector { + return mal.Vector{ast.vec.map(eval(it, mut env)!)} + } + mal.Hashmap { + mut hm := map[string]mal.Type{} + for key in ast.hm.keys() { + hm[key] = eval(ast.hm[key] or { panic('') }, mut env)! + } + return mal.Hashmap{hm} + } + else { + return ast + } + } +} + +fn rep_print(ast mal.Type) string { + return mal.pr_str(ast, true) +} + +fn rep(line string, mut env mal.Env) string { + ast := rep_read(line) or { + if err.msg() != 'no form' { + println('READ ERROR: ${err}') + } + return '' + } + $if ast ? { + println('AST:\n${ast}') + } + res := eval(ast, mut env) or { + println('EVAL ERROR: ${err}') + return '' + } + return rep_print(res) +} + +fn main() { + // outer-most env + mut env := mal.Env{} + + // add eval + mut envp := &env // workaround no closure references in V + mal.add_fn(mut env, 'eval', 1, 1, fn [mut envp] (args mal.List) !mal.Type { + return eval(args.nth(0), mut envp)! + }) + + // core env + mal.add_core(mut env, eval) + + // mal defined env + rep('(def! not (fn* (a) (if a false true)))', mut env) + rep('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))', mut + env) + + if os.args.len > 1 { + // run script + file := os.args[1] + .replace('\\', '\\\\') + .replace('"', '\\"') + mut args := []mal.Type{} + for i in 2 .. os.args.len { + args << mal.Type(mal.String{os.args[i]}) + } + env.set('*ARGV*', mal.List{args}) + rep('(load-file "${file}")', mut env) + } else { + // repl + env.set('*ARGV*', mal.List{}) + for { + line := read_line('user> ') or { + println('') // newline + break + } + out := rep(line, mut env) + if out.len > 0 { + println(out) + } + } + } +} From c334911d951ab7e12400064f5cec7fc9dfccc5b8 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Wed, 8 Feb 2023 21:13:31 +0000 Subject: [PATCH 16/30] v: step 8 --- impls/v/mal/core.v | 13 ++ impls/v/mal/printer.v | 9 +- impls/v/mal/types.v | 35 +++++- impls/v/step5_tco.v | 2 +- impls/v/step6_file.v | 2 +- impls/v/step7_quote.v | 4 +- impls/v/step8_macros.v | 263 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 319 insertions(+), 9 deletions(-) create mode 100644 impls/v/step8_macros.v diff --git a/impls/v/mal/core.v b/impls/v/mal/core.v index 28c309d560..3a3ffe6019 100644 --- a/impls/v/mal/core.v +++ b/impls/v/mal/core.v @@ -118,4 +118,17 @@ pub fn add_core(mut env Env, eval_fn EvalFn) { add_fn(mut env, 'vec', 1, 1, fn (args List) !Type { return Vector{args.nth(0).list_or_vec()!} }) + add_fn(mut env, 'nth', 2, 2, fn (args List) !Type { + list := args.nth(0).list_or_vec()! + i := args.nth(1).int_()! + return if i < list.len { list[i] } else { error('out of range') } + }) + add_fn(mut env, 'first', 1, 1, fn (args List) !Type { + list := List{args.nth(0).list_or_vec()!} + return *list.nth(0) + }) + add_fn(mut env, 'rest', 1, 1, fn (args List) !Type { + list := List{args.nth(0).list_or_vec()!} + return list.rest() + }) } diff --git a/impls/v/mal/printer.v b/impls/v/mal/printer.v index b2acc05f62..d48c9b0490 100644 --- a/impls/v/mal/printer.v +++ b/impls/v/mal/printer.v @@ -44,9 +44,16 @@ pub fn pr_str(ast Type, readable bool) string { return '${k} ${pr_str(v, readable)}' }).join(' ') + '}' } - Fn, Closure { + Fn { '#' } + Closure { + if ast.is_macro { + '#' + } else { + '#' + } + } Atom { '(atom ${pr_str(ast.typ, readable)})' } diff --git a/impls/v/mal/types.v b/impls/v/mal/types.v index 74a4959e3c..0216e007c3 100644 --- a/impls/v/mal/types.v +++ b/impls/v/mal/types.v @@ -55,6 +55,18 @@ pub fn (t Type) falsey() bool { return t in [False, Nil] } +pub fn (t Type) call_sym() ?string { + if t is List { + if t.list.len > 0 { + list0 := t.list[0] + if list0 is Symbol { + return list0.sym + } + } + } + return none +} + pub fn (t Type) numeric() bool { return t in [Int, Float] } @@ -158,6 +170,10 @@ pub fn (t Type) fn_() !FnDef { return if t is Fn { t.f } else { error('function expected') } } +pub fn (t Type) cls() !&Closure { + return if t is Closure { &t } else { error('closure expected') } +} + pub fn (t Type) int_() !i64 { return if t is Int { t.val } else { error('integer expected') } } @@ -284,13 +300,24 @@ pub: pub struct Closure { pub: - ast Type - params []string - env &Env + ast Type + params []string + env &Env + is_macro bool } fn (c Closure) str() string { - return 'mal.Closure{\n \n}' + disp := if c.is_macro { 'macro' } else { 'closure' } + return 'mal.Closure{\n <${disp}>\n}' +} + +pub fn (c Closure) to_macro() Closure { + return Closure{ + ast: c.ast + params: c.params + env: c.env + is_macro: true + } } // -- diff --git a/impls/v/step5_tco.v b/impls/v/step5_tco.v index a6cdc33205..03e46f9e49 100644 --- a/impls/v/step5_tco.v +++ b/impls/v/step5_tco.v @@ -61,7 +61,7 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { return error('fn*: & has 1 arg') } } - return mal.Closure{args.nth(1), syms, env} + return mal.Closure{args.nth(1), syms, env, false} } else { // regular list apply res := eval_ast(ast, mut env)! as mal.List diff --git a/impls/v/step6_file.v b/impls/v/step6_file.v index 0b61fb3ba8..3c02ee23c1 100644 --- a/impls/v/step6_file.v +++ b/impls/v/step6_file.v @@ -62,7 +62,7 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { return error('fn*: & has 1 arg') } } - return mal.Closure{args.nth(1), syms, env} + return mal.Closure{args.nth(1), syms, env, false} } else { // regular list apply res := eval_ast(ast, mut env)! as mal.List diff --git a/impls/v/step7_quote.v b/impls/v/step7_quote.v index 488769c43e..eb1ead48bb 100644 --- a/impls/v/step7_quote.v +++ b/impls/v/step7_quote.v @@ -9,7 +9,7 @@ fn rep_read(input string) !mal.Type { fn quasiquote_list(list []mal.Type) !mal.Type { mut res := []mal.Type{} for elt in list.reverse() { - if elt is mal.List && (elt as mal.List).nth(0).eq(mal.Symbol{'splice-unquote'}) { + if elt is mal.List && elt.call_sym() or { '' } == 'splice-unquote' { res = [mal.Symbol{'concat'}, elt.nth(1), mal.List{res}] } else { res = [mal.Symbol{'cons'}, quasiquote(elt)!, mal.List{res}] @@ -95,7 +95,7 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { return error('fn*: & has 1 arg') } } - return mal.Closure{args.nth(1), syms, env} + return mal.Closure{args.nth(1), syms, env, false} } 'quote' { mal.check_args(args, 1, 1) or { return error('quote: ${err}') } diff --git a/impls/v/step8_macros.v b/impls/v/step8_macros.v new file mode 100644 index 0000000000..493fbd867b --- /dev/null +++ b/impls/v/step8_macros.v @@ -0,0 +1,263 @@ +import mal +import os +import readline { read_line } + +fn rep_read(input string) !mal.Type { + return mal.read_str(input)! +} + +fn quasiquote_list(list []mal.Type) !mal.Type { + mut res := []mal.Type{} + for elt in list.reverse() { + if elt is mal.List && elt.call_sym() or { '' } == 'splice-unquote' { + res = [mal.Symbol{'concat'}, elt.nth(1), mal.List{res}] + } else { + res = [mal.Symbol{'cons'}, quasiquote(elt)!, mal.List{res}] + } + } + return mal.List{res} +} + +fn quasiquote(ast mal.Type) !mal.Type { + return match ast { + mal.List { + if ast.nth(0).eq(mal.Symbol{'unquote'}) { + *ast.nth(1) + } else { + quasiquote_list(ast.list)! + } + } + mal.Vector { + mal.List{[mal.Symbol{'vec'}, quasiquote_list(ast.vec)!]} + } + mal.Symbol, mal.Hashmap { + mal.List{[mal.Symbol{'quote'}, ast]} + } + else { + ast + } + } +} + +fn is_macro_call(ast mal.Type, env mal.Env) ?mal.Closure { + if sym := ast.call_sym() { + if cls := env.find(sym) { + if cls is mal.Closure { + if cls.is_macro { + return cls + } + } + } + } + return none +} + +fn macroexpand(ast_ mal.Type, env mal.Env) !mal.Type { + mut ast := unsafe { &ast_ } + for { + cls := is_macro_call(ast, env) or { break } + res := mal.apply(cls, eval, (ast as mal.List).rest())! + ast = unsafe { &res } + } + return *ast +} + +fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { + mut ast := unsafe { &ast_ } + mut env := unsafe { &env_ } + for true { + if ast is mal.List { + expanded := macroexpand(ast, env)! + ast = unsafe { &expanded } + if ast !is mal.List { + return eval_ast(ast, mut env)! + } + ast0 := (ast as mal.List).first() or { return *ast } // return empty list + args := (ast as mal.List).rest() + match ast0.sym() or { '' } { + 'def!' { + mal.check_args(args, 2, 2) or { return error('def!: ${err}') } + sym := args.nth(0).sym() or { return error('def!: ${err}') } + return env.set(sym, eval(args.nth(1), mut env)!) + } + 'let*' { + mal.check_args(args, 2, 2) or { return error('let*: ${err}') } + env = unsafe { mal.mk_env(env) } // TCO + tmp := args.nth(0).sequence() or { return error('let*: ${err}') } + mut pairs := tmp[0..] // copy + if pairs.len % 2 == 1 { + return error('let*: extra binding param') + } + for pairs.len > 0 { + sym := pairs[0].sym() or { return error('let*: ${err}') } + env.set(sym, eval(pairs[1], mut env)!) + pairs = pairs[2..] + } + ast = unsafe { args.nth(1) } // TCO + } + 'do' { + if args.len() > 1 { + args.list[0..args.list.len - 1].map(eval(it, mut env)!) + } else if args.len() == 0 { + return mal.Nil{} + } + ast = unsafe { args.last()! } // TCO + } + 'if' { + mal.check_args(args, 2, 3) or { return error('if: ${err}') } + if eval(args.nth(0), mut env)!.truthy() { + ast = unsafe { args.nth(1) } // TCO + } else if args.len() == 3 { + ast = unsafe { args.nth(2) } // TCO + } else { + return mal.Nil{} + } + } + 'fn*' { + mal.check_args(args, 2, 2) or { return error('fn*: ${err}') } + binds := args.nth(0).sequence() or { return error('fn*: ${err}') } + syms := binds.map(it.sym() or { return error('fn*: ${err}') }) + for i, sym in syms { + if sym == '&' && syms.len != i + 2 { + return error('fn*: & has 1 arg') + } + } + return mal.Closure{args.nth(1), syms, env, false} + } + 'quote' { + mal.check_args(args, 1, 1) or { return error('quote: ${err}') } + return *args.nth(0) + } + 'quasiquoteexpand' { + mal.check_args(args, 1, 1) or { return error('quasiquoteexpand: ${err}') } + return quasiquote(args.nth(0)) or { return error('quasiquoteexpand: ${err}') } + } + 'quasiquote' { + mal.check_args(args, 1, 1) or { return error('quasiquote: ${err}') } + res := quasiquote(args.nth(0)) or { return error('quasiquote: ${err}') } + ast = unsafe { &res } // TCO + } + 'defmacro!' { + mal.check_args(args, 2, 2) or { return error('defmacro!: ${err}') } + sym := args.nth(0).sym() or { return error('defmacro!: ${err}') } + cls := eval(args.nth(1), mut env)!.cls() or { + return error('defmacro!: ${err}') + } + return env.set(sym, cls.to_macro()) + } + 'macroexpand' { + mal.check_args(args, 1, 1) or { return error('macroexpand: ${err}') } + return macroexpand(args.nth(0), env) + } + else { // regular list apply + res := eval_ast(ast, mut env)! as mal.List + list0 := res.list[0] + if list0 is mal.Fn { + return list0.f(res.rest())! + } else if list0 is mal.Closure { + ast = &list0.ast // TCO + env = mal.mk_env(list0.env) + env.bind(list0.params, res.rest()) + } else { + return error('function expected') + } + } + } + } else { + return eval_ast(ast, mut env)! + } + } + panic('unreachable code') +} + +fn eval_ast(ast mal.Type, mut env mal.Env) !mal.Type { + match ast { + mal.Symbol { + return env.get(ast.sym)! + } + mal.List { + return mal.List{ast.list.map(eval(it, mut env)!)} + } + mal.Vector { + return mal.Vector{ast.vec.map(eval(it, mut env)!)} + } + mal.Hashmap { + mut hm := map[string]mal.Type{} + for key in ast.hm.keys() { + hm[key] = eval(ast.hm[key] or { panic('') }, mut env)! + } + return mal.Hashmap{hm} + } + else { + return ast + } + } +} + +fn rep_print(ast mal.Type) string { + return mal.pr_str(ast, true) +} + +fn rep(line string, mut env mal.Env) string { + ast := rep_read(line) or { + if err.msg() != 'no form' { + println('READ ERROR: ${err}') + } + return '' + } + $if ast ? { + println('AST:\n${ast}') + } + res := eval(ast, mut env) or { + println('EVAL ERROR: ${err}') + return '' + } + return rep_print(res) +} + +fn main() { + // outer-most env + mut env := mal.Env{} + + // add eval + mut envp := &env // workaround no closure references in V + mal.add_fn(mut env, 'eval', 1, 1, fn [mut envp] (args mal.List) !mal.Type { + return eval(args.nth(0), mut envp)! + }) + + // core env + mal.add_core(mut env, eval) + + // mal defined env + rep('(def! not (fn* (a) (if a false true)))', mut env) + rep('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))', mut + env) + rep('(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list \'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons \'cond (rest (rest xs)))))))', mut + env) + + if os.args.len > 1 { + // run script + file := os.args[1] + .replace('\\', '\\\\') + .replace('"', '\\"') + mut args := []mal.Type{} + for i in 2 .. os.args.len { + args << mal.Type(mal.String{os.args[i]}) + } + env.set('*ARGV*', mal.List{args}) + rep('(load-file "${file}")', mut env) + } else { + // repl + env.set('*ARGV*', mal.List{}) + for { + line := read_line('user> ') or { + println('') // newline + break + } + out := rep(line, mut env) + if out.len > 0 { + println(out) + } + } + } +} From c845d66a2d8a650928b7b2bb4263e15c23acbc41 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Thu, 9 Feb 2023 19:59:12 +0000 Subject: [PATCH 17/30] v: refactor, fix eq() on Hashmap, fix keys and string representation --- impls/v/mal/core.v | 34 +++++++++++++++++++---------- impls/v/mal/printer.v | 2 +- impls/v/mal/reader.v | 12 +++------- impls/v/mal/types.v | 47 ++++++++++++++++++++++++++-------------- impls/v/step3_env.v | 2 +- impls/v/step4_if_fn_do.v | 4 ++-- impls/v/step5_tco.v | 4 ++-- impls/v/step6_file.v | 4 ++-- impls/v/step7_quote.v | 4 ++-- 9 files changed, 67 insertions(+), 46 deletions(-) diff --git a/impls/v/mal/core.v b/impls/v/mal/core.v index 3a3ffe6019..aa9994360d 100644 --- a/impls/v/mal/core.v +++ b/impls/v/mal/core.v @@ -2,6 +2,20 @@ module mal import os +type EvalFn = fn (Type, mut Env) !Type + +pub fn apply(ast Type, eval_fn EvalFn, args List) !Type { + if ast is Fn { + return ast.f(args)! + } else if ast is Closure { + mut env := mk_env(ast.env) + env.bind(ast.params, args) + return eval_fn(ast.ast, mut env)! + } else { + return error('function expected') + } +} + pub fn check_args(args List, min int, max int) ! { if args.len() < min { return error('missing param') @@ -17,8 +31,6 @@ pub fn add_fn(mut env Env, sym string, min int, max int, f FnDef) { }}) } -type EvalFn = fn (Type, mut Env) !Type - pub fn add_core(mut env Env, eval_fn EvalFn) { add_fn(mut env, '+', 2, 2, fn (args List) !Type { return Int{args.nth(0).int_()! + args.nth(1).int_()!} @@ -39,10 +51,10 @@ pub fn add_core(mut env Env, eval_fn EvalFn) { return make_bool(args.nth(0) is List) }) add_fn(mut env, 'empty?', 1, 1, fn (args List) !Type { - return make_bool(args.nth(0).list_or_vec()!.len == 0) + return make_bool(args.nth(0).sequence()!.len == 0) }) add_fn(mut env, 'count', 1, 1, fn (args List) !Type { - return Int{args.nth(0).list_or_vec()!.len} + return Int{args.nth(0).sequence()!.len} }) add_fn(mut env, '=', 2, 2, fn (args List) !Type { return make_bool(args.nth(0).eq(args.nth(1))) @@ -98,37 +110,37 @@ pub fn add_core(mut env Env, eval_fn EvalFn) { // list := arrays.concat[Type]([atom.typ], ...args.from(2).list) mut list := [atom.typ] list << args.from(2).list - return atom.set(args.nth(1).fn_apply(eval_fn, List{list})!) + return atom.set(apply(args.nth(1), eval_fn, List{list})!) }) add_fn(mut env, 'cons', 2, 2, fn (args List) !Type { // BUG: << doesn't like templated sumtype array args // https://github.com/vlang/v/issues/17259 // return List{arrays.concat[Type]([*args.nth(0)], ...args.nth(1).list()!)} mut list := [*args.nth(0)] - list << args.nth(1).list_or_vec()! + list << args.nth(1).sequence()! return List{list} }) add_fn(mut env, 'concat', 0, -1, fn (args List) !Type { mut list := []Type{} for arg in args.list { - list << arg.list_or_vec()! + list << arg.sequence()! } return List{list} }) add_fn(mut env, 'vec', 1, 1, fn (args List) !Type { - return Vector{args.nth(0).list_or_vec()!} + return Vector{args.nth(0).sequence()!} }) add_fn(mut env, 'nth', 2, 2, fn (args List) !Type { - list := args.nth(0).list_or_vec()! + list := args.nth(0).sequence()! i := args.nth(1).int_()! return if i < list.len { list[i] } else { error('out of range') } }) add_fn(mut env, 'first', 1, 1, fn (args List) !Type { - list := List{args.nth(0).list_or_vec()!} + list := List{args.nth(0).sequence()!} return *list.nth(0) }) add_fn(mut env, 'rest', 1, 1, fn (args List) !Type { - list := List{args.nth(0).list_or_vec()!} + list := List{args.nth(0).sequence()!} return list.rest() }) } diff --git a/impls/v/mal/printer.v b/impls/v/mal/printer.v index d48c9b0490..14f3c5f515 100644 --- a/impls/v/mal/printer.v +++ b/impls/v/mal/printer.v @@ -41,7 +41,7 @@ pub fn pr_str(ast Type, readable bool) string { } Hashmap { '{' + maps.to_array(ast.hm, fn [readable] (k string, v Type) string { - return '${k} ${pr_str(v, readable)}' + return '${pr_str(unkey(k), readable)} ${pr_str(v, readable)}' }).join(' ') + '}' } Fn { diff --git a/impls/v/mal/reader.v b/impls/v/mal/reader.v index 77c5773a5a..a8706a7c1c 100644 --- a/impls/v/mal/reader.v +++ b/impls/v/mal/reader.v @@ -31,20 +31,14 @@ fn (mut r Reader) peek() ?Token { } fn hash_list(list []Type) !map[string]Type { - mut list_ := list[0..] mut hash := map[string]Type{} + mut list_ := list[0..] if list_.len % 2 == 1 { return error('extra hashmap param') } for list_.len > 0 { - key, val := list_[0], list_[1] - if key is String { - hash['"${key.val}"'] = val - } else if key is Keyword { - hash[':${key.kw}'] = val - } else { - return error('bad hashmap key') - } + k, v := list_[0], list_[1] + hash[k.key() or { return error('bad hashmap key') }] = v list_ = list_[2..] } return hash diff --git a/impls/v/mal/types.v b/impls/v/mal/types.v index 0216e007c3..e077ea4f6a 100644 --- a/impls/v/mal/types.v +++ b/impls/v/mal/types.v @@ -1,5 +1,7 @@ module mal +import maps + type Type = Atom | Closure | False @@ -67,6 +69,22 @@ pub fn (t Type) call_sym() ?string { return none } +pub fn (t Type) key() !string { + return match t { + String { '"${t.val}' } + Keyword { ':${t.kw}' } + else { error('bad key') } + } +} + +pub fn unkey(key string) Type { + return match key[0] { + `:` { Keyword{key[1..]} } + `"` { String{key[1..]} } + else { Nil{} } + } +} + pub fn (t Type) numeric() bool { return t in [Int, Float] } @@ -125,14 +143,23 @@ pub fn (t Type) eq(o Type) bool { return a.sym == (b as Symbol).sym } Hashmap { - return a.hm == (b as Hashmap).hm + if a.hm.len != (b as Hashmap).hm.len { + return false + } + for k, v in a.hm { + bv := (b as Hashmap).hm[k] or { return false } + if !v.eq(bv) { + return false + } + } + return true } Fn { return a.f == (b as Fn).f } Closure { - return - a.env == (b as Closure).env && a.ast == (b as Closure).ast && a.params == (b as Closure).params + bc := b as Closure + return a.env == bc.env && a.ast == bc.ast && a.params == bc.params } Atom { panic('unresolved atom') @@ -150,18 +177,6 @@ pub fn (t Type) lt(o Type) !bool { } } -pub fn (t Type) fn_apply(eval_fn EvalFn, args List) !Type { - if t is Fn { - return t.f(args)! - } else if t is Closure { - mut env := mk_env(t.env) - env.bind(t.params, args) - return eval_fn(t.ast, mut env)! - } else { - return error('function expected') - } -} - pub fn (t Type) sym() !string { return if t is Symbol { t.sym } else { error('symbol expected') } } @@ -186,7 +201,7 @@ pub fn (t Type) list() ![]Type { return if t is List { t.list } else { error('list expected') } } -pub fn (t Type) list_or_vec() ![]Type { +pub fn (t Type) sequence() ![]Type { return match t { List { t.list } Vector { t.vec } diff --git a/impls/v/step3_env.v b/impls/v/step3_env.v index 9c90ae8f30..d430f83525 100644 --- a/impls/v/step3_env.v +++ b/impls/v/step3_env.v @@ -18,7 +18,7 @@ fn eval(ast mal.Type, mut env mal.Env) !mal.Type { 'let*' { mal.check_args(args, 2, 2) or { return error('let*: ${err}') } mut new_env := mal.mk_env(&env) - tmp := args.nth(0).list_or_vec() or { return error('let*: ${err}') } + tmp := args.nth(0).sequence() or { return error('let*: ${err}') } mut pairs := tmp[0..] // copy if pairs.len % 2 == 1 { return error('let*: extra binding param') diff --git a/impls/v/step4_if_fn_do.v b/impls/v/step4_if_fn_do.v index 7a52831524..d32e287084 100644 --- a/impls/v/step4_if_fn_do.v +++ b/impls/v/step4_if_fn_do.v @@ -18,7 +18,7 @@ fn eval(ast mal.Type, mut env mal.Env) !mal.Type { 'let*' { mal.check_args(args, 2, 2) or { return error('let*: ${err}') } mut new_env := mal.mk_env(&env) - tmp := args.nth(0).list_or_vec() or { return error('let*: ${err}') } + tmp := args.nth(0).sequence() or { return error('let*: ${err}') } mut pairs := tmp[0..] // copy if pairs.len % 2 == 1 { return error('let*: extra binding param') @@ -42,7 +42,7 @@ fn eval(ast mal.Type, mut env mal.Env) !mal.Type { } 'fn*' { mal.check_args(args, 2, 2) or { return error('fn*: ${err}') } - binds := args.nth(0).list_or_vec() or { return error('fn*: ${err}') } + binds := args.nth(0).sequence() or { return error('fn*: ${err}') } syms := binds.map(it.sym() or { return error('fn*: ${err}') }) body := args.nth(1) for i, sym in syms { diff --git a/impls/v/step5_tco.v b/impls/v/step5_tco.v index 03e46f9e49..36f39762fc 100644 --- a/impls/v/step5_tco.v +++ b/impls/v/step5_tco.v @@ -22,7 +22,7 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { 'let*' { mal.check_args(args, 2, 2) or { return error('let*: ${err}') } env = unsafe { mal.mk_env(env) } // TCO - tmp := args.nth(0).list_or_vec() or { return error('let*: ${err}') } + tmp := args.nth(0).sequence() or { return error('let*: ${err}') } mut pairs := tmp[0..] // copy if pairs.len % 2 == 1 { return error('let*: extra binding param') @@ -54,7 +54,7 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { } 'fn*' { mal.check_args(args, 2, 2) or { return error('fn*: ${err}') } - binds := args.nth(0).list_or_vec() or { return error('fn*: ${err}') } + binds := args.nth(0).sequence() or { return error('fn*: ${err}') } syms := binds.map(it.sym() or { return error('fn*: ${err}') }) for i, sym in syms { if sym == '&' && syms.len != i + 2 { diff --git a/impls/v/step6_file.v b/impls/v/step6_file.v index 3c02ee23c1..37f636d5c5 100644 --- a/impls/v/step6_file.v +++ b/impls/v/step6_file.v @@ -23,7 +23,7 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { 'let*' { mal.check_args(args, 2, 2) or { return error('let*: ${err}') } env = unsafe { mal.mk_env(env) } // TCO - tmp := args.nth(0).list_or_vec() or { return error('let*: ${err}') } + tmp := args.nth(0).sequence() or { return error('let*: ${err}') } mut pairs := tmp[0..] // copy if pairs.len % 2 == 1 { return error('let*: extra binding param') @@ -55,7 +55,7 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { } 'fn*' { mal.check_args(args, 2, 2) or { return error('fn*: ${err}') } - binds := args.nth(0).list_or_vec() or { return error('fn*: ${err}') } + binds := args.nth(0).sequence() or { return error('fn*: ${err}') } syms := binds.map(it.sym() or { return error('fn*: ${err}') }) for i, sym in syms { if sym == '&' && syms.len != i + 2 { diff --git a/impls/v/step7_quote.v b/impls/v/step7_quote.v index eb1ead48bb..82f293e23c 100644 --- a/impls/v/step7_quote.v +++ b/impls/v/step7_quote.v @@ -56,7 +56,7 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { 'let*' { mal.check_args(args, 2, 2) or { return error('let*: ${err}') } env = unsafe { mal.mk_env(env) } // TCO - tmp := args.nth(0).list_or_vec() or { return error('let*: ${err}') } + tmp := args.nth(0).sequence() or { return error('let*: ${err}') } mut pairs := tmp[0..] // copy if pairs.len % 2 == 1 { return error('let*: extra binding param') @@ -88,7 +88,7 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { } 'fn*' { mal.check_args(args, 2, 2) or { return error('fn*: ${err}') } - binds := args.nth(0).list_or_vec() or { return error('fn*: ${err}') } + binds := args.nth(0).sequence() or { return error('fn*: ${err}') } syms := binds.map(it.sym() or { return error('fn*: ${err}') }) for i, sym in syms { if sym == '&' && syms.len != i + 2 { From 3c4504e78df55e84c3c6f810fa45ff62a869982b Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Thu, 9 Feb 2023 20:12:53 +0000 Subject: [PATCH 18/30] v: step 9 --- impls/v/mal/core.v | 85 ++++++++++++- impls/v/mal/types.v | 67 +++++++++++ impls/v/step9_try.v | 283 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 433 insertions(+), 2 deletions(-) create mode 100644 impls/v/step9_try.v diff --git a/impls/v/mal/core.v b/impls/v/mal/core.v index aa9994360d..9581440d78 100644 --- a/impls/v/mal/core.v +++ b/impls/v/mal/core.v @@ -24,10 +24,14 @@ pub fn check_args(args List, min int, max int) ! { } } +fn wrap_err(sym string, err IError) IError { + return if err is Exception { IError(err) } else { error('${sym}: ${err}') } +} + pub fn add_fn(mut env Env, sym string, min int, max int, f FnDef) { env.set(sym, Fn{fn [sym, min, max, f] (args List) !Type { - check_args(args, min, max) or { return error('${sym}: ${err}') } - return f(args) or { error('${sym}: ${err}') } + check_args(args, min, max) or { return wrap_err(sym, err) } + return f(args) or { return wrap_err(sym, err) } }}) } @@ -143,4 +147,81 @@ pub fn add_core(mut env Env, eval_fn EvalFn) { list := List{args.nth(0).sequence()!} return list.rest() }) + add_fn(mut env, 'throw', 1, 1, fn (args List) !Type { + return IError(Exception{Error{}, args.nth(0)}) + }) + add_fn(mut env, 'apply', 2, -1, fn [eval_fn] (args List) !Type { + // BUG: << doesn't like templated sumtype array args + // https://github.com/vlang/v/issues/17259 + // list := arrays.concat(args.range(1, args.len() - 2).list, args.last()!.sequence()!) + mut list := args.list[1..args.len() - 1] + list << args.last()!.sequence()! + return apply(args.nth(0), eval_fn, List{list}) + }) + add_fn(mut env, 'map', 2, 2, fn [eval_fn] (args List) !Type { + mut list := []Type{} + for typ in args.nth(1).sequence()! { + list << apply(args.nth(0), eval_fn, List{[typ]})! + } + return List{list} + }) + add_fn(mut env, 'nil?', 1, 1, fn (args List) !Type { + return make_bool(args.nth(0) is Nil) + }) + add_fn(mut env, 'true?', 1, 1, fn (args List) !Type { + return make_bool(args.nth(0) is True) + }) + add_fn(mut env, 'false?', 1, 1, fn (args List) !Type { + return make_bool(args.nth(0) is False) + }) + add_fn(mut env, 'symbol?', 1, 1, fn (args List) !Type { + return make_bool(args.nth(0) is Symbol) + }) + add_fn(mut env, 'symbol', 1, 1, fn (args List) !Type { + return Symbol{args.nth(0).str_()!} + }) + add_fn(mut env, 'keyword', 1, 1, fn (args List) !Type { + arg0 := args.nth(0) + return match arg0 { + Keyword { arg0 } + String { Keyword{arg0.val} } + else { error('keyword/string expected') } + } + }) + add_fn(mut env, 'keyword?', 1, 1, fn (args List) !Type { + return make_bool(args.nth(0) is Keyword) + }) + add_fn(mut env, 'vector', -1, -1, fn (args List) !Type { + return Vector{args.list} + }) + add_fn(mut env, 'vector?', 1, 1, fn (args List) !Type { + return make_bool(args.nth(0) is Vector) + }) + add_fn(mut env, 'sequential?', 1, 1, fn (args List) !Type { + return make_bool(args.nth(0) in [Vector, List]) + }) + add_fn(mut env, 'hash-map', -1, -1, fn (args List) !Type { + return make_hashmap(args)! + }) + add_fn(mut env, 'map?', 1, 1, fn (args List) !Type { + return make_bool(args.nth(0) is Hashmap) + }) + add_fn(mut env, 'assoc', 2, -1, fn (args List) !Type { + return make_hashmap(Type(args.first()!.hashmap()!), args.rest())! + }) + add_fn(mut env, 'dissoc', 2, -1, fn (args List) !Type { + return args.first()!.hashmap()!.filter(args.from(1))! + }) + add_fn(mut env, 'get', 2, 2, fn (args List) !Type { + return args.nth(0).hashmap()!.get(args.nth(1).key()!) + }) + add_fn(mut env, 'contains?', 2, 2, fn (args List) !Type { + return make_bool(args.nth(0).hashmap()!.has(args.nth(1).key()!)) + }) + add_fn(mut env, 'keys', 1, 1, fn (args List) !Type { + return List{args.nth(0).hashmap()!.hm.keys().map(unkey(it))} + }) + add_fn(mut env, 'vals', 1, 1, fn (args List) !Type { + return List{args.nth(0).hashmap()!.hm.values()} + }) } diff --git a/impls/v/mal/types.v b/impls/v/mal/types.v index e077ea4f6a..0f919fddc8 100644 --- a/impls/v/mal/types.v +++ b/impls/v/mal/types.v @@ -214,6 +214,14 @@ pub fn (t &Type) atom() !&Atom { return if t is Atom { unsafe { &t } } else { error('atom expected') } } +pub fn (t &Type) hashmap() !&Hashmap { + return match t { + Hashmap { unsafe { &t } } + Nil { &Hashmap{} } + else { error('hashmap expected') } + } +} + // -- pub struct Int { @@ -306,6 +314,53 @@ pub: hm map[string]Type } +pub fn (h &Hashmap) filter(list List) !Hashmap { + mut list_ := list.list.map(it.key()!) + return Hashmap{maps.filter(h.hm, fn [list_] (k string, _ Type) bool { + return k !in list_ + })} +} + +pub fn (h &Hashmap) get(key string) Type { + if val := h.hm[key] { + return val + } else { + return Nil{} + } +} + +pub fn (h &Hashmap) has(key string) bool { + return if _ := h.hm[key] { true } else { false } +} + +pub fn make_hashmap(srcs ...Type) !Hashmap { + mut hm := map[string]Type{} + for src in srcs { + match src { + List { + mut list := src.list[0..] // copy + if list.len % 2 == 1 { + return error('extra param') + } + for list.len > 0 { + k, v := list[0], list[1] + hm[k.key()!] = v + list = list[2..] + } + } + Hashmap { + for k, v in src.hm { + hm[k] = v + } + } + else { + panic('make_hashmap') + } + } + } + return Hashmap{hm} +} + // -- pub struct Fn { @@ -347,3 +402,15 @@ fn (a &Atom) set(t Type) Type { mut_a.typ = t return t } + +// -- + +struct Exception { + Error +pub: + typ Type +} + +fn (e Exception) msg() string { + return 'Exception' +} diff --git a/impls/v/step9_try.v b/impls/v/step9_try.v new file mode 100644 index 0000000000..49a0505354 --- /dev/null +++ b/impls/v/step9_try.v @@ -0,0 +1,283 @@ +import mal +import os +import readline { read_line } + +fn rep_read(input string) !mal.Type { + return mal.read_str(input)! +} + +fn quasiquote_list(list []mal.Type) !mal.Type { + mut res := []mal.Type{} + for elt in list.reverse() { + if elt is mal.List && elt.call_sym() or { '' } == 'splice-unquote' { + res = [mal.Symbol{'concat'}, elt.nth(1), mal.List{res}] + } else { + res = [mal.Symbol{'cons'}, quasiquote(elt)!, mal.List{res}] + } + } + return mal.List{res} +} + +fn quasiquote(ast mal.Type) !mal.Type { + return match ast { + mal.List { + if ast.nth(0).eq(mal.Symbol{'unquote'}) { + *ast.nth(1) + } else { + quasiquote_list(ast.list)! + } + } + mal.Vector { + mal.List{[mal.Symbol{'vec'}, quasiquote_list(ast.vec)!]} + } + mal.Symbol, mal.Hashmap { + mal.List{[mal.Symbol{'quote'}, ast]} + } + else { + ast + } + } +} + +fn is_macro_call(ast mal.Type, env mal.Env) ?mal.Closure { + if sym := ast.call_sym() { + if cls := env.find(sym) { + if cls is mal.Closure { + if cls.is_macro { + return cls + } + } + } + } + return none +} + +fn macroexpand(ast_ mal.Type, env mal.Env) !mal.Type { + mut ast := unsafe { &ast_ } + for { + cls := is_macro_call(ast, env) or { break } + res := mal.apply(cls, eval, (ast as mal.List).rest())! + ast = unsafe { &res } + } + return *ast +} + +fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { + mut ast := unsafe { &ast_ } + mut env := unsafe { &env_ } + for true { + if ast !is mal.List { + return eval_ast(ast, mut env)! + } + expanded := macroexpand(ast, env)! + ast = unsafe { &expanded } + if ast !is mal.List { + return eval_ast(ast, mut env)! + } + ast0 := (ast as mal.List).first() or { return *ast } // return empty list + args := (ast as mal.List).rest() + match ast0.sym() or { '' } { + 'def!' { + mal.check_args(args, 2, 2) or { return error('def!: ${err}') } + sym := args.nth(0).sym() or { return error('def!: ${err}') } + return env.set(sym, eval(args.nth(1), mut env)!) + } + 'let*' { + mal.check_args(args, 2, 2) or { return error('let*: ${err}') } + env = unsafe { mal.mk_env(env) } // TCO + tmp := args.nth(0).sequence() or { return error('let*: ${err}') } + mut pairs := tmp[0..] // copy + if pairs.len % 2 == 1 { + return error('let*: extra binding param') + } + for pairs.len > 0 { + sym := pairs[0].sym() or { return error('let*: ${err}') } + env.set(sym, eval(pairs[1], mut env)!) + pairs = pairs[2..] + } + ast = unsafe { args.nth(1) } // TCO + } + 'do' { + if args.len() > 1 { + args.list[0..args.list.len - 1].map(eval(it, mut env)!) + } else if args.len() == 0 { + return mal.Nil{} + } + ast = unsafe { args.last()! } // TCO + } + 'if' { + mal.check_args(args, 2, 3) or { return error('if: ${err}') } + if eval(args.nth(0), mut env)!.truthy() { + ast = unsafe { args.nth(1) } // TCO + } else if args.len() == 3 { + ast = unsafe { args.nth(2) } // TCO + } else { + return mal.Nil{} + } + } + 'fn*' { + mal.check_args(args, 2, 2) or { return error('fn*: ${err}') } + binds := args.nth(0).sequence() or { return error('fn*: ${err}') } + syms := binds.map(it.sym() or { return error('fn*: ${err}') }) + for i, sym in syms { + if sym == '&' && syms.len != i + 2 { + return error('fn*: & has 1 arg') + } + } + return mal.Closure{args.nth(1), syms, env, false} + } + 'quote' { + mal.check_args(args, 1, 1) or { return error('quote: ${err}') } + return *args.nth(0) + } + 'quasiquoteexpand' { + mal.check_args(args, 1, 1) or { return error('quasiquoteexpand: ${err}') } + return quasiquote(args.nth(0)) or { return error('quasiquoteexpand: ${err}') } + } + 'quasiquote' { + mal.check_args(args, 1, 1) or { return error('quasiquote: ${err}') } + res := quasiquote(args.nth(0)) or { return error('quasiquote: ${err}') } + ast = unsafe { &res } // TCO + } + 'defmacro!' { + mal.check_args(args, 2, 2) or { return error('defmacro!: ${err}') } + sym := args.nth(0).sym() or { return error('defmacro!: ${err}') } + cls := eval(args.nth(1), mut env)!.cls() or { return error('defmacro!: ${err}') } + return env.set(sym, cls.to_macro()) + } + 'macroexpand' { + mal.check_args(args, 1, 1) or { return error('macroexpand: ${err}') } + return macroexpand(args.nth(0), env) + } + 'try*' { + mal.check_args(args, 1, 2) or { return error('try*: ${err}') } + if res := eval(args.nth(0), mut env) { + return res + } else { + if args.nth(1).call_sym() or { '' } == 'catch*' { + cargs := (args.nth(1) as mal.List).rest() + mal.check_args(cargs, 2, 2) or { return error('catch* ${err}') } + sym := cargs.nth(0).sym() or { return error('catch*: ${err}') } + typ := if err is mal.Exception { err.typ } else { mal.String{err.msg()} } + ast = cargs.nth(1) // TCO + env = unsafe { mal.mk_env(env) } + env.set(sym, typ) + } else { + return mal.Nil{} + } + } + } + else { // regular list apply w/ TCO + res := eval_ast(ast, mut env)! as mal.List + list0 := res.list[0] + if list0 is mal.Fn { + return list0.f(res.rest())! + } else if list0 is mal.Closure { + ast = &list0.ast // TCO + env = mal.mk_env(list0.env) + env.bind(list0.params, res.rest()) + } else { + return error('function expected') + } + } + } + } + panic('unreachable code') +} + +fn eval_ast(ast mal.Type, mut env mal.Env) !mal.Type { + match ast { + mal.Symbol { + return env.get(ast.sym)! + } + mal.List { + return mal.List{ast.list.map(eval(it, mut env)!)} + } + mal.Vector { + return mal.Vector{ast.vec.map(eval(it, mut env)!)} + } + mal.Hashmap { + mut hm := map[string]mal.Type{} + for key in ast.hm.keys() { + hm[key] = eval(ast.hm[key] or { panic('') }, mut env)! + } + return mal.Hashmap{hm} + } + else { + return ast + } + } +} + +fn rep_print(ast mal.Type) string { + return mal.pr_str(ast, true) +} + +fn rep(line string, mut env mal.Env) string { + ast := rep_read(line) or { + if err.msg() != 'no form' { + println('Error: ${err}') + } + return '' + } + $if ast ? { + println('AST:\n${ast}') + } + if res := eval(ast, mut env) { + return rep_print(res) + } else { + if err is mal.Exception { + println('Exception: ${mal.pr_str(err.typ, true)}') + } else { + println('Error: ${err}') + } + return '' + } +} + +fn main() { + // outer-most env + mut env := mal.Env{} + + // add eval + mut envp := &env // workaround no closure references in V + mal.add_fn(mut env, 'eval', 1, 1, fn [mut envp] (args mal.List) !mal.Type { + return eval(args.nth(0), mut envp)! + }) + + // core env + mal.add_core(mut env, eval) + + // mal defined env + rep('(def! not (fn* (a) (if a false true)))', mut env) + rep('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))', mut + env) + rep('(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list \'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons \'cond (rest (rest xs)))))))', mut + env) + + if os.args.len > 1 { + // run script + file := os.args[1] + .replace('\\', '\\\\') + .replace('"', '\\"') + mut args := []mal.Type{} + for i in 2 .. os.args.len { + args << mal.Type(mal.String{os.args[i]}) + } + env.set('*ARGV*', mal.List{args}) + rep('(load-file "${file}")', mut env) + } else { + // repl + env.set('*ARGV*', mal.List{}) + for { + line := read_line('user> ') or { + println('') // newline + break + } + out := rep(line, mut env) + if out.len > 0 { + println(out) + } + } + } +} From b3a17dca3a91f22c0b3e84fdf190639c99d76849 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Fri, 10 Feb 2023 18:45:12 +0000 Subject: [PATCH 19/30] v: fix bug in step 9 with try* --- impls/v/step9_try.v | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/impls/v/step9_try.v b/impls/v/step9_try.v index 49a0505354..59fab94c9c 100644 --- a/impls/v/step9_try.v +++ b/impls/v/step9_try.v @@ -68,13 +68,20 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { for true { if ast !is mal.List { return eval_ast(ast, mut env)! + } else if (ast as mal.List).len() == 0 { + return *ast } + + // macro expansion expanded := macroexpand(ast, env)! ast = unsafe { &expanded } if ast !is mal.List { return eval_ast(ast, mut env)! + } else if (ast as mal.List).len() == 0 { + return *ast } - ast0 := (ast as mal.List).first() or { return *ast } // return empty list + + ast0 := (ast as mal.List).first()! args := (ast as mal.List).rest() match ast0.sym() or { '' } { 'def!' { @@ -163,7 +170,7 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { env = unsafe { mal.mk_env(env) } env.set(sym, typ) } else { - return mal.Nil{} + return err } } } From e620ad868c0dadc93caf75061503cefa8888ea1a Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Sat, 11 Feb 2023 15:52:28 +0000 Subject: [PATCH 20/30] v: add factory fns for hashmaps, lists, vectors, fns, closures --- impls/v/mal/core.v | 69 +++++++++++---- impls/v/mal/reader.v | 8 +- impls/v/mal/types.v | 175 +++++++++++++++++++++++++++++---------- impls/v/step2_eval.v | 22 ++--- impls/v/step3_env.v | 22 ++--- impls/v/step4_if_fn_do.v | 10 ++- impls/v/step5_tco.v | 8 +- impls/v/step6_file.v | 10 +-- impls/v/step7_quote.v | 20 ++--- impls/v/step8_macros.v | 20 ++--- impls/v/step9_try.v | 20 ++--- 11 files changed, 255 insertions(+), 129 deletions(-) diff --git a/impls/v/mal/core.v b/impls/v/mal/core.v index 9581440d78..d991220c16 100644 --- a/impls/v/mal/core.v +++ b/impls/v/mal/core.v @@ -1,8 +1,9 @@ module mal import os +import readline { read_line } -type EvalFn = fn (Type, mut Env) !Type +type EvalFn = fn (_ Type, mut _ Env) !Type pub fn apply(ast Type, eval_fn EvalFn, args List) !Type { if ast is Fn { @@ -29,10 +30,10 @@ fn wrap_err(sym string, err IError) IError { } pub fn add_fn(mut env Env, sym string, min int, max int, f FnDef) { - env.set(sym, Fn{fn [sym, min, max, f] (args List) !Type { + env.set(sym, new_fn(fn [sym, min, max, f] (args List) !Type { check_args(args, min, max) or { return wrap_err(sym, err) } return f(args) or { return wrap_err(sym, err) } - }}) + })) } pub fn add_core(mut env Env, eval_fn EvalFn) { @@ -114,25 +115,25 @@ pub fn add_core(mut env Env, eval_fn EvalFn) { // list := arrays.concat[Type]([atom.typ], ...args.from(2).list) mut list := [atom.typ] list << args.from(2).list - return atom.set(apply(args.nth(1), eval_fn, List{list})!) + return atom.set(apply(args.nth(1), eval_fn, new_list(list))!) }) add_fn(mut env, 'cons', 2, 2, fn (args List) !Type { // BUG: << doesn't like templated sumtype array args // https://github.com/vlang/v/issues/17259 - // return List{arrays.concat[Type]([*args.nth(0)], ...args.nth(1).list()!)} + // return new_list(arrays.concat[Type]([*args.nth(0)], ...args.nth(1).list()!)) mut list := [*args.nth(0)] list << args.nth(1).sequence()! - return List{list} + return new_list(list) }) add_fn(mut env, 'concat', 0, -1, fn (args List) !Type { mut list := []Type{} for arg in args.list { list << arg.sequence()! } - return List{list} + return new_list(list) }) add_fn(mut env, 'vec', 1, 1, fn (args List) !Type { - return Vector{args.nth(0).sequence()!} + return new_vector(args.nth(0).sequence()!) }) add_fn(mut env, 'nth', 2, 2, fn (args List) !Type { list := args.nth(0).sequence()! @@ -140,11 +141,11 @@ pub fn add_core(mut env Env, eval_fn EvalFn) { return if i < list.len { list[i] } else { error('out of range') } }) add_fn(mut env, 'first', 1, 1, fn (args List) !Type { - list := List{args.nth(0).sequence()!} + list := new_list(args.nth(0).sequence()!) return *list.nth(0) }) add_fn(mut env, 'rest', 1, 1, fn (args List) !Type { - list := List{args.nth(0).sequence()!} + list := new_list(args.nth(0).sequence()!) return list.rest() }) add_fn(mut env, 'throw', 1, 1, fn (args List) !Type { @@ -156,14 +157,14 @@ pub fn add_core(mut env Env, eval_fn EvalFn) { // list := arrays.concat(args.range(1, args.len() - 2).list, args.last()!.sequence()!) mut list := args.list[1..args.len() - 1] list << args.last()!.sequence()! - return apply(args.nth(0), eval_fn, List{list}) + return apply(args.nth(0), eval_fn, new_list(list)) }) add_fn(mut env, 'map', 2, 2, fn [eval_fn] (args List) !Type { mut list := []Type{} for typ in args.nth(1).sequence()! { - list << apply(args.nth(0), eval_fn, List{[typ]})! + list << apply(args.nth(0), eval_fn, new_list([typ]))! } - return List{list} + return new_list(list) }) add_fn(mut env, 'nil?', 1, 1, fn (args List) !Type { return make_bool(args.nth(0) is Nil) @@ -192,7 +193,7 @@ pub fn add_core(mut env Env, eval_fn EvalFn) { return make_bool(args.nth(0) is Keyword) }) add_fn(mut env, 'vector', -1, -1, fn (args List) !Type { - return Vector{args.list} + return new_vector(args.list) }) add_fn(mut env, 'vector?', 1, 1, fn (args List) !Type { return make_bool(args.nth(0) is Vector) @@ -201,13 +202,13 @@ pub fn add_core(mut env Env, eval_fn EvalFn) { return make_bool(args.nth(0) in [Vector, List]) }) add_fn(mut env, 'hash-map', -1, -1, fn (args List) !Type { - return make_hashmap(args)! + return new_hashmap({}).load(args)! }) add_fn(mut env, 'map?', 1, 1, fn (args List) !Type { return make_bool(args.nth(0) is Hashmap) }) add_fn(mut env, 'assoc', 2, -1, fn (args List) !Type { - return make_hashmap(Type(args.first()!.hashmap()!), args.rest())! + return new_hashmap(args.first()!.hashmap()!.hm).load(args.rest())! }) add_fn(mut env, 'dissoc', 2, -1, fn (args List) !Type { return args.first()!.hashmap()!.filter(args.from(1))! @@ -219,9 +220,41 @@ pub fn add_core(mut env Env, eval_fn EvalFn) { return make_bool(args.nth(0).hashmap()!.has(args.nth(1).key()!)) }) add_fn(mut env, 'keys', 1, 1, fn (args List) !Type { - return List{args.nth(0).hashmap()!.hm.keys().map(unkey(it))} + return new_list(args.nth(0).hashmap()!.hm.keys().map(unkey(it))) }) add_fn(mut env, 'vals', 1, 1, fn (args List) !Type { - return List{args.nth(0).hashmap()!.hm.values()} + return new_list(args.nth(0).hashmap()!.hm.values()) + }) + add_fn(mut env, 'readline', 1, 1, fn (args List) !Type { + if line := read_line(args.nth(0).str_()!) { + return String{line.replace('\n', '')} + } else { + println('') // newline + return Nil{} + } + }) + add_fn(mut env, 'time-ms', -1, -1, fn (args List) !Type { + return error('not implemented') + }) + add_fn(mut env, 'meta', 1, 1, fn (args List) !Type { + return args.nth(0).get_meta() + }) + add_fn(mut env, 'with-meta', -1, -1, fn (args List) !Type { + return error('not implemented') + }) + add_fn(mut env, 'fn?', -1, -1, fn (args List) !Type { + return error('not implemented') + }) + add_fn(mut env, 'string?', -1, -1, fn (args List) !Type { + return error('not implemented') + }) + add_fn(mut env, 'number?', -1, -1, fn (args List) !Type { + return error('not implemented') + }) + add_fn(mut env, 'seq', -1, -1, fn (args List) !Type { + return error('not implemented') + }) + add_fn(mut env, 'conj', -1, -1, fn (args List) !Type { + return error('not implemented') }) } diff --git a/impls/v/mal/reader.v b/impls/v/mal/reader.v index a8706a7c1c..71ffe88f3e 100644 --- a/impls/v/mal/reader.v +++ b/impls/v/mal/reader.v @@ -51,15 +51,15 @@ fn (mut r Reader) macro_wrap(name string, num_forms int) !List { list << r.read_form() or { return error('${name}: missing param') } } list << Symbol{name} - return List{list.reverse()} + return new_list(list.reverse()) } fn (mut r Reader) read_form() !Type { tok := r.peek() or { return error('no form') } return match true { - tok == '(' { List{r.read_list(')')!} } - tok == '[' { Vector{r.read_list(']')!} } - tok == '{' { Hashmap{hash_list(r.read_list('}')!)!} } + tok == '(' { new_list(r.read_list(')')!) } + tok == '[' { new_vector(r.read_list(']')!) } + tok == '{' { new_hashmap(hash_list(r.read_list('}')!)!) } tok == "'" { r.macro_wrap('quote', 1)! } tok == '`' { r.macro_wrap('quasiquote', 1)! } tok == '~' { r.macro_wrap('unquote', 1)! } diff --git a/impls/v/mal/types.v b/impls/v/mal/types.v index 0f919fddc8..26b10de9ca 100644 --- a/impls/v/mal/types.v +++ b/impls/v/mal/types.v @@ -36,10 +36,10 @@ fn implicit_conv(a_ Type, b_ Type) !(Type, Type) { return a, Float{b.val} } if a is Vector && b is List { - return List{a.vec}, b + return new_list(a.vec), b } if a is List && b is Vector { - return a, List{b.vec} + return a, new_list(b.vec) } // fail return error('type mismatch') @@ -177,6 +177,28 @@ pub fn (t Type) lt(o Type) !bool { } } +pub fn (t Type) get_meta() !Type { + return match t { + Fn, Closure, List, Vector, Hashmap { + t.meta + } + else { + error('metadata not supported here') + } + } +} + +pub fn (t Type) set_meta(meta Type) !Type { + return match t { + Fn, Closure, List, Vector, Hashmap { + t.with_meta(meta) + } + else { + error('metadata not supported here') + } + } +} + pub fn (t Type) sym() !string { return if t is Symbol { t.sym } else { error('symbol expected') } } @@ -214,10 +236,10 @@ pub fn (t &Type) atom() !&Atom { return if t is Atom { unsafe { &t } } else { error('atom expected') } } -pub fn (t &Type) hashmap() !&Hashmap { +pub fn (t &Type) hashmap() !Hashmap { return match t { - Hashmap { unsafe { &t } } - Nil { &Hashmap{} } + Hashmap { unsafe { t } } + Nil { new_hashmap({}) } else { error('hashmap expected') } } } @@ -274,6 +296,20 @@ pub: pub struct List { pub: list []Type + meta Type = Nil{} +} + +pub fn new_list(list []Type) List { + return List{ + list: list + } +} + +pub fn (l &List) with_meta(meta Type) Type { + return List{ + list: l.list + meta: meta + } } pub fn (l &List) first() !&Type { @@ -289,7 +325,7 @@ pub fn (l &List) rest() List { } pub fn (l &List) from(n int) List { - return if l.list.len > n { List{l.list[n..]} } else { List{} } + return if l.list.len > n { new_list(l.list[n..]) } else { List{} } } pub fn (l List) len() int { @@ -304,21 +340,74 @@ pub fn (l &List) nth(n int) &Type { pub struct Vector { pub: - vec []Type + vec []Type + meta Type = Nil{} +} + +pub fn new_vector(vec []Type) Vector { + return Vector{ + vec: vec + } +} + +pub fn (v &Vector) with_meta(meta Type) Type { + return Vector{ + vec: v.vec + meta: meta + } } // -- pub struct Hashmap { pub: - hm map[string]Type + hm map[string]Type + meta Type = Nil{} +} + +pub fn new_hashmap(from map[string]Type) Hashmap { + return Hashmap{ + hm: from + } +} + +fn (m map[string]Type) copy() map[string]Type { + mut hm := map[string]Type{} + for k, v in m { + hm[k] = v + } + return hm +} + +pub fn (h &Hashmap) load(list List) !Hashmap { + mut hm := h.hm.copy() + mut list_ := list.list[0..] // copy + if list_.len % 2 == 1 { + return error('extra param') + } + for list_.len > 0 { + k, v := list_[0], list_[1] + hm[k.key()!] = v + list_ = list_[2..] + } + return Hashmap{ + hm: hm + meta: h.meta + } +} + +pub fn (h &Hashmap) with_meta(meta Type) Type { + return Hashmap{ + hm: h.hm + meta: meta + } } pub fn (h &Hashmap) filter(list List) !Hashmap { mut list_ := list.list.map(it.key()!) - return Hashmap{maps.filter(h.hm, fn [list_] (k string, _ Type) bool { + return new_hashmap(maps.filter(h.hm, fn [list_] (k string, _ Type) bool { return k !in list_ - })} + })) } pub fn (h &Hashmap) get(key string) Type { @@ -333,47 +422,51 @@ pub fn (h &Hashmap) has(key string) bool { return if _ := h.hm[key] { true } else { false } } -pub fn make_hashmap(srcs ...Type) !Hashmap { - mut hm := map[string]Type{} - for src in srcs { - match src { - List { - mut list := src.list[0..] // copy - if list.len % 2 == 1 { - return error('extra param') - } - for list.len > 0 { - k, v := list[0], list[1] - hm[k.key()!] = v - list = list[2..] - } - } - Hashmap { - for k, v in src.hm { - hm[k] = v - } - } - else { - panic('make_hashmap') - } - } - } - return Hashmap{hm} -} - // -- pub struct Fn { pub: - f FnDef + f FnDef + meta Type = Nil{} } +pub fn new_fn(f FnDef) Fn { + return Fn{ + f: f + } +} + +pub fn (f &Fn) with_meta(meta Type) Type { + return Fn{ + f: f.f + meta: meta + } +} + +// -- + pub struct Closure { pub: ast Type params []string env &Env is_macro bool + meta Type = Nil{} +} + +pub fn new_closure(ast Type, params []string, env &Env) Closure { + return Closure{ + ast: ast + params: params + env: env + } +} + +pub fn (c &Closure) with_meta(meta Type) Type { + return Closure{ + ...c + meta: meta + } } fn (c Closure) str() string { @@ -383,9 +476,7 @@ fn (c Closure) str() string { pub fn (c Closure) to_macro() Closure { return Closure{ - ast: c.ast - params: c.params - env: c.env + ...c is_macro: true } } diff --git a/impls/v/step2_eval.v b/impls/v/step2_eval.v index 3f17b81866..91037f8084 100644 --- a/impls/v/step2_eval.v +++ b/impls/v/step2_eval.v @@ -32,17 +32,17 @@ fn eval_ast(ast mal.Type, env RepEnv) !mal.Type { } } mal.List { - return mal.List{ast.list.map(eval(it, env)!)} + return mal.new_list(ast.list.map(eval(it, env)!)) } mal.Vector { - return mal.Vector{ast.vec.map(eval(it, env)!)} + return mal.new_vector(ast.vec.map(eval(it, env)!)) } mal.Hashmap { mut hm := map[string]mal.Type{} for key in ast.hm.keys() { hm[key] = eval(ast.hm[key] or { panic('') }, env)! } - return mal.Hashmap{hm} + return mal.new_hashmap(hm) } else { return ast @@ -80,22 +80,22 @@ fn get_args(op string, args mal.List) !(i64, i64) { fn main() { env := RepEnv({ - '+': mal.Type(mal.Fn{fn (args mal.List) !mal.Type { + '+': mal.Type(mal.new_fn(fn (args mal.List) !mal.Type { a, b := get_args('+', args)! return mal.Int{a + b} - }}) - '-': mal.Type(mal.Fn{fn (args mal.List) !mal.Type { + })) + '-': mal.Type(mal.new_fn(fn (args mal.List) !mal.Type { a, b := get_args('-', args)! return mal.Int{a - b} - }}) - '*': mal.Type(mal.Fn{fn (args mal.List) !mal.Type { + })) + '*': mal.Type(mal.new_fn(fn (args mal.List) !mal.Type { a, b := get_args('*', args)! return mal.Int{a * b} - }}) - '/': mal.Type(mal.Fn{fn (args mal.List) !mal.Type { + })) + '/': mal.Type(mal.new_fn(fn (args mal.List) !mal.Type { a, b := get_args('/', args)! return mal.Int{a / b} - }}) + })) }) for { diff --git a/impls/v/step3_env.v b/impls/v/step3_env.v index d430f83525..cac99f1cd4 100644 --- a/impls/v/step3_env.v +++ b/impls/v/step3_env.v @@ -47,17 +47,17 @@ fn eval_ast(ast mal.Type, mut env mal.Env) !mal.Type { return env.get(ast.sym)! } mal.List { - return mal.List{ast.list.map(eval(it, mut env)!)} + return mal.new_list(ast.list.map(eval(it, mut env)!)) } mal.Vector { - return mal.Vector{ast.vec.map(eval(it, mut env)!)} + return mal.new_vector(ast.vec.map(eval(it, mut env)!)) } mal.Hashmap { mut hm := map[string]mal.Type{} for key in ast.hm.keys() { hm[key] = eval(ast.hm[key] or { panic('') }, mut env)! } - return mal.Hashmap{hm} + return mal.new_hashmap(hm) } else { return ast @@ -95,22 +95,22 @@ fn get_args(op string, args mal.List) !(i64, i64) { fn main() { mut env := mal.Env{} - env.set('+', mal.Type(mal.Fn{fn (args mal.List) !mal.Type { + env.set('+', mal.Type(mal.new_fn(fn (args mal.List) !mal.Type { a, b := get_args('+', args)! return mal.Int{a + b} - }})) - env.set('-', mal.Type(mal.Fn{fn (args mal.List) !mal.Type { + }))) + env.set('-', mal.Type(mal.new_fn(fn (args mal.List) !mal.Type { a, b := get_args('-', args)! return mal.Int{a - b} - }})) - env.set('*', mal.Type(mal.Fn{fn (args mal.List) !mal.Type { + }))) + env.set('*', mal.Type(mal.new_fn(fn (args mal.List) !mal.Type { a, b := get_args('*', args)! return mal.Int{a * b} - }})) - env.set('/', mal.Type(mal.Fn{fn (args mal.List) !mal.Type { + }))) + env.set('/', mal.Type(mal.new_fn(fn (args mal.List) !mal.Type { a, b := get_args('/', args)! return mal.Int{a / b} - }})) + }))) for { line := read_line('user> ') or { diff --git a/impls/v/step4_if_fn_do.v b/impls/v/step4_if_fn_do.v index d32e287084..792234fadb 100644 --- a/impls/v/step4_if_fn_do.v +++ b/impls/v/step4_if_fn_do.v @@ -62,7 +62,7 @@ fn eval(ast mal.Type, mut env mal.Env) !mal.Type { } return eval(body, mut new_env)! } - return mal.Fn{closure} + return mal.new_fn(closure) } else { // regular list apply res := eval_ast(ast, mut env)! as mal.List @@ -81,17 +81,19 @@ fn eval_ast(ast mal.Type, mut env mal.Env) !mal.Type { return env.get(ast.sym)! } mal.List { - return mal.List{ast.list.map(eval(it, mut env)!)} + return mal.new_list(ast.list.map(eval(it, mut env)!)) } mal.Vector { - return mal.Vector{ast.vec.map(eval(it, mut env)!)} + return mal.Vector{ + vec: ast.vec.map(eval(it, mut env)!) + } } mal.Hashmap { mut hm := map[string]mal.Type{} for key in ast.hm.keys() { hm[key] = eval(ast.hm[key] or { panic('') }, mut env)! } - return mal.Hashmap{hm} + return mal.new_hashmap(hm) } else { return ast diff --git a/impls/v/step5_tco.v b/impls/v/step5_tco.v index 36f39762fc..fe7ca6eba5 100644 --- a/impls/v/step5_tco.v +++ b/impls/v/step5_tco.v @@ -61,7 +61,7 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { return error('fn*: & has 1 arg') } } - return mal.Closure{args.nth(1), syms, env, false} + return mal.new_closure(args.nth(1), syms, env) } else { // regular list apply res := eval_ast(ast, mut env)! as mal.List @@ -90,17 +90,17 @@ fn eval_ast(ast mal.Type, mut env mal.Env) !mal.Type { return env.get(ast.sym)! } mal.List { - return mal.List{ast.list.map(eval(it, mut env)!)} + return mal.new_list(ast.list.map(eval(it, mut env)!)) } mal.Vector { - return mal.Vector{ast.vec.map(eval(it, mut env)!)} + return mal.new_vector(ast.vec.map(eval(it, mut env)!)) } mal.Hashmap { mut hm := map[string]mal.Type{} for key in ast.hm.keys() { hm[key] = eval(ast.hm[key] or { panic('') }, mut env)! } - return mal.Hashmap{hm} + return mal.new_hashmap(hm) } else { return ast diff --git a/impls/v/step6_file.v b/impls/v/step6_file.v index 37f636d5c5..eb93e4d97a 100644 --- a/impls/v/step6_file.v +++ b/impls/v/step6_file.v @@ -62,7 +62,7 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { return error('fn*: & has 1 arg') } } - return mal.Closure{args.nth(1), syms, env, false} + return mal.new_closure(args.nth(1), syms, env) } else { // regular list apply res := eval_ast(ast, mut env)! as mal.List @@ -91,17 +91,17 @@ fn eval_ast(ast mal.Type, mut env mal.Env) !mal.Type { return env.get(ast.sym)! } mal.List { - return mal.List{ast.list.map(eval(it, mut env)!)} + return mal.new_list(ast.list.map(eval(it, mut env)!)) } mal.Vector { - return mal.Vector{ast.vec.map(eval(it, mut env)!)} + return mal.new_vector(ast.vec.map(eval(it, mut env)!)) } mal.Hashmap { mut hm := map[string]mal.Type{} for key in ast.hm.keys() { hm[key] = eval(ast.hm[key] or { panic('') }, mut env)! } - return mal.Hashmap{hm} + return mal.new_hashmap(hm) } else { return ast @@ -157,7 +157,7 @@ fn main() { for i in 2 .. os.args.len { args << mal.Type(mal.String{os.args[i]}) } - env.set('*ARGV*', mal.List{args}) + env.set('*ARGV*', mal.new_list(args)) rep('(load-file "${file}")', mut env) } else { // repl diff --git a/impls/v/step7_quote.v b/impls/v/step7_quote.v index 82f293e23c..2b56c3e525 100644 --- a/impls/v/step7_quote.v +++ b/impls/v/step7_quote.v @@ -10,12 +10,12 @@ fn quasiquote_list(list []mal.Type) !mal.Type { mut res := []mal.Type{} for elt in list.reverse() { if elt is mal.List && elt.call_sym() or { '' } == 'splice-unquote' { - res = [mal.Symbol{'concat'}, elt.nth(1), mal.List{res}] + res = [mal.Symbol{'concat'}, elt.nth(1), mal.new_list(res)] } else { - res = [mal.Symbol{'cons'}, quasiquote(elt)!, mal.List{res}] + res = [mal.Symbol{'cons'}, quasiquote(elt)!, mal.new_list(res)] } } - return mal.List{res} + return mal.new_list(res) } fn quasiquote(ast mal.Type) !mal.Type { @@ -28,10 +28,10 @@ fn quasiquote(ast mal.Type) !mal.Type { } } mal.Vector { - mal.List{[mal.Symbol{'vec'}, quasiquote_list(ast.vec)!]} + mal.new_list([mal.Symbol{'vec'}, quasiquote_list(ast.vec)!]) } mal.Symbol, mal.Hashmap { - mal.List{[mal.Symbol{'quote'}, ast]} + mal.new_list([mal.Symbol{'quote'}, ast]) } else { ast @@ -95,7 +95,7 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { return error('fn*: & has 1 arg') } } - return mal.Closure{args.nth(1), syms, env, false} + return mal.new_closure(args.nth(1), syms, env) } 'quote' { mal.check_args(args, 1, 1) or { return error('quote: ${err}') } @@ -137,17 +137,17 @@ fn eval_ast(ast mal.Type, mut env mal.Env) !mal.Type { return env.get(ast.sym)! } mal.List { - return mal.List{ast.list.map(eval(it, mut env)!)} + return mal.new_list(ast.list.map(eval(it, mut env)!)) } mal.Vector { - return mal.Vector{ast.vec.map(eval(it, mut env)!)} + return mal.new_vector(ast.vec.map(eval(it, mut env)!)) } mal.Hashmap { mut hm := map[string]mal.Type{} for key in ast.hm.keys() { hm[key] = eval(ast.hm[key] or { panic('') }, mut env)! } - return mal.Hashmap{hm} + return mal.new_hashmap(hm) } else { return ast @@ -203,7 +203,7 @@ fn main() { for i in 2 .. os.args.len { args << mal.Type(mal.String{os.args[i]}) } - env.set('*ARGV*', mal.List{args}) + env.set('*ARGV*', mal.new_list(args)) rep('(load-file "${file}")', mut env) } else { // repl diff --git a/impls/v/step8_macros.v b/impls/v/step8_macros.v index 493fbd867b..2cca39b900 100644 --- a/impls/v/step8_macros.v +++ b/impls/v/step8_macros.v @@ -10,12 +10,12 @@ fn quasiquote_list(list []mal.Type) !mal.Type { mut res := []mal.Type{} for elt in list.reverse() { if elt is mal.List && elt.call_sym() or { '' } == 'splice-unquote' { - res = [mal.Symbol{'concat'}, elt.nth(1), mal.List{res}] + res = [mal.Symbol{'concat'}, elt.nth(1), mal.new_list(res)] } else { - res = [mal.Symbol{'cons'}, quasiquote(elt)!, mal.List{res}] + res = [mal.Symbol{'cons'}, quasiquote(elt)!, mal.new_list(res)] } } - return mal.List{res} + return mal.new_list(res) } fn quasiquote(ast mal.Type) !mal.Type { @@ -28,10 +28,10 @@ fn quasiquote(ast mal.Type) !mal.Type { } } mal.Vector { - mal.List{[mal.Symbol{'vec'}, quasiquote_list(ast.vec)!]} + mal.new_list([mal.Symbol{'vec'}, quasiquote_list(ast.vec)!]) } mal.Symbol, mal.Hashmap { - mal.List{[mal.Symbol{'quote'}, ast]} + mal.new_list([mal.Symbol{'quote'}, ast]) } else { ast @@ -122,7 +122,7 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { return error('fn*: & has 1 arg') } } - return mal.Closure{args.nth(1), syms, env, false} + return mal.new_closure(args.nth(1), syms, env) } 'quote' { mal.check_args(args, 1, 1) or { return error('quote: ${err}') } @@ -176,17 +176,17 @@ fn eval_ast(ast mal.Type, mut env mal.Env) !mal.Type { return env.get(ast.sym)! } mal.List { - return mal.List{ast.list.map(eval(it, mut env)!)} + return mal.new_list(ast.list.map(eval(it, mut env)!)) } mal.Vector { - return mal.Vector{ast.vec.map(eval(it, mut env)!)} + return mal.new_vector(ast.vec.map(eval(it, mut env)!)) } mal.Hashmap { mut hm := map[string]mal.Type{} for key in ast.hm.keys() { hm[key] = eval(ast.hm[key] or { panic('') }, mut env)! } - return mal.Hashmap{hm} + return mal.new_hashmap(hm) } else { return ast @@ -244,7 +244,7 @@ fn main() { for i in 2 .. os.args.len { args << mal.Type(mal.String{os.args[i]}) } - env.set('*ARGV*', mal.List{args}) + env.set('*ARGV*', mal.new_list(args)) rep('(load-file "${file}")', mut env) } else { // repl diff --git a/impls/v/step9_try.v b/impls/v/step9_try.v index 59fab94c9c..1e44e254b3 100644 --- a/impls/v/step9_try.v +++ b/impls/v/step9_try.v @@ -10,12 +10,12 @@ fn quasiquote_list(list []mal.Type) !mal.Type { mut res := []mal.Type{} for elt in list.reverse() { if elt is mal.List && elt.call_sym() or { '' } == 'splice-unquote' { - res = [mal.Symbol{'concat'}, elt.nth(1), mal.List{res}] + res = [mal.Symbol{'concat'}, elt.nth(1), mal.new_list(res)] } else { - res = [mal.Symbol{'cons'}, quasiquote(elt)!, mal.List{res}] + res = [mal.Symbol{'cons'}, quasiquote(elt)!, mal.new_list(res)] } } - return mal.List{res} + return mal.new_list(res) } fn quasiquote(ast mal.Type) !mal.Type { @@ -28,10 +28,10 @@ fn quasiquote(ast mal.Type) !mal.Type { } } mal.Vector { - mal.List{[mal.Symbol{'vec'}, quasiquote_list(ast.vec)!]} + mal.new_list([mal.Symbol{'vec'}, quasiquote_list(ast.vec)!]) } mal.Symbol, mal.Hashmap { - mal.List{[mal.Symbol{'quote'}, ast]} + mal.new_list([mal.Symbol{'quote'}, ast]) } else { ast @@ -131,7 +131,7 @@ fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { return error('fn*: & has 1 arg') } } - return mal.Closure{args.nth(1), syms, env, false} + return mal.new_closure(args.nth(1), syms, env) } 'quote' { mal.check_args(args, 1, 1) or { return error('quote: ${err}') } @@ -198,17 +198,17 @@ fn eval_ast(ast mal.Type, mut env mal.Env) !mal.Type { return env.get(ast.sym)! } mal.List { - return mal.List{ast.list.map(eval(it, mut env)!)} + return mal.new_list(ast.list.map(eval(it, mut env)!)) } mal.Vector { - return mal.Vector{ast.vec.map(eval(it, mut env)!)} + return mal.new_vector(ast.vec.map(eval(it, mut env)!)) } mal.Hashmap { mut hm := map[string]mal.Type{} for key in ast.hm.keys() { hm[key] = eval(ast.hm[key] or { panic('') }, mut env)! } - return mal.Hashmap{hm} + return mal.new_hashmap(hm) } else { return ast @@ -271,7 +271,7 @@ fn main() { for i in 2 .. os.args.len { args << mal.Type(mal.String{os.args[i]}) } - env.set('*ARGV*', mal.List{args}) + env.set('*ARGV*', mal.new_list(args)) rep('(load-file "${file}")', mut env) } else { // repl From 7c0d2e031f8c40d00d11a8aa4cca7fc808a053ab Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Wed, 15 Feb 2023 21:10:26 +0000 Subject: [PATCH 21/30] v: step A --- impls/v/mal/core.v | 67 +++++++--- impls/v/mal/env.v | 21 ++-- impls/v/mal/types.v | 52 +++++++- impls/v/stepA_mal.v | 295 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 402 insertions(+), 33 deletions(-) create mode 100644 impls/v/stepA_mal.v diff --git a/impls/v/mal/core.v b/impls/v/mal/core.v index d991220c16..69dd7c3b5a 100644 --- a/impls/v/mal/core.v +++ b/impls/v/mal/core.v @@ -1,6 +1,7 @@ module mal import os +import time import readline { read_line } type EvalFn = fn (_ Type, mut _ Env) !Type @@ -234,27 +235,61 @@ pub fn add_core(mut env Env, eval_fn EvalFn) { } }) add_fn(mut env, 'time-ms', -1, -1, fn (args List) !Type { - return error('not implemented') + return Int{time.ticks()} }) add_fn(mut env, 'meta', 1, 1, fn (args List) !Type { return args.nth(0).get_meta() }) - add_fn(mut env, 'with-meta', -1, -1, fn (args List) !Type { - return error('not implemented') - }) - add_fn(mut env, 'fn?', -1, -1, fn (args List) !Type { - return error('not implemented') - }) - add_fn(mut env, 'string?', -1, -1, fn (args List) !Type { - return error('not implemented') - }) - add_fn(mut env, 'number?', -1, -1, fn (args List) !Type { - return error('not implemented') + add_fn(mut env, 'with-meta', 2, 2, fn (args List) !Type { + return args.nth(0).set_meta(args.nth(1))! + }) + add_fn(mut env, 'fn?', 1, 1, fn (args List) !Type { + f := args.nth(0) + return make_bool(match f { + Fn { true } + Closure { !f.is_macro } + else { false } + }) + }) + add_fn(mut env, 'string?', 1, 1, fn (args List) !Type { + return make_bool(args.nth(0) is String) + }) + add_fn(mut env, 'number?', 1, 1, fn (args List) !Type { + return make_bool(args.nth(0) in [Int, Float]) + }) + add_fn(mut env, 'seq', 1, 1, fn (args List) !Type { + return match args.nth(0) { + List, Vector { + if args.nth(0).sequence()!.len == 0 { + Nil{} + } else { + new_list(args.nth(0).sequence()!) + } + } + String { + if args.nth(0).str_()! == '' { + Nil{} + } else { + new_list(args.nth(0).str_()!.split('').map(Type(String{it}))) + } + } + else { + Nil{} + } + } }) - add_fn(mut env, 'seq', -1, -1, fn (args List) !Type { - return error('not implemented') + add_fn(mut env, 'conj', 2, -1, fn (args List) !Type { + t := args.nth(0) + return match t { + // https://github.com/vlang/v/issues/17333 + // List, Vector { t.conj(args.from(1)) } + List { t.conj(args.from(1)) } + Vector { t.conj(args.from(1)) } + else { error('vector/list expected') } + } }) - add_fn(mut env, 'conj', -1, -1, fn (args List) !Type { - return error('not implemented') + add_fn(mut env, 'macro?', 1, 1, fn (args List) !Type { + f := args.nth(0) + return make_bool(if f is Closure { f.is_macro } else { false }) }) } diff --git a/impls/v/mal/env.v b/impls/v/mal/env.v index 9031b33dbb..52d7f8ad08 100644 --- a/impls/v/mal/env.v +++ b/impls/v/mal/env.v @@ -26,33 +26,28 @@ pub fn (mut e Env) bind(syms []string, args List) { } pub fn (mut e Env) set(sym string, val Type) Type { - //$if env ? { - // println('ENV: setting [${sym}] in 0x${voidptr(&e)} (outer 0x${voidptr(e.outer)})') - //} + $if env ? { + println('SET(${e.level()}): ${sym} ${pr_str(val, true)}') + } e.data[sym] = val return val } pub fn (e Env) find(sym string) ?Type { - $if env ? { - println('ENV: looking for [${sym}] in\n${e.data}') - // println('ENV: finding [${sym}] in 0x${voidptr(&e)} (outer 0x${voidptr(e.outer)}):') - // println("${e.data}") - } if res := e.data[sym] { $if env ? { - println('...found') + println('GET(${e.level()}): ${sym} ...found') } return res } if e.outer != unsafe { nil } { $if env ? { - println('...checking outer') + println('GET(${e.level()}): ${sym} ...checking outer') } return e.outer.find(sym) } $if env ? { - println('...not found') + println('GET(${e.level()}): ${sym} ...not found') } return none } @@ -60,3 +55,7 @@ pub fn (e Env) find(sym string) ?Type { pub fn (e Env) get(sym string) !Type { return e.find(sym) or { error("'${sym}' not found") } } + +pub fn (e Env) level() int { + return if e.outer == unsafe { nil } { 1 } else { 1 + e.outer.level() } +} diff --git a/impls/v/mal/types.v b/impls/v/mal/types.v index 26b10de9ca..33002e1f25 100644 --- a/impls/v/mal/types.v +++ b/impls/v/mal/types.v @@ -190,7 +190,21 @@ pub fn (t Type) get_meta() !Type { pub fn (t Type) set_meta(meta Type) !Type { return match t { - Fn, Closure, List, Vector, Hashmap { + // https://github.com/vlang/v/issues/17333 + // Fn, Closure, List, Vector, Hashmap { + Fn { + t.with_meta(meta) + } + Closure { + t.with_meta(meta) + } + List { + t.with_meta(meta) + } + Vector { + t.with_meta(meta) + } + Hashmap { t.with_meta(meta) } else { @@ -306,10 +320,10 @@ pub fn new_list(list []Type) List { } pub fn (l &List) with_meta(meta Type) Type { - return List{ + return Type(List{ list: l.list meta: meta - } + }) } pub fn (l &List) first() !&Type { @@ -336,6 +350,15 @@ pub fn (l &List) nth(n int) &Type { return if n < l.list.len { &l.list[n] } else { Nil{} } } +pub fn (l &List) conj(list List) Type { + mut list_ := l.list[0..] + list_.prepend(list.list.reverse()) + return Type(List{ + ...l + list: list_ + }) +} + // -- pub struct Vector { @@ -357,6 +380,15 @@ pub fn (v &Vector) with_meta(meta Type) Type { } } +pub fn (v &Vector) conj(list List) Type { + mut vec := v.vec[0..] + vec.insert(v.vec.len, list.list) + return Vector{ + ...v + vec: vec + } +} + // -- pub struct Hashmap { @@ -424,6 +456,7 @@ pub fn (h &Hashmap) has(key string) bool { // -- +[heap] pub struct Fn { pub: f FnDef @@ -437,10 +470,16 @@ pub fn new_fn(f FnDef) Fn { } pub fn (f &Fn) with_meta(meta Type) Type { - return Fn{ - f: f.f + ff := Fn{ + ...f meta: meta } + return ff +} + +fn (f &Fn) str() string { + meta := f.meta.str().replace('\n', '\n ') + return 'mal.Fn{\n \n meta: ${meta}\n}' } // -- @@ -471,7 +510,8 @@ pub fn (c &Closure) with_meta(meta Type) Type { fn (c Closure) str() string { disp := if c.is_macro { 'macro' } else { 'closure' } - return 'mal.Closure{\n <${disp}>\n}' + meta := c.meta.str().replace('\n', '\n ') + return 'mal.Closure{\n <${disp}>\n meta: ${meta}\n}' } pub fn (c Closure) to_macro() Closure { diff --git a/impls/v/stepA_mal.v b/impls/v/stepA_mal.v new file mode 100644 index 0000000000..888c9c3c1d --- /dev/null +++ b/impls/v/stepA_mal.v @@ -0,0 +1,295 @@ +import mal +import os +import readline { read_line } + +fn rep_read(input string) !mal.Type { + return mal.read_str(input)! +} + +fn quasiquote_list(list []mal.Type) !mal.Type { + mut res := []mal.Type{} + for elt in list.reverse() { + if elt is mal.List && elt.call_sym() or { '' } == 'splice-unquote' { + res = [mal.Symbol{'concat'}, elt.nth(1), mal.new_list(res)] + } else { + res = [mal.Symbol{'cons'}, quasiquote(elt)!, mal.new_list(res)] + } + } + return mal.new_list(res) +} + +fn quasiquote(ast mal.Type) !mal.Type { + return match ast { + mal.List { + if ast.nth(0).eq(mal.Symbol{'unquote'}) { + *ast.nth(1) + } else { + quasiquote_list(ast.list)! + } + } + mal.Vector { + mal.new_list([mal.Symbol{'vec'}, quasiquote_list(ast.vec)!]) + } + mal.Symbol, mal.Hashmap { + mal.new_list([mal.Symbol{'quote'}, ast]) + } + else { + ast + } + } +} + +fn is_macro_call(ast mal.Type, env mal.Env) ?mal.Closure { + if sym := ast.call_sym() { + if cls := env.find(sym) { + if cls is mal.Closure { + if cls.is_macro { + return cls + } + } + } + } + return none +} + +fn macroexpand(ast_ mal.Type, env mal.Env) !mal.Type { + mut ast := unsafe { &ast_ } + for { + cls := is_macro_call(ast, env) or { break } + res := mal.apply(cls, eval, (ast as mal.List).rest())! + ast = unsafe { &res } + } + return *ast +} + +fn eval(ast_ mal.Type, mut env_ mal.Env) !mal.Type { + mut ast := unsafe { &ast_ } + mut env := unsafe { &env_ } + for true { + $if eval ? { + println('EVAL: ${mal.pr_str(ast, true)}') + } + if ast !is mal.List { + return eval_ast(ast, mut env)! + } else if (ast as mal.List).len() == 0 { + return *ast + } + + // macro expansion + expanded := macroexpand(ast, env)! + ast = unsafe { &expanded } + if ast !is mal.List { + return eval_ast(ast, mut env)! + } else if (ast as mal.List).len() == 0 { + return *ast + } + + ast0 := (ast as mal.List).first()! + args := (ast as mal.List).rest() + match ast0.sym() or { '' } { + 'def!' { + mal.check_args(args, 2, 2) or { return error('def!: ${err}') } + sym := args.nth(0).sym() or { return error('def!: ${err}') } + return env.set(sym, eval(args.nth(1), mut env)!) + } + 'let*' { + mal.check_args(args, 2, 2) or { return error('let*: ${err}') } + env = unsafe { mal.mk_env(env) } // TCO + tmp := args.nth(0).sequence() or { return error('let*: ${err}') } + mut pairs := tmp[0..] // copy + if pairs.len % 2 == 1 { + return error('let*: extra binding param') + } + for pairs.len > 0 { + sym := pairs[0].sym() or { return error('let*: ${err}') } + env.set(sym, eval(pairs[1], mut env)!) + pairs = pairs[2..] + } + ast = unsafe { args.nth(1) } // TCO + } + 'do' { + if args.len() > 1 { + args.list[0..args.list.len - 1].map(eval(it, mut env)!) + } else if args.len() == 0 { + return mal.Nil{} + } + ast = unsafe { args.last()! } // TCO + } + 'if' { + mal.check_args(args, 2, 3) or { return error('if: ${err}') } + if eval(args.nth(0), mut env)!.truthy() { + ast = unsafe { args.nth(1) } // TCO + } else if args.len() == 3 { + ast = unsafe { args.nth(2) } // TCO + } else { + return mal.Nil{} + } + } + 'fn*' { + mal.check_args(args, 2, 2) or { return error('fn*: ${err}') } + binds := args.nth(0).sequence() or { return error('fn*: ${err}') } + syms := binds.map(it.sym() or { return error('fn*: ${err}') }) + for i, sym in syms { + if sym == '&' && syms.len != i + 2 { + return error('fn*: & has 1 arg') + } + } + return mal.new_closure(args.nth(1), syms, env) + } + 'quote' { + mal.check_args(args, 1, 1) or { return error('quote: ${err}') } + return *args.nth(0) + } + 'quasiquoteexpand' { + mal.check_args(args, 1, 1) or { return error('quasiquoteexpand: ${err}') } + return quasiquote(args.nth(0)) or { return error('quasiquoteexpand: ${err}') } + } + 'quasiquote' { + mal.check_args(args, 1, 1) or { return error('quasiquote: ${err}') } + res := quasiquote(args.nth(0)) or { return error('quasiquote: ${err}') } + ast = unsafe { &res } // TCO + } + 'defmacro!' { + mal.check_args(args, 2, 2) or { return error('defmacro!: ${err}') } + sym := args.nth(0).sym() or { return error('defmacro!: ${err}') } + cls := eval(args.nth(1), mut env)!.cls() or { return error('defmacro!: ${err}') } + return env.set(sym, cls.to_macro()) + } + 'macroexpand' { + mal.check_args(args, 1, 1) or { return error('macroexpand: ${err}') } + return macroexpand(args.nth(0), env) + } + 'try*' { + mal.check_args(args, 1, 2) or { return error('try*: ${err}') } + if res := eval(args.nth(0), mut env) { + return res + } else { + if args.nth(1).call_sym() or { '' } == 'catch*' { + cargs := (args.nth(1) as mal.List).rest() + mal.check_args(cargs, 2, 2) or { return error('catch* ${err}') } + sym := cargs.nth(0).sym() or { return error('catch*: ${err}') } + typ := if err is mal.Exception { err.typ } else { mal.String{err.msg()} } + ast = cargs.nth(1) // TCO + env = unsafe { mal.mk_env(env) } + env.set(sym, typ) + } else { + return err + } + } + } + else { // regular list apply w/ TCO + res := eval_ast(ast, mut env)! as mal.List + list0 := res.list[0] + if list0 is mal.Fn { + return list0.f(res.rest())! + } else if list0 is mal.Closure { + ast = &list0.ast // TCO + env = mal.mk_env(list0.env) + env.bind(list0.params, res.rest()) + } else { + return error('function expected') + } + } + } + } + panic('unreachable code') +} + +fn eval_ast(ast mal.Type, mut env mal.Env) !mal.Type { + match ast { + mal.Symbol { + return env.get(ast.sym)! + } + mal.List { + return mal.new_list(ast.list.map(eval(it, mut env)!)) + } + mal.Vector { + return mal.new_vector(ast.vec.map(eval(it, mut env)!)) + } + mal.Hashmap { + mut hm := map[string]mal.Type{} + for key in ast.hm.keys() { + hm[key] = eval(ast.hm[key]!, mut env)! + } + return mal.new_hashmap(hm) + } + else { + return ast + } + } +} + +fn rep_print(ast mal.Type) string { + return mal.pr_str(ast, true) +} + +fn rep(line string, mut env mal.Env) string { + ast := rep_read(line) or { + if err.msg() != 'no form' { + println('Error: ${err}') + } + return '' + } + $if ast ? { + println('AST:\n${ast}') + } + if res := eval(ast, mut env) { + return rep_print(res) + } else { + if err is mal.Exception { + println('Exception: ${mal.pr_str(err.typ, true)}') + } else { + println('Error: ${err}') + } + return '' + } +} + +fn main() { + // outer-most env + mut env := mal.Env{} + + // add to env + mut envp := &env // workaround no closure references in V + mal.add_fn(mut env, 'eval', 1, 1, fn [mut envp] (args mal.List) !mal.Type { + return eval(args.nth(0), mut envp)! + }) + env.set('*ARGV*', mal.new_list([])) + env.set('*host-language*', mal.Symbol{'v'}) + + // core env + mal.add_core(mut env, eval) + + // mal defined env + rep('(def! not (fn* (a) (if a false true)))', mut env) + rep('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))', mut + env) + rep('(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list \'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons \'cond (rest (rest xs)))))))', mut + env) + + if os.args.len > 1 { + // run script + file := os.args[1] + .replace('\\', '\\\\') + .replace('"', '\\"') + mut args := []mal.Type{} + for i in 2 .. os.args.len { + args << mal.Type(mal.String{os.args[i]}) + } + env.set('*ARGV*', mal.new_list(args)) + rep('(load-file "${file}")', mut env) + } else { + // repl + rep('(println (str "Mal [" *host-language* "]"))', mut env) + for { + line := read_line('user> ') or { + println('') // newline + break + } + out := rep(line, mut env) + if out.len > 0 { + println(out) + } + } + } +} From b751fcbf8b406542573e76d06971a34f18821cc1 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Wed, 15 Feb 2023 21:18:28 +0000 Subject: [PATCH 22/30] v: updated README --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 3fa1c13df2..306c306667 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,7 @@ FAQ](docs/FAQ.md) where I attempt to answer some common questions. | [TypeScript](#typescript) | [Masahiro Wakame](https://github.com/vvakame) | | [Vala](#vala) | [Simon Tatham](https://github.com/sgtatham) | | [VHDL](#vhdl) | [Dov Murik](https://github.com/dubek) | +| [V](#v) | [Tim Marston](http://ed.am/) | | [Vimscript](#vimscript) | [Dov Murik](https://github.com/dubek) | | [Visual Basic.NET](#visual-basicnet) | [Joel Martin](https://github.com/kanaka) | | [WebAssembly](#webassembly-wasm) (wasm) | [Joel Martin](https://github.com/kanaka) | @@ -1175,6 +1176,16 @@ make node ./stepX_YYY.js ``` +### V + +The V implementatin of mal has been tested to work with v 0.3.3 and can +be run a follows: + +``` +cd impls/v +v run stepX_YYY.v +``` + ### Vala The Vala implementation of mal has been tested with the Vala 0.40.8 From bbabea09974de657f497e62bfa23309615274569 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Wed, 15 Feb 2023 21:57:41 +0000 Subject: [PATCH 23/30] v: add to Makefie.impls alphabetically --- Makefile.impls | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.impls b/Makefile.impls index 1bd1b3f58a..3f0a919c63 100644 --- a/Makefile.impls +++ b/Makefile.impls @@ -41,7 +41,7 @@ IMPLS = ada ada.2 awk bash basic bbc-basic c c.2 chuck clojure coffee \ matlab miniMAL nasm nim objc objpascal ocaml perl perl6 php picolisp pike \ plpgsql plsql powershell prolog ps purs python python.2 r racket rexx \ rpython ruby ruby.2 rust scala scheme skew sml swift swift3 swift4 swift5 \ - tcl ts vala v vb vhdl vimscript wasm wren yorick xslt zig + tcl ts v vala vb vhdl vimscript wasm wren yorick xslt zig step5_EXCLUDES += bash # never completes at 10,000 step5_EXCLUDES += basic # too slow, and limited to ints of 2^16 @@ -190,8 +190,8 @@ swift4_STEP_TO_PROG = impls/swift4/$($(1)) swift5_STEP_TO_PROG = impls/swift5/$($(1)) tcl_STEP_TO_PROG = impls/tcl/$($(1)).tcl ts_STEP_TO_PROG = impls/ts/$($(1)).js -vala_STEP_TO_PROG = impls/vala/$($(1)) v_STEP_TO_PROG = impls/v/$($(1)).v +vala_STEP_TO_PROG = impls/vala/$($(1)) vb_STEP_TO_PROG = impls/vb/$($(1)).exe vhdl_STEP_TO_PROG = impls/vhdl/$($(1)) vimscript_STEP_TO_PROG = impls/vimscript/$($(1)).vim From 316097ded2258ac6fe370e175f1cf0194afefa97 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Wed, 15 Feb 2023 22:31:02 +0000 Subject: [PATCH 24/30] v: add Dockerfile --- impls/v/Dockerfile | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 impls/v/Dockerfile diff --git a/impls/v/Dockerfile b/impls/v/Dockerfile new file mode 100644 index 0000000000..4ff29a80f7 --- /dev/null +++ b/impls/v/Dockerfile @@ -0,0 +1,30 @@ +FROM ubuntu:jammy +MAINTAINER Joel Martin + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python3 + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +# Install other tools +RUN apt-get -y install git gcc + +# Install v +RUN git clone https://github.com/vlang/v \ + && make -C v \ + && ln -s /mal/v/v /usr/bin/v From f93376126f668fd916528a33e02d6a2bc0d6347f Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Thu, 16 Feb 2023 19:07:06 +0000 Subject: [PATCH 25/30] v: upstream fixed some bugs --- impls/v/mal/core.v | 5 +---- impls/v/mal/types.v | 16 +--------------- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/impls/v/mal/core.v b/impls/v/mal/core.v index 69dd7c3b5a..f7ed60e790 100644 --- a/impls/v/mal/core.v +++ b/impls/v/mal/core.v @@ -281,10 +281,7 @@ pub fn add_core(mut env Env, eval_fn EvalFn) { add_fn(mut env, 'conj', 2, -1, fn (args List) !Type { t := args.nth(0) return match t { - // https://github.com/vlang/v/issues/17333 - // List, Vector { t.conj(args.from(1)) } - List { t.conj(args.from(1)) } - Vector { t.conj(args.from(1)) } + List, Vector { t.conj(args.from(1)) } else { error('vector/list expected') } } }) diff --git a/impls/v/mal/types.v b/impls/v/mal/types.v index 33002e1f25..91f78bb3a6 100644 --- a/impls/v/mal/types.v +++ b/impls/v/mal/types.v @@ -190,21 +190,7 @@ pub fn (t Type) get_meta() !Type { pub fn (t Type) set_meta(meta Type) !Type { return match t { - // https://github.com/vlang/v/issues/17333 - // Fn, Closure, List, Vector, Hashmap { - Fn { - t.with_meta(meta) - } - Closure { - t.with_meta(meta) - } - List { - t.with_meta(meta) - } - Vector { - t.with_meta(meta) - } - Hashmap { + Fn, Closure, List, Vector, Hashmap { t.with_meta(meta) } else { From 3e64e2762e6d353e7a2e221787676d399d52d3db Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Thu, 16 Feb 2023 19:07:22 +0000 Subject: [PATCH 26/30] v: exception consistency with other mals --- impls/v/step9_try.v | 4 ++-- impls/v/stepA_mal.v | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/impls/v/step9_try.v b/impls/v/step9_try.v index 1e44e254b3..6a51998e73 100644 --- a/impls/v/step9_try.v +++ b/impls/v/step9_try.v @@ -234,9 +234,9 @@ fn rep(line string, mut env mal.Env) string { return rep_print(res) } else { if err is mal.Exception { - println('Exception: ${mal.pr_str(err.typ, true)}') + println('Uncaught exception: ${mal.pr_str(err.typ, true)}') } else { - println('Error: ${err}') + println('Uncaught exception: ${err}') } return '' } diff --git a/impls/v/stepA_mal.v b/impls/v/stepA_mal.v index 888c9c3c1d..a2238649b6 100644 --- a/impls/v/stepA_mal.v +++ b/impls/v/stepA_mal.v @@ -237,9 +237,9 @@ fn rep(line string, mut env mal.Env) string { return rep_print(res) } else { if err is mal.Exception { - println('Exception: ${mal.pr_str(err.typ, true)}') + println('Uncaught exception: ${mal.pr_str(err.typ, true)}') } else { - println('Error: ${err}') + println('Uncaught exception: ${err}') } return '' } From 197439abd2d06ac3b32bb7a54240d6bde32ad36f Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Thu, 16 Feb 2023 19:54:41 +0000 Subject: [PATCH 27/30] v: fix bug checking properly terminated strings; self hosts now! --- impls/v/mal/reader.v | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/impls/v/mal/reader.v b/impls/v/mal/reader.v index 71ffe88f3e..c919314eef 100644 --- a/impls/v/mal/reader.v +++ b/impls/v/mal/reader.v @@ -3,7 +3,7 @@ module mal import regex const ( - re_token = '^[\\s,]*((?:~@)|(?:[\\[\\]{}()\'\`~^@])|("(?:(?:\\\\.)|[^\\\\"])*"?)|(?:;[^\n]*)|(?:[^\\s\\[\\\]{}(\'"`,;)]*))' + re_token = '^[\\s,]*((?:~@)|(?:[\\[\\]{}()\'\`~^@])|(?:"(?:(?:\\\\.)|[^\\\\"])*("?))|(?:;[^\n]*)|(?:[^\\s\\[\\\]{}(\'"`,;)]*))' re_int = '^-?[0-9]+$' re_float = '^-?[0-9]*\\.[0-9]+$' ) @@ -103,7 +103,7 @@ fn (mut r Reader) read_atom() !Type { pub fn read_str(input string) !Type { mut reader := Reader{ - toks: tokenise(input)! + toks: tokenise(input.trim_right("\r\n"))! re_int: regex.regex_opt(mal.re_int) or { panic('regex_opt()') } re_float: regex.regex_opt(mal.re_float) or { panic('regex_opt()') } } @@ -116,7 +116,7 @@ fn tokenise(input string) ![]Token { mut input_ := input for { $if token ? { - println('${input_}') + println('remaining: ${input_}') } start, end := re.match_string(input_) if start < 0 { @@ -127,10 +127,8 @@ fn tokenise(input string) ![]Token { } if re.groups[1] > re.groups[0] { tok := input_[re.groups[0]..re.groups[1]] - if tok[0] == `"` { - if tok.len == 1 || tok[tok.len - 1] != `"` { - return error('unbalanced quotes') - } + if tok[0] == `"` && re.groups[2] < 0 { + return error('unbalanced quotes') } ret << tok } From f2f5127d3a75a87b74f33bedd72d894438e301e4 Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Thu, 16 Feb 2023 21:21:00 +0000 Subject: [PATCH 28/30] v: Dockerfile fix --- impls/v/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/impls/v/Dockerfile b/impls/v/Dockerfile index 4ff29a80f7..c91f8a23df 100644 --- a/impls/v/Dockerfile +++ b/impls/v/Dockerfile @@ -25,6 +25,6 @@ WORKDIR /mal RUN apt-get -y install git gcc # Install v -RUN git clone https://github.com/vlang/v \ - && make -C v \ - && ln -s /mal/v/v /usr/bin/v +RUN git clone https://github.com/vlang/v /v +RUN make -C /v && ln -s /v/v /usr/bin/v +RUN mkdir /.vmodules && chmod 777 /.vmodules From f4307f0356d1c6bd8bdfdbb40eaff2797c983cbd Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Sun, 19 Feb 2023 12:23:55 +0000 Subject: [PATCH 29/30] v: switch to gnu readline, fixup Dockerfile --- impls/v/Dockerfile | 20 ++++++++++++++++---- impls/v/mal/reader.v | 2 +- impls/v/stepA_mal.v | 17 ++++++++++++----- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/impls/v/Dockerfile b/impls/v/Dockerfile index c91f8a23df..2ce9b4c6cd 100644 --- a/impls/v/Dockerfile +++ b/impls/v/Dockerfile @@ -21,10 +21,22 @@ WORKDIR /mal # Specific implementation requirements ########################################################## -# Install other tools +# fix ubuntu +RUN ln -sf bash /bin/sh + +# install other tools RUN apt-get -y install git gcc -# Install v +# install v RUN git clone https://github.com/vlang/v /v -RUN make -C /v && ln -s /v/v /usr/bin/v -RUN mkdir /.vmodules && chmod 777 /.vmodules +RUN cd /v && make && ./v symlink + +# install v modules +RUN v -v install edam.greadline +RUN mv /root/.vmodules / + +# fix permissions +RUN find /v -type d -print0 | xargs -0 -- chmod a+x +RUN chmod -R a+r /v +RUN find /.vmodules -type d -print0 | xargs -0 -- chmod a+x +RUN chmod -R a+r /.vmodules diff --git a/impls/v/mal/reader.v b/impls/v/mal/reader.v index c919314eef..b14b849a8e 100644 --- a/impls/v/mal/reader.v +++ b/impls/v/mal/reader.v @@ -103,7 +103,7 @@ fn (mut r Reader) read_atom() !Type { pub fn read_str(input string) !Type { mut reader := Reader{ - toks: tokenise(input.trim_right("\r\n"))! + toks: tokenise(input.trim_right('\r\n'))! re_int: regex.regex_opt(mal.re_int) or { panic('regex_opt()') } re_float: regex.regex_opt(mal.re_float) or { panic('regex_opt()') } } diff --git a/impls/v/stepA_mal.v b/impls/v/stepA_mal.v index a2238649b6..64a2eaa78d 100644 --- a/impls/v/stepA_mal.v +++ b/impls/v/stepA_mal.v @@ -1,6 +1,11 @@ import mal import os -import readline { read_line } +import edam.greadline + +const ( + history_file = '~/.mal-history' + history_limit = 10000 +) fn rep_read(input string) !mal.Type { return mal.read_str(input)! @@ -281,11 +286,13 @@ fn main() { } else { // repl rep('(println (str "Mal [" *host-language* "]"))', mut env) + greadline.history_file_read(os.expand_tilde_to_home(history_file))! + greadline.set_history_file_limit(history_limit)! + defer { + greadline.history_file_write() or {} + } for { - line := read_line('user> ') or { - println('') // newline - break - } + line := greadline.readline('user> ') or { break } out := rep(line, mut env) if out.len > 0 { println(out) From 749ce11cb7da699c94fc2d15a8a711b525630f2b Mon Sep 17 00:00:00 2001 From: Tim Marston Date: Sun, 19 Feb 2023 12:24:17 +0000 Subject: [PATCH 30/30] v: add to IMPLS.yml --- IMPLS.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/IMPLS.yml b/IMPLS.yml index f89b099b56..2a990c37e9 100644 --- a/IMPLS.yml +++ b/IMPLS.yml @@ -98,6 +98,7 @@ IMPL: - {IMPL: sml, sml_MODE: mosml} - {IMPL: tcl} - {IMPL: ts} + - {IMPL: v} - {IMPL: vala} - {IMPL: vb} - {IMPL: vhdl, NO_SELF_HOST_PERF: 1} # perf timeout