diff --git a/src/arvalid.go b/src/arvalid.go index c1c4b59..c349bb1 100644 --- a/src/arvalid.go +++ b/src/arvalid.go @@ -8,6 +8,10 @@ func AnyToArValid(arr any) any { return ArString(arr) case anymap: return Map(arr) + case []byte: + return ArBuffer(arr) + case byte: + return ArByte(arr) default: return arr } diff --git a/src/buffer.go b/src/buffer.go new file mode 100644 index 0000000..47ffab5 --- /dev/null +++ b/src/buffer.go @@ -0,0 +1,194 @@ +package main + +import "fmt" + +func ArByte(Byte byte) ArObject { + obj := ArObject{ + obj: anymap{ + "__name__": "byte", + "__value__": Byte, + }, + } + obj.obj["__string__"] = builtinFunc{ + "__string__", + func(a ...any) (any, ArErr) { + return "", ArErr{} + }, + } + obj.obj["__repr__"] = builtinFunc{ + "__repr__", + func(a ...any) (any, ArErr) { + return "", ArErr{} + }, + } + obj.obj["number"] = builtinFunc{ + "number", + func(a ...any) (any, ArErr) { + return newNumber().SetInt64(int64(Byte)), ArErr{} + }, + } + obj.obj["from"] = builtinFunc{ + "from", + func(a ...any) (any, ArErr) { + if len(a) == 0 { + return nil, ArErr{ + TYPE: "TypeError", + message: "expected at least 1 argument, got 0", + EXISTS: true, + } + } + a[0] = ArValidToAny(a[0]) + switch x := a[0].(type) { + case number: + if x.Denom().Cmp(one.Denom()) != 0 { + return nil, ArErr{ + TYPE: "TypeError", + message: "expected integer, got " + fmt.Sprint(x), + EXISTS: true, + } + } + n := x.Num().Int64() + if n > 255 || n < 0 { + return nil, ArErr{ + TYPE: "ValueError", + message: "expected number between 0 and 255, got " + fmt.Sprint(floor(x).Num().Int64()), + EXISTS: true, + } + } + Byte = byte(n) + case string: + if len(x) != 1 { + return nil, ArErr{ + TYPE: "ValueError", + message: "expected string of length 1, got " + fmt.Sprint(len(x)), + EXISTS: true, + } + } + Byte = byte(x[0]) + default: + return nil, ArErr{ + TYPE: "TypeError", + message: "expected number or string, got " + typeof(x), + EXISTS: true, + } + } + return obj, ArErr{} + }, + } + return obj +} + +func ArBuffer(buf []byte) ArObject { + obj := ArObject{ + obj: anymap{ + "__name__": "buffer", + "__value__": buf, + "length": newNumber().SetInt64(int64(len(buf))), + }, + } + obj.obj["__string__"] = builtinFunc{ + "__string__", + func(a ...any) (any, ArErr) { + return "", ArErr{} + }, + } + obj.obj["__repr__"] = builtinFunc{ + "__repr__", + func(a ...any) (any, ArErr) { + return "", ArErr{} + }, + } + obj.obj["from"] = builtinFunc{ + "from", + func(a ...any) (any, ArErr) { + if len(a) == 0 { + return nil, ArErr{ + TYPE: "TypeError", + message: "expected at least 1 argument, got 0", + EXISTS: true, + } + } + a[0] = ArValidToAny(a[0]) + switch x := a[0].(type) { + case string: + buf = []byte(x) + case []byte: + buf = x + case []any: + outputbuf := []byte{} + for _, v := range x { + switch y := v.(type) { + case number: + if y.Denom().Cmp(one.Denom()) != 0 { + return nil, ArErr{ + TYPE: "TypeError", + message: "Cannot convert non-integer to byte", + EXISTS: true, + } + } + outputbuf = append(outputbuf, byte(y.Num().Int64())) + default: + return nil, ArErr{ + TYPE: "TypeError", + message: "Cannot convert " + typeof(v) + " to byte", + EXISTS: true, + } + } + } + buf = outputbuf + default: + return nil, ArErr{ + TYPE: "TypeError", + message: "expected string or []byte, got " + typeof(x), + EXISTS: true, + } + } + obj.obj["__value__"] = buf + obj.obj["length"] = newNumber().SetInt64(int64(len(buf))) + return obj, ArErr{} + }, + } + obj.obj["to"] = builtinFunc{ + "to", + func(a ...any) (any, ArErr) { + if len(a) != 1 { + return nil, ArErr{ + TYPE: "TypeError", + message: "expected 1 argument, got " + fmt.Sprint(len(a)), + EXISTS: true, + } + } + if typeof(a[0]) != "string" { + return nil, ArErr{ + TYPE: "TypeError", + message: "expected string, got " + typeof(a[0]), + EXISTS: true, + } + } + Type := ArValidToAny(a[0]).(string) + switch Type { + case "string": + return ArString(string(buf)), ArErr{} + case "bytes": + output := []any{} + for _, v := range buf { + output = append(output, ArByte(v)) + } + return ArArray(output), ArErr{} + case "array": + output := []any{} + for _, v := range buf { + output = append(output, newNumber().SetInt64(int64(v))) + } + return ArArray(output), ArErr{} + default: + return nil, ArErr{ + TYPE: "TypeError", + message: "expected string, bytes or array, got '" + Type + "'", + EXISTS: true, + } + } + }, + } + return obj +} diff --git a/src/built-ins.go b/src/built-ins.go index 7a94bf5..5d279ac 100644 --- a/src/built-ins.go +++ b/src/built-ins.go @@ -63,6 +63,12 @@ func makeGlobal() ArObject { } return nil, ArErr{TYPE: "TypeError", message: "Cannot convert '" + typeof(a[0]) + "' to hex", EXISTS: true} }} + vars["buffer"] = builtinFunc{"buffer", func(a ...any) (any, ArErr) { + if len(a) != 0 { + return nil, ArErr{TYPE: "TypeError", message: "expected 0 arguments, got " + fmt.Sprint(len(a)), EXISTS: true} + } + return ArBuffer([]byte{}), ArErr{} + }} vars["throwError"] = builtinFunc{"throwError", ArThrowError} vars["array"] = builtinFunc{"array", func(a ...any) (any, ArErr) { if len(a) == 0 { diff --git a/src/file.go b/src/file.go index 6e455bc..3e1167c 100644 --- a/src/file.go +++ b/src/file.go @@ -67,16 +67,30 @@ func ArRead(args ...any) (any, ArErr) { } return mimetype.String(), ArErr{} }}, - "bytes": builtinFunc{"bytes", func(...any) (any, ArErr) { + "buffer": builtinFunc{"buffer", func(args ...any) (any, ArErr) { + if len(args) > 1 { + return ArObject{}, ArErr{TYPE: "Runtime Error", message: "buffer takes 0 or 1 argument, got " + fmt.Sprint(len(args)), EXISTS: true} + } + if len(args) == 1 { + if typeof(args[0]) != "number" { + return ArObject{}, ArErr{TYPE: "Runtime Error", message: "buffer takes a number not type '" + typeof(args[0]) + "'", EXISTS: true} + } + size := args[0].(number) + if size.Denom().Int64() != 1 { + return ArObject{}, ArErr{TYPE: "Runtime Error", message: "buffer takes an integer not type '" + typeof(args[0]) + "'", EXISTS: true} + } + buf := make([]byte, size.Num().Int64()) + n, err := file.Read(buf) + if err != nil { + return ArObject{}, ArErr{TYPE: "Runtime Error", message: err.Error(), EXISTS: true} + } + return ArBuffer(buf[:n]), ArErr{} + } bytes, err := readbinary(file) if err != nil { return ArObject{}, ArErr{TYPE: "Runtime Error", message: err.Error(), EXISTS: true} } - ArBinary := []any{} - for _, b := range bytes { - ArBinary = append(ArBinary, newNumber().SetInt64(int64(b))) - } - return ArBinary, ArErr{} + return ArBuffer(bytes), ArErr{} }}, }), ArErr{} } @@ -105,6 +119,17 @@ func ArWrite(args ...any) (any, ArErr) { file.Write([]byte(args[0].(string))) return nil, ArErr{} }}, + "buffer": builtinFunc{"buffer", func(args ...any) (any, ArErr) { + if len(args) != 1 { + return ArObject{}, ArErr{TYPE: "Runtime Error", message: "buffer takes 1 argument, got " + fmt.Sprint(len(args)), EXISTS: true} + } + if typeof(args[0]) != "buffer" { + return ArObject{}, ArErr{TYPE: "Runtime Error", message: "buffer takes a buffer not type '" + typeof(args[0]) + "'", EXISTS: true} + } + args[0] = ArValidToAny(args[0]) + file.Write(args[0].([]byte)) + return nil, ArErr{} + }}, "json": builtinFunc{"json", func(args ...any) (any, ArErr) { if len(args) != 1 { return ArObject{}, ArErr{TYPE: "Runtime Error", message: "json takes 1 argument, got " + fmt.Sprint(len(args)), EXISTS: true} diff --git a/src/run.go b/src/run.go index 4982983..316e661 100644 --- a/src/run.go +++ b/src/run.go @@ -216,7 +216,7 @@ func runVal(line any, stack stack, stacklevel int) (any, ArErr) { break } return runTryCatch(x, stack, stacklevel+1) - case bool, ArObject, number, nil, Callable: + case bool, ArObject, number, nil, Callable, builtinFunc, anymap: return x, ArErr{} } if stackoverflow { diff --git a/src/socket.go b/src/socket.go index 51c3aec..b1e8b19 100644 --- a/src/socket.go +++ b/src/socket.go @@ -3,6 +3,7 @@ package main import ( "fmt" "net" + "time" ) func ArSocket(args ...any) (any, ArErr) { @@ -46,6 +47,13 @@ func ArSocket(args ...any) (any, ArErr) { "accept": builtinFunc{ "accept", func(args ...any) (any, ArErr) { + if ln == nil { + return ArObject{}, ArErr{ + TYPE: "SocketError", + message: "Socket is closed", + EXISTS: true, + } + } conn, err := ln.Accept() if err != nil { return ArObject{}, ArErr{ @@ -61,13 +69,14 @@ func ArSocket(args ...any) (any, ArErr) { if len(args) != 1 { return ArObject{}, ArErr{ TYPE: "SocketError", - message: "Socket.read() takes exactly 1 argument", + message: "Socket.readData() takes exactly 1 argument", EXISTS: true, } - } else if typeof(args[0]) != "number" { + } + if conn == nil { return ArObject{}, ArErr{ TYPE: "SocketError", - message: "Socket.read() argument must be a number", + message: "Connection is closed", EXISTS: true, } } @@ -80,33 +89,7 @@ func ArSocket(args ...any) (any, ArErr) { EXISTS: true, } } - return ArString(string(buf[:n])), ArErr{} - }, - }, - "writeData": builtinFunc{ - "writeData", - func(args ...any) (any, ArErr) { - if len(args) != 1 { - return ArObject{}, ArErr{ - TYPE: "SocketError", - message: "Socket.writeData() takes exactly 1 argument", - EXISTS: true, - } - } - data := ArValidToAny(args[0]) - switch x := data.(type) { - case []any: - bytes := []byte{} - for _, v := range x { - bytes = append(bytes, byte(v.(number).Num().Int64())) - } - conn.Write(bytes) - return nil, ArErr{} - } - return nil, ArErr{ - TYPE: "SocketError", - message: "Socket.writeData() argument must be a array of numbers", - } + return ArBuffer(buf[:n]), ArErr{} }, }, "write": builtinFunc{ @@ -115,24 +98,53 @@ func ArSocket(args ...any) (any, ArErr) { if len(args) != 1 { return ArObject{}, ArErr{ TYPE: "SocketError", - message: "Socket.write() takes exactly 1 argument", - EXISTS: true, - } - } else if typeof(args[0]) != "string" { - return ArObject{}, ArErr{ - TYPE: "SocketError", - message: "Socket.write() argument must be a string", + message: "Socket.writeData() takes exactly 1 argument", EXISTS: true, } } - data := ArValidToAny(args[0]).(string) - conn.Write([]byte(data)) - return nil, ArErr{} + if conn == nil { + return ArObject{}, ArErr{ + TYPE: "SocketError", + message: "Connection is closed", + EXISTS: true, + } + } + data := ArValidToAny(args[0]) + switch x := data.(type) { + case []any: + bytes := []byte{} + for _, v := range x { + if typeof(v) != "number" && v.(number).Denom().Int64() != 1 { + return ArObject{}, ArErr{ + TYPE: "SocketError", + message: "Socket.writeData() argument must be a array of integers", + EXISTS: true, + } + } + bytes = append(bytes, byte(v.(number).Num().Int64())) + } + conn.Write(bytes) + return nil, ArErr{} + case []byte: + conn.Write(x) + return nil, ArErr{} + } + return nil, ArErr{ + TYPE: "SocketError", + message: "Socket.writeData() argument must be a array of numbers", + } }, }, "close": builtinFunc{ "close", func(args ...any) (any, ArErr) { + if conn == nil { + return ArObject{}, ArErr{ + TYPE: "SocketError", + message: "Connection is already closed", + EXISTS: true, + } + } conn.Close() conn = nil return nil, ArErr{} @@ -141,12 +153,31 @@ func ArSocket(args ...any) (any, ArErr) { "isClosed": builtinFunc{ "isClosed", func(args ...any) (any, ArErr) { - return conn == nil, ArErr{} + if conn == nil { + return true, ArErr{} + } + conn.SetWriteDeadline(time.Now().Add(1 * time.Millisecond)) + _, err := conn.Write([]byte{}) + conn.SetWriteDeadline(time.Time{}) + if err != nil { + conn.Close() + conn = nil + return true, ArErr{} + } + return false, ArErr{} + }, }, "RemoteAddr": builtinFunc{ "RemoteAddr", func(args ...any) (any, ArErr) { + if conn == nil { + return ArObject{}, ArErr{ + TYPE: "SocketError", + message: "Connection is closed", + EXISTS: true, + } + } return ArString(conn.RemoteAddr().String()), ArErr{} }, }, @@ -162,6 +193,13 @@ func ArSocket(args ...any) (any, ArErr) { "close": builtinFunc{ "close", func(args ...any) (any, ArErr) { + if ln == nil { + return ArObject{}, ArErr{ + TYPE: "SocketError", + message: "Socket is already closed", + EXISTS: true, + } + } ln.Close() ln = nil return nil, ArErr{} diff --git a/src/term-class.go b/src/term-class.go index d7ede5a..05ede44 100644 --- a/src/term-class.go +++ b/src/term-class.go @@ -2,10 +2,12 @@ package main import ( "fmt" + "sync" "time" ) var timing = anymap{} +var timingSync = sync.RWMutex{} var ArTerm = Map(anymap{ "log": builtinFunc{"log", func(args ...any) (any, ArErr) { @@ -100,7 +102,9 @@ var ArTerm = Map(anymap{ if len(args) > 0 { id = ArValidToAny(args[0]) } + timingSync.Lock() timing[id] = time.Now() + timingSync.Unlock() return nil, ArErr{} }, }, @@ -109,11 +113,15 @@ var ArTerm = Map(anymap{ if len(args) > 0 { id = ArValidToAny(args[0]) } + timingSync.RLock() if _, ok := timing[id]; !ok { return nil, ArErr{TYPE: "TypeError", message: "Cannot find timer with id '" + fmt.Sprint(id) + "'", EXISTS: true} } timesince := time.Since(timing[id].(time.Time)) + timingSync.RUnlock() + timingSync.Lock() delete(timing, id) + timingSync.Unlock() if id == nil { id = "Timer" } diff --git a/src/to-argon.go b/src/to-argon.go index 6ce65ea..bc0c3d9 100644 --- a/src/to-argon.go +++ b/src/to-argon.go @@ -12,7 +12,6 @@ import ( ) func anyToArgon(x any, quote bool, simplify bool, depth int, indent int, colored bool, plain int) string { - x = ArValidToAny(x) output := []string{} maybenewline := "" if plain == 1 { @@ -75,7 +74,37 @@ func anyToArgon(x any, quote bool, simplify bool, depth int, indent int, colored output = append(output, "\x1b[0m") } case ArObject: - + if callable, ok := x.obj["__string__"]; ok && !quote { + val, err := runCall( + call{ + callable: callable, + args: []any{}, + }, + stack{}, + 0, + ) + if !err.EXISTS { + output = append(output, anyToArgon(val, false, simplify, depth, indent, colored, plain)) + break + } + } else if callable, ok := x.obj["__repr__"]; ok { + val, err := runCall( + call{ + callable: callable, + args: []any{}, + }, + stack{}, + 0, + ) + if !err.EXISTS { + output = append(output, anyToArgon(val, false, simplify, depth, indent, colored, plain)) + break + } + } else if val, ok := x.obj["__value__"]; ok { + output = append(output, anyToArgon(val, quote, simplify, depth, indent, colored, plain)) + break + } + output = append(output, "") case anymap: if len(x) == 0 { return "{}"