all: make a ton of features work

This commit is contained in:
2018-05-14 20:59:24 +02:00
parent 8cb7c37096
commit e3334b9ef0
6 changed files with 919 additions and 514 deletions

View File

@@ -7,61 +7,12 @@
## TODOS ## TODOS
- Destructuring assignment - Destructuring assignment
- Defining functions
- Anonymous functions
- Datastructures as functions
- Characters
- `let`
- `with`
- `pr`
- `prn`
- `if`
- `do`
- `when`
- `and`
- `no`
- `is`
- `in`
- `case`
- `for`
- `each`
- `while`
- `repeat`
- `:`
- `~`
- `keep`
- `rem`
- `all`
- `some`
- `pos`
- `trues`
- `table`
- `listtab`
- `obj`
- `keys`
- `vals`
- `maptable` - `maptable`
- `alref`
- `string`
- `tostring`
- `type`
- `coerce` - `coerce`
- `pop`
- `push`
- `++`
- `--`
- `zap`
- `sort`
- `insort`
- `compare`
- `o` - `o`
- `.`
- `apply`
- `len` - `len`
- `mac` - `mac`
- `\`` - `\``
- `defop`
- `asv`
- `w/link` - `w/link`
- `aform` - `aform`
- ... - ...

View File

@@ -1,44 +1,155 @@
package ast package ast
import ( import (
"fmt" "fmt"
"strings" "strings"
) )
type AST struct { type AST struct {
Tag Tag Tag Tag
Val interface{} Val interface{}
} }
type Tag int8 type Tag int8
const ( const (
Symbol Tag = iota Symbol Tag = iota
List List
String String
Num Num
Bool Bool
Fn
Char
Table
Prim
Quoted
) )
func (tag Tag) String() string { func (tag Tag) String() string {
names := []string{ names := []string{
"Symbol", "Symbol",
"List", "List",
"String", "String",
"Num", "Num",
"Bool", "Bool",
} "Fn",
"Char",
"Table",
"Prim",
"Quoted",
}
return names[tag] return names[tag]
} }
func (ast AST) Pretty() string { func (ast *AST) Type() *AST {
if ast.Tag == List { names := []string{
var agg []string "sym",
for _, elem := range(ast.Val.([]AST)) { "cons",
agg = append(agg, elem.Pretty()) "string",
} "num",
return "(" + strings.Join(agg, " ") + ")" "bool",
"fn",
"char",
"table",
"fn",
"cons",
}
if ast.Tag == Quoted {
return ast.Val.(*AST).Type()
} }
return fmt.Sprintf("%v", ast.Val)
name := names[ast.Tag]
if ast.Tag == Num {
val := ast.Val.(float64)
if val == float64(int64(val)) {
name = "int"
}
}
res := AST{Symbol, name}
return &res
}
func (ast *AST) Pretty() string {
if ast.Tag == List {
var agg []string
for _, elem := range ast.Val.([]*AST) {
agg = append(agg, elem.Pretty())
}
return "(" + strings.Join(agg, " ") + ")"
} else if ast.Tag == Quoted {
return "'" + ast.Val.(*AST).Pretty()
} else if ast.Tag == Fn {
val := ast.Val.(Func)
var agg []string
for _, elem := range val.Params {
agg = append(agg, elem)
}
opt := ""
if val.HasOpt() {
opt = " . " + *val.Opt
}
body := val.Body.Pretty()
return "(fn (" + strings.Join(agg, " ") + opt + ") " + body + ")"
} else if ast.Tag == Char {
return "#" + string(ast.Val.(rune))
} else if ast.Tag == Table {
var agg []string
for k, v := range ast.Val.(map[*AST]*AST) {
agg = append(agg, "("+k.Pretty()+" . "+v.Pretty()+")")
}
return "#hash(" + strings.Join(agg, " ") + ")"
} else if ast.Tag == Prim {
return "#<procedure: " + ast.Val.(Primitive).Name + ">"
}
return fmt.Sprintf("%v", ast.Val)
}
type Env struct {
parent *Env
Values map[string]*AST
}
type PrimFn func([]*AST) (*AST, error)
type Primitive struct {
Name string
Fn PrimFn
}
func newEnv(parent *Env) Env {
return Env{parent, make(map[string]*AST)}
}
func ParentEnv() Env {
return newEnv(nil)
}
func (e *Env) Lookup(elem string) (*AST, error) {
res, ok := e.Values[elem]
if !ok {
if e.parent == nil {
return nil, fmt.Errorf("Symbol not found: %s", elem)
}
return e.parent.Lookup(elem)
}
return res, nil
}
type Func struct {
Params []string
Opt *string
Body *AST
Env Env
}
func (f *Func) HasOpt() bool {
return f.Opt != nil
} }

