diff --git a/go.mod b/go.mod index 98b77e2..ba9a91d 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( ) require ( - github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 golang.org/x/net v0.8.0 // indirect ) diff --git a/src/built-ins.go b/src/built-ins.go index 5d279ac..bfc019b 100644 --- a/src/built-ins.go +++ b/src/built-ins.go @@ -9,9 +9,13 @@ func makeGlobal() ArObject { var vars = anymap{} vars["global"] = vars vars["term"] = ArTerm + vars["ArgonVersion"] = ArString(VERSION) vars["number"] = builtinFunc{"number", ArgonNumber} vars["string"] = builtinFunc{"string", ArgonString} - vars["socket"] = builtinFunc{"boolean", ArSocket} + vars["socket"] = Map(anymap{ + "server": builtinFunc{"server", ArSocketServer}, + "client": builtinFunc{"client", ArSocketClient}, + }) vars["infinity"] = infinity vars["map"] = builtinFunc{"map", func(a ...any) (any, ArErr) { if len(a) == 0 { @@ -277,5 +281,59 @@ func makeGlobal() ArObject { } return nil, ArErr{TYPE: "TypeError", message: "Cannot convert '" + typeof(a[0]) + "' to string", EXISTS: true} }} + vars["max"] = builtinFunc{"max", func(a ...any) (any, ArErr) { + if len(a) != 1 { + return nil, ArErr{TYPE: "runtime Error", message: "max takes 1 argument, got " + fmt.Sprint(len(a)), EXISTS: true} + } + a[0] = ArValidToAny(a[0]) + switch x := a[0].(type) { + case []any: + if len(x) == 0 { + return nil, ArErr{TYPE: "runtime Error", message: "max takes a non-empty array", EXISTS: true} + } + var max number + for i, v := range x { + switch m := v.(type) { + case number: + if i == 0 { + max = m + } else { + if m.Cmp(max) == 1 { + max = m + } + } + } + } + return max, ArErr{} + } + return nil, ArErr{TYPE: "TypeError", message: "Cannot get max of type '" + typeof(a[0]) + "'", EXISTS: true} + }} + vars["min"] = builtinFunc{"min", func(a ...any) (any, ArErr) { + if len(a) != 1 { + return nil, ArErr{TYPE: "runtime Error", message: "max takes 1 argument, got " + fmt.Sprint(len(a)), EXISTS: true} + } + a[0] = ArValidToAny(a[0]) + switch x := a[0].(type) { + case []any: + if len(x) == 0 { + return nil, ArErr{TYPE: "runtime Error", message: "max takes a non-empty array", EXISTS: true} + } + var max number + for i, v := range x { + switch m := v.(type) { + case number: + if i == 0 { + max = m + } else { + if m.Cmp(max) == -1 { + max = m + } + } + } + } + return max, ArErr{} + } + return nil, ArErr{TYPE: "TypeError", message: "Cannot get max of type '" + typeof(a[0]) + "'", EXISTS: true} + }} return Map(vars) } diff --git a/src/file.go b/src/file.go index 3e1167c..1d97f2e 100644 --- a/src/file.go +++ b/src/file.go @@ -92,6 +92,30 @@ func ArRead(args ...any) (any, ArErr) { } return ArBuffer(bytes), ArErr{} }}, + "seek": builtinFunc{"seek", func(args ...any) (any, ArErr) { + if len(args) != 1 { + return ArObject{}, ArErr{TYPE: "Runtime Error", message: "seek takes 1 argument, got " + fmt.Sprint(len(args)), EXISTS: true} + } + if typeof(args[0]) != "number" { + return ArObject{}, ArErr{TYPE: "Runtime Error", message: "seek takes a number not type '" + typeof(args[0]) + "'", EXISTS: true} + } + offset := args[0].(number) + if offset.Denom().Int64() != 1 { + return ArObject{}, ArErr{TYPE: "Runtime Error", message: "seek takes an integer not type '" + typeof(args[0]) + "'", EXISTS: true} + } + _, err := file.Seek(offset.Num().Int64(), io.SeekStart) + if err != nil { + return ArObject{}, ArErr{TYPE: "Runtime Error", message: err.Error(), EXISTS: true} + } + return nil, ArErr{} + }}, + "size": builtinFunc{"size", func(...any) (any, ArErr) { + info, err := file.Stat() + if err != nil { + return ArObject{}, ArErr{TYPE: "Runtime Error", message: err.Error(), EXISTS: true} + } + return newNumber().SetInt64(info.Size()), ArErr{} + }}, }), ArErr{} } diff --git a/src/main.go b/src/main.go index a24d2e6..a434450 100644 --- a/src/main.go +++ b/src/main.go @@ -10,6 +10,8 @@ var Args = os.Args[1:] type stack = []ArObject +const VERSION = "3.0.0" + func newscope() ArObject { return Map(anymap{}) } diff --git a/src/map.go b/src/map.go index 7e12f33..c2bb2e1 100644 --- a/src/map.go +++ b/src/map.go @@ -114,8 +114,9 @@ func parseMap(code UNPARSEcode, index int, codelines []UNPARSEcode) (any, bool, }, true, ArErr{}, countIndex } +var mutex = sync.RWMutex{} + func Map(m anymap) ArObject { - var mutex = sync.RWMutex{} obj := ArObject{ obj: anymap{ "__value__": m, diff --git a/src/socket.go b/src/socket.go index b1e8b19..2f8890e 100644 --- a/src/socket.go +++ b/src/socket.go @@ -6,7 +6,152 @@ import ( "time" ) -func ArSocket(args ...any) (any, ArErr) { +func ArSocketClient(args ...any) (any, ArErr) { + if len(args) != 2 { + return ArObject{}, ArErr{ + TYPE: "SocketError", + message: "Socket takes exactly 2 arguments", + EXISTS: true, + } + } else if typeof(args[0]) != "string" { + return ArObject{}, ArErr{ + TYPE: "SocketError", + message: "Socket type must be a string", + EXISTS: true, + } + } else if typeof(args[1]) != "string" { + return ArObject{}, ArErr{ + TYPE: "SocketError", + message: "Socket address must be a string", + EXISTS: true, + } + } + networktype := ArValidToAny(args[0]).(string) + address := ArValidToAny(args[1]).(string) + conn, err := net.Dial(networktype, address) + if err != nil { + return ArObject{}, ArErr{ + TYPE: "SocketError", + message: fmt.Sprintf("Socket connection failed: %s", err.Error()), + EXISTS: true, + } + } + return ArObject{ + obj: anymap{ + "read": builtinFunc{ + "read", + func(args ...any) (any, ArErr) { + if len(args) != 1 { + return ArObject{}, ArErr{ + TYPE: "SocketError", + message: "Socket.readData() takes exactly 1 argument", + EXISTS: true, + } + } + if conn == nil { + return ArObject{}, ArErr{ + TYPE: "SocketError", + message: "Connection is closed", + EXISTS: true, + } + } + buf := make([]byte, args[0].(number).Num().Int64()) + n, err := conn.Read(buf) + if err != nil { + return ArObject{}, ArErr{ + TYPE: "SocketError", + message: fmt.Sprintf("Socket read failed: %s", err.Error()), + EXISTS: true, + } + } + return ArBuffer(buf[:n]), ArErr{} + }}, + "write": builtinFunc{ + "write", + func(args ...any) (any, ArErr) { + if len(args) != 1 { + return nil, ArErr{ + TYPE: "TypeError", + message: fmt.Sprintf("write() takes exactly 1 argument (%d given)", len(args)), + EXISTS: true, + } + } + if typeof(args[0]) != "buffer" { + return ArObject{}, ArErr{ + TYPE: "TypeError", + message: fmt.Sprintf("write() argument must be a buffer, not %s", typeof(args[0])), + EXISTS: true, + } + } + if conn == nil { + return ArObject{}, ArErr{ + TYPE: "SocketError", + message: "Connection is closed", + EXISTS: true, + } + } + args[0] = ArValidToAny(args[0]) + if typeof(args[0]) != "buffer" { + return ArObject{}, ArErr{ + TYPE: "TypeError", + message: fmt.Sprintf("write() argument must be a buffer, not %s", typeof(args[0])), + EXISTS: true, + } + } + _, err := conn.Write(args[0].([]byte)) + if err != nil { + return ArObject{}, ArErr{ + TYPE: "SocketError", + message: err.Error(), + EXISTS: true, + } + } + return nil, ArErr{} + }}, + "close": builtinFunc{ + "close", + func(args ...any) (any, ArErr) { + if conn == nil { + return ArObject{}, ArErr{ + TYPE: "SocketError", + message: "Connection is already closed", + EXISTS: true, + } + } + err := conn.Close() + if err != nil { + return ArObject{}, ArErr{ + TYPE: "SocketError", + message: err.Error(), + EXISTS: true, + } + } + conn = nil + return nil, ArErr{} + }, + }, + "isClosed": builtinFunc{ + "isClosed", + func(args ...any) (any, 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{} + + }, + }, + }}, ArErr{} +} + +func ArSocketServer(args ...any) (any, ArErr) { if len(args) != 2 { return ArObject{}, ArErr{ TYPE: "SocketError", diff --git a/src/translate.go b/src/translate.go index 3a4c92b..eb5395d 100644 --- a/src/translate.go +++ b/src/translate.go @@ -71,9 +71,6 @@ func translateVal(code UNPARSEcode, index int, codelines []UNPARSEcode, isLine i return resp, worked, err, i } } - if isnot(code) { - return parseNot(code, index, codelines, isLine) - } if isSetVariable(code) { resp, worked, err, i = parseSetVariable(code, index, codelines, isLine) if worked { @@ -121,6 +118,12 @@ func translateVal(code UNPARSEcode, index int, codelines []UNPARSEcode, isLine i return nil, worked, err, step } } + if isnot(code) { + resp, worked, err, i = parseNot(code, index, codelines, isLine) + if worked { + return resp, worked, err, i + } + } if isCall(code) { resp, worked, err, i = parseCall(code, index, codelines) if worked { diff --git a/src/typeof.go b/src/typeof.go index c00f44c..a28a828 100644 --- a/src/typeof.go +++ b/src/typeof.go @@ -18,6 +18,10 @@ func typeof(val any) string { return "function" case builtinFunc: return "function" + case byte: + return "byte" + case []byte: + return "buffer" case ArObject: if val, ok := x.obj["__name__"]; ok { val := ArValidToAny(val)