View File

@@ -1,308 +1,597 @@
package eval package eval
// TODO: a lot of code duplication that can be solved by a primitive registry
// TODO: a lot of code duplication that can be solved by a type checking helper // TODO: a lot of code duplication that can be solved by a type checking helper
import ( import (
"fmt" "fmt"
"strings"
"github.com/hellerve/argos/ast" "github.com/hellerve/argos/ast"
) )
var trueVal = ast.AST{ast.Bool, true} var trueVal = ast.AST{ast.Bool, true}
var falseVal = ast.AST{ast.Bool, false} var falseVal = ast.AST{ast.Bool, false}
var nilVal = ast.AST{ast.List, []ast.AST{}} var nilVal = ast.AST{ast.List, []*ast.AST{}}
type env struct { func RootEnv() ast.Env {
parent *env e := ast.ParentEnv()
values map[string]*ast.AST
prims := map[string]ast.PrimFn{
"cons": evalCons,
"car": evalCar,
"cdr": evalCdr,
"null?": evalNull,
"pr": evalPr,
"table": evalTable,
"type": evalType,
}
for k, v := range prims {
prim := ast.AST{ast.Prim, ast.Primitive{k, v}}
e.Values[k] = &prim
}
return e
} }
func newEnv(parent *env) env { func checkArity(input []*ast.AST, arity int, name string) error {
return env{parent, make(map[string]*ast.AST)} ilen := len(input)
if ilen != arity {
return fmt.Errorf("Argument count to '%s' must be %d, was %d", name, arity, ilen)
}
return nil
} }
func ParentEnv() env { func evalDef(input []*ast.AST, e ast.Env) (*ast.AST, error) {
return newEnv(nil) err := checkArity(input, 2, "=")
if err != nil {
return nil, err
}
variable := input[0]
if variable.Tag != ast.Symbol {
// TODO destructuring assignment
variable, err = Eval(variable, e)
if err != nil {
return nil, err
}
if variable.Tag != ast.Symbol {
variable, err = Eval(input[1], e)
return variable, err
}
}
if err != nil {
return nil, err
}
sym := variable.Val.(string)
evald, err := Eval(input[1], e)
if err != nil {
return nil, err
}
e.Values[sym] = evald
return evald, nil
} }
func (e env) Lookup(elem string) (*ast.AST, error) { func arithCast(input *ast.AST, e ast.Env) (float64, error) {
res, ok := e.values[elem] evald, err := Eval(input, e)
if err != nil {
return 0, err
}
if !ok { if evald.Tag != ast.Num {
if e.parent == nil { return 0, fmt.Errorf("Cannot perform arithmetic on ", evald.Pretty())
return nil, fmt.Errorf("Symbol not found: %s", elem) }
return evald.Val.(float64), nil
}
func evalArith(input []*ast.AST, e ast.Env, fn func(float64, float64) float64) (*ast.AST, error) {
ilen := len(input)
if ilen < 2 {
return nil, fmt.Errorf("Arithmetic functions take at least 2 arguments, got %d", ilen)
}
acc, err := arithCast(input[0], e)
if err != nil {
return nil, err
}
for _, elem := range input[1:] {
val, err := arithCast(elem, e)
if err != nil {
return nil, err
}
acc = fn(acc, val)
}
res := ast.AST{ast.Num, acc}
return &res, nil
}
func evalLog(input []*ast.AST, e ast.Env, fn func(float64, float64) bool) (*ast.AST, error) {
ilen := len(input)
if ilen < 2 {
return nil, fmt.Errorf("Logic functions take at least 2 arguments, got %d", ilen)
}
old, err := arithCast(input[0], e)
if err != nil {
return nil, err
}
acc := true
for _, elem := range input[1:] {
val, err := arithCast(elem, e)
if err != nil {
return nil, err
}
acc = acc && fn(old, val)
old = val
}
if acc {
return &trueVal, nil
}
return &falseVal, nil
}
func evalEq(input []*ast.AST, e ast.Env) (*ast.AST, error) {
err := checkArity(input, 2, "is")
if err != nil {
return nil, err
}
x, err := Eval(input[0], e)
if err != nil {
return nil, err
}
y, err := Eval(input[1], e)
if err != nil {
return nil, err
}
if x.Tag != y.Tag {
return &falseVal, nil
}
if x.Val == y.Val {
return &trueVal, nil
}
return &falseVal, nil
}
func evalCons(input []*ast.AST) (*ast.AST, error) {
err := checkArity(input, 2, "cons")
if err != nil {
return nil, err
}
lst := input[1]
if lst.Tag != ast.Quoted {
return nil, fmt.Errorf("Cannot cons to non-list %s", lst.Pretty())
}
fst := input[0]
res := ast.AST{ast.List, append([]*ast.AST{fst}, lst.Val.(*ast.AST).Val.([]*ast.AST)...)}
return &res, nil
}
func evalCar(input []*ast.AST) (*ast.AST, error) {
err := checkArity(input, 1, "car")
if err != nil {
return nil, err
}
lst := input[0]
if lst.Tag != ast.Quoted {
return nil, fmt.Errorf("Cannot car from non-list %s", lst.Pretty())
}
res := lst.Val.(*ast.AST).Val.([]*ast.AST)[0]
return res, nil
}
func evalCdr(input []*ast.AST) (*ast.AST, error) {
err := checkArity(input, 1, "cdr")
if err != nil {
return nil, err
}
lst := input[0]
if lst.Tag != ast.Quoted {
return nil, fmt.Errorf("Cannot cdr from non-list %s", lst.Pretty())
}
res := ast.AST{ast.List, lst.Val.(*ast.AST).Val.([]*ast.AST)[1:]}
return &res, nil
}
func evalNull(input []*ast.AST) (*ast.AST, error) {
err := checkArity(input, 1, "null")
if err != nil {
return nil, err
}
lst := input[0]
if lst.Tag != ast.Quoted {
return nil, fmt.Errorf("Cannot call null? on non-list %s", lst.Pretty())
}
res := ast.AST{ast.Bool, len(lst.Val.(*ast.AST).Val.([]*ast.AST)) == 0}
return &res, nil
}
func evalFn(input []*ast.AST, e ast.Env) (*ast.AST, error) {
err := checkArity(input, 2, "fn")
if err != nil {
return nil, err
}
args := input[0]
if args.Tag != ast.List {
return nil, fmt.Errorf("Cannot call fn with argument list %s", args.Pretty())
}
var argsStr []string
var opt *string = nil
argslst := args.Val.([]*ast.AST)
for i, a := range argslst {
if a.Tag != ast.Symbol {
return nil, fmt.Errorf("Argument list cannot contain %s", a.Pretty())
}
val := a.Val.(string)
// TODO: error handling
if val == "." {
str := argslst[i+1].Val.(string)
opt = &str
break
} }
return e.parent.Lookup(elem) argsStr = append(argsStr, val)
} }
return res, nil body := input[1]
res := ast.AST{ast.Fn, ast.Func{argsStr, opt, body, e}}
return &res, nil
} }
func checkArity(input []ast.AST, arity int, name string) error { func funcApply(f ast.Func, args []*ast.AST, e ast.Env) (*ast.AST, error) {
ilen := len(input) plen := len(f.Params)
if ilen != arity+1 { alen := len(args)
return fmt.Errorf("Argument count to '%s' must be %d, was %d", name, arity, ilen)
if plen != alen && !f.HasOpt() {
return nil, fmt.Errorf("Function expected %d arguments, was called with %d.", plen, alen)
}
for i, a := range f.Params {
f.Env.Values[a] = args[i]
}
if f.HasOpt() {
lst := ast.AST{ast.List, args[plen:]}
quoted := ast.AST{ast.Quoted, &lst}
f.Env.Values[*f.Opt] = &quoted
} }
return nil return Eval(f.Body, f.Env)
} }
func evalDef(input []ast.AST, e env) (*ast.AST, error) { func evalPr(l []*ast.AST) (*ast.AST, error) {
err := checkArity(input, 2, "=") var toPrint []string
for _, elem := range l {
toPrint = append(toPrint, elem.Pretty())
}
fmt.Print(strings.Join(toPrint, " "))
return &nilVal, nil
}
if err != nil { func evalIf(l []*ast.AST, e ast.Env) (*ast.AST, error) {
return nil, err i := 0
for i < len(l) {
if i == len(l)-1 {
return Eval(l[i], e)
}
cond, err := Eval(l[i], e)
if err != nil {
return nil, err
}
if cond == &trueVal {
return Eval(l[i+1], e)
}
i += 2
}
return &nilVal, nil
}
func evalDo(l []*ast.AST, e ast.Env) (*ast.AST, error) {
var res *ast.AST
var err error
for _, elem := range l {
res, err = Eval(elem, e)
if err != nil {
return nil, err
}
}
return res, nil
}
func evalApply(l []*ast.AST, e ast.Env) (*ast.AST, error) {
err := checkArity(l, 2, "apply")
if err != nil {
return nil, err
}
// TODO: error handling
args, err := Eval(l[1], e)
if err != nil {
return nil, err
}
if args.Tag != ast.Quoted {
return nil, fmt.Errorf("Argument 2 to apply must be a list, got %s", args.Pretty())
} }
variable := &input[1] argslst := args.Val.(*ast.AST).Val.([]*ast.AST)
return evalList(append([]*ast.AST{l[0]}, argslst...), e)
}
if variable.Tag != ast.Symbol { func evalWhile(l []*ast.AST, e ast.Env) (*ast.AST, error) {
variable, err = Eval(variable, e) err := checkArity(l, 2, "while")
if variable.Tag != ast.Symbol { if err != nil {
return nil, fmt.Errorf("First argument to 'def' must be symbol, was %v", variable.Tag) return nil, err
}
var res *ast.AST
for {
cond, err := Eval(l[0], e)
if err != nil {
return nil, err
}
if cond == &falseVal {
break
}
res, err = Eval(l[1], e)
if err != nil {
return nil, err
}
}
return res, nil
}
func evalTable(l []*ast.AST) (*ast.AST, error) {
err := checkArity(l, 0, "table")
if err != nil {
return nil, err
}
res := ast.AST{ast.Table, make(map[*ast.AST]*ast.AST)}
return &res, nil
}
func evalType(l []*ast.AST) (*ast.AST, error) {
err := checkArity(l, 1, "type")
if err != nil {
return nil, err
}
return l[0].Type(), nil
}
func evalSymbolList(l []*ast.AST, e ast.Env) (*ast.AST, error) {
sym := l[0].Val.(string)
l = l[1:]
// TODO: make more of these primitives
switch sym {
case "=":
return evalDef(l, e)
case "is":
return evalEq(l, e)
case "apply":
return evalApply(l, e)
case "quote":
lst := l[0]
res := ast.AST{ast.Quoted, lst}
return &res, nil
case "fn":
return evalFn(l, e)
case "if":
return evalIf(l, e)
case "do":
return evalDo(l, e)
case "while":
return evalWhile(l, e)
case "+":
return evalArith(l, e, func(x float64, y float64) float64 { return x + y })
case "-":
return evalArith(l, e, func(x float64, y float64) float64 { return x - y })
case "*":
return evalArith(l, e, func(x float64, y float64) float64 { return x * y })
case "/":
return evalArith(l, e, func(x float64, y float64) float64 { return x / y })
case "%":
return evalArith(l, e, func(x float64, y float64) float64 { return float64(int64(x) % int64(y)) })
case "<":
return evalLog(l, e, func(x float64, y float64) bool { return x < y })
case ">":
return evalLog(l, e, func(x float64, y float64) bool { return x > y })
}
res, err := e.Lookup(sym)
if err != nil {
return nil, err
}
return evalList(append([]*ast.AST{res}, l...), e)
}
func evalIdx(l []*ast.AST, e ast.Env) (*ast.AST, error) {
head := l[0]
err := checkArity(l, 2, "indexing")
if err != nil {
return nil, err
}
arg, err := Eval(l[1], e)
if err != nil {
return nil, err
}
switch head.Tag {
case ast.Quoted:
if arg.Tag != ast.Num {
return nil, fmt.Errorf("Cannot index using %s", arg.Pretty())
}
idx := int64(arg.Val.(float64))
var actualLen int64
val := head.Val.(*ast.AST).Val.([]*ast.AST)
actualLen = int64(len(val))
actualIdx := idx
if idx < 0 {
actualIdx = actualLen + idx
}
if actualLen > actualIdx && actualIdx > 0 {
return val[actualIdx], nil
}
return nil, fmt.Errorf("Out of bounds access (idx %d at %s)", idx, head.Pretty())
case ast.String:
if arg.Tag != ast.Num {
return nil, fmt.Errorf("Cannot index using %s", arg.Pretty())
}
idx := int64(arg.Val.(float64))
var actualLen int64
val := []rune(head.Val.(string))
actualLen = int64(len(val))
actualIdx := idx
if idx < 0 {
actualIdx = actualLen + idx
}
if actualLen > actualIdx && actualIdx > 0 {
res := ast.AST{ast.Char, val[actualIdx]}
return &res, nil
}
return nil, fmt.Errorf("Out of bounds access (idx %d at %s)", idx, head.Pretty())
case ast.Table:
val := head.Val.(map[*ast.AST]*ast.AST)
res, ok := val[arg]
if !ok {
return &nilVal, nil
}
return res, nil
}
return nil, fmt.Errorf("Cannot index %s", head.Pretty())
}
func primCall(f ast.Primitive, l []*ast.AST, e ast.Env) (*ast.AST, error) {
var args []*ast.AST
for _, a := range l {
res, err := Eval(a, e)
if err != nil {
return nil, err
} }
args = append(args, res)
} }
if err != nil { return f.Fn(args)
return nil, err
}
sym := variable.Val.(string)
evald, err := Eval(&input[2], e)
if err != nil {
return nil, err
}
e.values[sym] = evald
return evald, nil
} }
func arithCast(input ast.AST, e env) (float64, error) { func evalList(l []*ast.AST, e ast.Env) (*ast.AST, error) {
evald, err := Eval(&input, e) head := l[0]
if err != nil { var err error
return 0, err
}
if evald.Tag != ast.Num { if head.Tag == ast.List {
return 0, fmt.Errorf("Cannot perform arithmetic on ", evald.Pretty()) head, err = Eval(head, e)
}
return evald.Val.(float64), nil
}
func evalArith(input []ast.AST, e env, fn (func(float64, float64) float64)) (*ast.AST, error) {
ilen := len(input)
if ilen < 3 {
return nil, fmt.Errorf("Arithmetic functions take at least 2 arguments, got %d", ilen)
}
acc, err := arithCast(input[1], e)
if err != nil {
return nil, err
}
for _, elem := range input[2:] {
val, err := arithCast(elem, e)
if err != nil { if err != nil {
return nil, err return nil, err
} }
acc = fn(acc, val)
} }
res := ast.AST{ast.Num, acc} switch head.Tag {
return &res, nil case ast.Symbol:
return evalSymbolList(l, e)
case ast.Fn:
return funcApply(head.Val.(ast.Func), l[1:], e)
case ast.Prim:
return primCall(head.Val.(ast.Primitive), l[1:], e)
case ast.String, ast.Quoted, ast.Table:
return evalIdx(append([]*ast.AST{head}, l[1:]...), e)
}
return nil, fmt.Errorf("Cannot perform call on %s", head.Pretty())
} }
func evalEq(input []ast.AST, e env) (*ast.AST, error) { func Eval(input *ast.AST, e ast.Env) (*ast.AST, error) {
err := checkArity(input, 2, "iso") if input.Tag == ast.List {
l := input.Val.([]*ast.AST)
return evalList(l, e)
}
if input.Tag == ast.Symbol {
val := input.Val.(string)
if err != nil { var err error
return nil, err var res *ast.AST
} switch val {
case "true":
x, err := Eval(&input[1], e) res = &trueVal
case "false":
if err != nil { res = &falseVal
return nil, err case "nil":
} res = &nilVal
default:
y, err := Eval(&input[2], e) res, err = e.Lookup(val)
}
if err != nil { return res, err
return nil, err }
} return input, nil
if x.Tag != y.Tag {
return &falseVal, nil
}
if x.Val == y.Val {
return &trueVal, nil
}
return &falseVal, nil
}
func evalCons(input []ast.AST, e env) (*ast.AST, error) {
err := checkArity(input, 2, "cons")
if err != nil {
return nil, err
}
lst, err := Eval(&input[2], e)
if err != nil {
return nil, err
}
if lst.Tag != ast.List {
return nil, fmt.Errorf("Cannot cons to non-list %s", lst.Pretty())
}
fst, err := Eval(&input[1], e)
if err != nil {
return nil, err
}
res := ast.AST{ast.List, append([]ast.AST{*fst}, lst.Val.([]ast.AST)...)}
return &res, nil
}
func evalCar(input []ast.AST, e env) (*ast.AST, error) {
err := checkArity(input, 1, "car")
if err != nil {
return nil, err
}
lst, err := Eval(&input[1], e)
if err != nil {
return nil, err
}
if lst.Tag != ast.List {
return nil, fmt.Errorf("Cannot car from non-list %s", lst.Pretty())
}
res := lst.Val.([]ast.AST)[0]
return &res, nil
}
func evalCdr(input []ast.AST, e env) (*ast.AST, error) {
err := checkArity(input, 1, "cdr")
if err != nil {
return nil, err
}
lst, err := Eval(&input[1], e)
if err != nil {
return nil, err
}
if lst.Tag != ast.List {
return nil, fmt.Errorf("Cannot cdr from non-list %s", lst.Pretty())
}
res := ast.AST{ast.List, lst.Val.([]ast.AST)[1:]}
return &res, nil
}
func evalNull(input []ast.AST, e env) (*ast.AST, error) {
err := checkArity(input, 1, "null")
if err != nil {
return nil, err
}
lst, err := Eval(&input[1], e)
if err != nil {
return nil, err
}
if lst.Tag != ast.List {
return nil, fmt.Errorf("Cannot call null? on non-list %s", lst.Pretty())
}
res := ast.AST{ast.Bool, len(lst.Val.([]ast.AST)) == 0}
return &res, nil
}
func evalList(input *ast.AST, e env) (*ast.AST, error) {
l := input.Val.([]ast.AST)
head := l[0]
if head.Tag != ast.Symbol {
err := fmt.Errorf("Calling non-symbol: %s", head.Pretty())
return nil, err
}
sym := head.Val.(string)
switch sym {
case "=":
return evalDef(l, e)
case "iso":
return evalEq(l, e)
case "quote":
res := l[1]
return &res, nil
case "cons":
return evalCons(l, e)
case "car":
return evalCar(l, e)
case "cdr":
return evalCdr(l, e)
case "null?":
return evalNull(l, e)
case "+":
return evalArith(l, e, func(x float64, y float64) float64 { return x + y })
case "-":
return evalArith(l, e, func(x float64, y float64) float64 { return x - y })
case "*":
return evalArith(l, e, func(x float64, y float64) float64 { return x * y })
case "/":
return evalArith(l, e, func(x float64, y float64) float64 { return x / y })
case "%":
return evalArith(l, e, func(x float64, y float64) float64 { return float64(int64(x) % int64(y)) })
}
return input, nil
}
func Eval(input *ast.AST, e env) (*ast.AST, error) {
if (input.Tag == ast.List) {
return evalList(input, e)
}
if (input.Tag == ast.Symbol) {
val := input.Val.(string)
var err error
var res *ast.AST
switch val {
case "true":
res = &trueVal
case "false":
res = &falseVal
case "nil":
res = &nilVal
default:
res, err = e.Lookup(val)
}
return res, err
}
return input, nil
} }

View File

@@ -21,22 +21,8 @@
; not sure this is a mistake; strings may be subtly different from ; not sure this is a mistake; strings may be subtly different from
; lists of chars ; lists of chars
(mac def (name params body)
(assign do (annotate 'mac (list '= name (list 'fn params body)))
(fn args `((fn () ,@args)))))
(assign safeset (annotate 'mac
(fn (var val)
`(do (if (bound ',var)
(do (disp "*** redefining " (stderr))
(disp ',var (stderr))
(disp #\newline (stderr))))
(assign ,var ,val)))))
(assign def (annotate 'mac
(fn (name parms . body)
`(do (sref sig ',parms ',name)
(safeset ,name (fn ,parms ,@body))))))
(def caar (xs) (car (car xs))) (def caar (xs) (car (car xs)))
(def cadr (xs) (car (cdr xs))) (def cadr (xs) (car (cdr xs)))
@@ -54,8 +40,8 @@
; (def list args args) ; (def list args args)
(def copylist (xs) (def copylist (xs)
(if (no xs) (if (no xs)
nil nil
(cons (car xs) (copylist (cdr xs))))) (cons (car xs) (copylist (cdr xs)))))
(def list args (copylist args)) (def list args (copylist args))

129
main.go
View File

@@ -1,96 +1,99 @@
package main package main
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"strings" "strings"
"github.com/chzyer/readline" "github.com/chzyer/readline"
"github.com/hellerve/argos/eval" "github.com/hellerve/argos/eval"
"github.com/hellerve/argos/parser" "github.com/hellerve/argos/parser"
) )
func runRepl() { func runRepl() {
rl, err := readline.New("argos> ") rl, err := readline.New("argos> ")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer rl.Close() defer rl.Close()
e := eval.ParentEnv() e := eval.RootEnv()
for { for {
input, err := rl.Readline() input, err := rl.Readline()
if err != nil { if err != nil {
return return
} }
parsed, err, unconsumed := parser.Parse(input) if len(input) == 0 {
continue
}
if err != nil { parsed, err, unconsumed := parser.Parse(input)
fmt.Println(err)
continue
}
if len(unconsumed) != 0 { if err != nil {
fmt.Println("Unconsumed input:", strings.Join(unconsumed, " ")) fmt.Println(err)
continue continue
} }
// TODO: how to avoid bindings on error? if len(unconsumed) != 0 {
evald, err := eval.Eval(parsed, e) fmt.Println("Unconsumed input:", strings.Join(unconsumed, " "))
continue
}
// TODO: how to avoid bindings on error?
evald, err := eval.Eval(parsed, e)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
continue continue
} }
fmt.Println(evald.Pretty()) fmt.Println(evald.Pretty())
} }
} }
func runFile(path string) { func runFile(path string) {
e := eval.ParentEnv() e := eval.RootEnv()
input, err := ioutil.ReadFile(path) input, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return return
} }
parsed, err, unconsumed := parser.Parse(string(input)) parsed, err, unconsumed := parser.Parse(string(input))
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return return
} }
if len(unconsumed) != 0 { if len(unconsumed) != 0 {
fmt.Println("Unconsumed input:", strings.Join(unconsumed, " ")) fmt.Println("Unconsumed input:", strings.Join(unconsumed, " "))
return return
} }
evald, err := eval.Eval(parsed, e) evald, err := eval.Eval(parsed, e)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return return
} }
fmt.Println(evald.Pretty()) fmt.Println(evald.Pretty())
} }
func main() { func main() {
args := os.Args args := os.Args
if len(args) == 1 { if len(args) == 1 {
runRepl() runRepl()
} else { } else {
runFile(os.Args[1]) runFile(os.Args[1])
} }
} }

View File

@@ -1,140 +1,205 @@
package parser package parser
import ( import (
"errors" "errors"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"github.com/hellerve/argos/ast" "github.com/hellerve/argos/ast"
) )
func withoutEmpty(input []string) []string { func withoutEmpty(input []string) []string {
var r []string var r []string
for _, str := range input { for _, str := range input {
if str != "" { if str != "" {
r = append(r, str) r = append(r, str)
} }
} }
return r return r
} }
var comments = regexp.MustCompile(";.*") var comments = regexp.MustCompile(";.*")
var whitespace = regexp.MustCompile("\\s") var whitespace = regexp.MustCompile("\\s")
func explode(s string, start string, end string) string { func explode(s string, start string, end string) string {
return strings.Replace(strings.Replace(s, start, " "+start+" ", -1), end, " "+end+" ", -1) return strings.Replace(strings.Replace(s, start, " "+start+" ", -1), end, " "+end+" ", -1)
} }
func tokenize(input string) []string { func tokenize(input string) []string {
withoutComments := string(comments.ReplaceAll([]byte(input), []byte(""))) withoutComments := string(comments.ReplaceAll([]byte(input), []byte("")))
explodedParens := explode(withoutComments, "(", ")") explodedParens := explode(withoutComments, "(", ")")
explodedBrackets := explode(explodedParens, "[", "]") explodedBrackets := explode(explodedParens, "[", "]")
return withoutEmpty(whitespace.Split(explodedBrackets, -1)) return withoutEmpty(whitespace.Split(explodedBrackets, -1))
}
func withEscape(input string) rune {
switch input[1:] {
case "\\a":
return '\a'
case "\\b":
return '\b'
case "newline":
return '\n'
case "tab":
return '\t'
case "\\f":
return '\f'
case "\\r":
return '\r'
case "\\v":
return '\v'
case "\\\\":
return '\\'
case "\\'":
return '\''
case "\\\"":
return '"'
}
return []rune(input)[1]
} }
func parseValue(input []string) (*ast.AST, error, []string) { func parseValue(input []string) (*ast.AST, error, []string) {
var res ast.AST var res ast.AST
f, err := strconv.ParseFloat(input[0], 64) f, err := strconv.ParseFloat(input[0], 64)
if err == nil { if err == nil {
res = ast.AST{ast.Num, f} res = ast.AST{ast.Num, f}
return &res, nil, input[1:] return &res, nil, input[1:]
} }
if input[0][0] == '"' { if input[0][0] == '#' {
var agg []string res := ast.AST{ast.Char, withEscape(input[0])}
for { return &res, nil, input[1:]
if len(input) == 0 { }
return nil, errors.New("Unmatched \""), input
}
token := input[0]
input = input[1:]
agg = append(agg, token)
if token[len(token)-1] == '"' {
break
}
}
res = ast.AST{ast.String, strings.Join(agg, " ")}
return &res, nil, input
}
res = ast.AST{ast.Symbol, input[0]} if input[0][0] == '"' {
return &res, nil, input[1:] var agg []string
for {
if len(input) == 0 {
return nil, errors.New("Unmatched \""), input
}
token := input[0]
input = input[1:]
agg = append(agg, token)
if token[len(token)-1] == '"' {
break
}
}
joined := strings.Join(agg, " ")
res = ast.AST{ast.String, joined[1 : len(joined)-1]}
return &res, nil, input
}
if input[0][0] == '~' {
fn := ast.AST{ast.Symbol, input[0][1:]}
complement := ast.AST{ast.Symbol, "complement"}
res = ast.AST{ast.List, []*ast.AST{&complement, &fn}}
return &res, nil, input[1:]
}
if strings.Contains(input[0], ":") {
comp := ast.AST{ast.Symbol, "compose"}
fns := []*ast.AST{&comp}
for _, fn := range strings.Split(input[0], ":") {
astfn := ast.AST{ast.Symbol, fn}
fns = append(fns, &astfn)
}
res = ast.AST{ast.List, fns}
return &res, nil, input[1:]
}
res = ast.AST{ast.Symbol, input[0]}
return &res, nil, input[1:]
} }
func makeFn(bodyStatements []ast.AST) ast.AST { func makeFn(bodyStatements []*ast.AST) ast.AST {
body := ast.AST{ast.List, bodyStatements} body := ast.AST{ast.List, bodyStatements}
args := ast.AST{ast.List, []ast.AST{ast.AST{ast.Symbol, "_"}}} underscore := ast.AST{ast.Symbol, "_"}
args := ast.AST{ast.List, []*ast.AST{&underscore}}
fn := ast.AST{ast.Symbol, "fn"}
return ast.AST{ast.List, []ast.AST{ast.AST{ast.Symbol, "fn"}, args, body}} return ast.AST{ast.List, []*ast.AST{&fn, &args, &body}}
} }
func parseToken(input []string) (*ast.AST, error, []string) { func parseToken(input []string) (*ast.AST, error, []string) {
if len(input) == 0 { if len(input) == 0 {
return nil, errors.New("Unmatched '(' or '['"), input return nil, errors.New("Unmatched '(' or '['"), input
} }
if input[0][0] == '\'' { if input[0][0] == '\'' {
if (input[0] == "'") { if input[0] == "'" {
input = input[1:] input = input[1:]
} else { } else {
input = append([]string{input[0][1:]}, input[1:]...) input = append([]string{input[0][1:]}, input[1:]...)
} }
tmp, err, input := parseToken(input) tmp, err, input := parseToken(input)
if err != nil { if err != nil {
return nil, err, input return nil, err, input
} }
res := ast.AST{ast.List, []ast.AST{ast.AST{ast.Symbol, "quote"}, *tmp}} quote := ast.AST{ast.Symbol, "quote"}
return &res, nil, input res := ast.AST{ast.List, []*ast.AST{&quote, tmp}}
} return &res, nil, input
}
switch input[0] { switch input[0] {
case "(": { case "(":
var l []ast.AST {
input = input[1:] var l []*ast.AST
for input[0] != ")" { input = input[1:]
elem, err, newInput := parseToken(input) for input[0] != ")" {
elem, err, newInput := parseToken(input)
if err != nil { if err != nil {
return nil, err, input return nil, err, input
} }
l = append(l, *elem) if len(newInput) == 0 {
input = newInput return nil, errors.New("Unmatched '('"), input
} }
res := ast.AST{ast.List, l}
return &res, nil, input[1:]
}
case ")": {
return nil, errors.New("Unmatched ')'"), input
}
case "[": {
var l []ast.AST
input = input[1:]
for input[0] != "]" {
elem, err, newInput := parseToken(input)
if err != nil { l = append(l, elem)
return nil, err, input input = newInput
} }
res := ast.AST{ast.List, l}
return &res, nil, input[1:]
}
case ")":
{
return nil, errors.New("Unmatched ')'"), input
}
case "[":
{
var l []*ast.AST
input = input[1:]
for input[0] != "]" {
elem, err, newInput := parseToken(input)
l = append(l, *elem) if err != nil {
input = newInput return nil, err, input
} }
res := makeFn(l)
return &res, nil, input[1:] if len(newInput) == 0 {
} return nil, errors.New("Unmatched '['"), input
case "]": { }
return nil, errors.New("Unmatched ']'"), input
} l = append(l, elem)
} input = newInput
return parseValue(input) }
res := makeFn(l)
return &res, nil, input[1:]
}
case "]":
{
return nil, errors.New("Unmatched ']'"), input
}
}
return parseValue(input)
} }
func Parse(input string) (*ast.AST, error, []string) { func Parse(input string) (*ast.AST, error, []string) {
return parseToken(tokenize(input)) return parseToken(tokenize(input))
} }