diff --git a/evaluator/builtins.go b/evaluator/builtins.go index 3c1a4b0..174f75a 100644 --- a/evaluator/builtins.go +++ b/evaluator/builtins.go @@ -1,7 +1,9 @@ package evaluator import ( + "bufio" "fmt" + "io/ioutil" "os" "strconv" "strings" @@ -314,4 +316,124 @@ var builtins = map[string]*object.Builtin{ } }, }, + + "open": { + Fn: func(file string, line int, args ...object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", file, line, len(args)) + } + if args[0].Type() != object.STRING_OBJ { + return newError("argument to `open` must be STRING, got %s", file, line, args[0].Type()) + } + if args[1].Type() != object.STRING_OBJ { + return newError("argument to `open` must be STRING, got %s", file, line, args[1].Type()) + } + filename := args[0].(*object.String).Value + mode := args[1].(*object.String).Value + var flag int + switch mode { + case "r": + flag = os.O_RDONLY + case "w": + flag = os.O_WRONLY | os.O_CREATE + case "a": + flag = os.O_WRONLY | os.O_APPEND | os.O_CREATE + default: + return newError("invalid mode %s", file, line, mode) + } + openedFile, err := os.OpenFile(filename, flag, 0644) + if err != nil { + return newError("could not open file %s", file, line, filename) + } + return &object.File{File: openedFile} + }, + }, + "close": { + Fn: func(file string, line int, args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", file, line, len(args)) + } + if args[0].Type() != object.FILE_OBJ { + return newError("argument to `close` must be FILE, got %s", file, line, args[0].Type()) + } + fileObj := args[0].(*object.File) + err := fileObj.File.Close() + if err != nil { + return newError("could not close file %s", file, line, fileObj.File.Name()) + } + return NULL + }, + }, + "read": { + Fn: func(file string, line int, args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", file, line, len(args)) + } + if args[0].Type() != object.FILE_OBJ { + return newError("argument to `read` must be FILE, got %s", file, line, args[0].Type()) + } + fileObj := args[0].(*object.File) + content, err := ioutil.ReadAll(fileObj.File) + if err != nil { + return newError("could not read file %s", file, line, fileObj.File.Name()) + } + return &object.String{Value: string(content)} + }, + }, + "write": { + Fn: func(file string, line int, args ...object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", file, line, len(args)) + } + if args[0].Type() != object.FILE_OBJ { + return newError("argument to `write` must be FILE, got %s", file, line, args[0].Type()) + } + if args[1].Type() != object.STRING_OBJ { + return newError("argument to `write` must be STRING, got %s", file, line, args[1].Type()) + } + fileObj := args[0].(*object.File) + _, err := fileObj.File.WriteString(args[1].(*object.String).Value) + if err != nil { + return newError("could not write to file %s", file, line, fileObj.File.Name()) + } + return NULL + }, + }, + "readline": { + Fn: func(file string, line int, args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", file, line, len(args)) + } + if args[0].Type() != object.FILE_OBJ { + return newError("argument to `readline` must be FILE, got %s", file, line, args[0].Type()) + } + fileObj := args[0].(*object.File) + content, err := bufio.NewReader(fileObj.File).ReadString('\n') + if err != nil { + return newError("could not read file %s", file, line, fileObj.File.Name()) + } + return &object.String{Value: content} + }, + }, + "readlines": { + Fn: func(file string, line int, args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", file, line, len(args)) + } + if args[0].Type() != object.FILE_OBJ { + return newError("argument to `readlines` must be FILE, got %s", file, line, args[0].Type()) + } + fileObj := args[0].(*object.File) + content, err := ioutil.ReadAll(fileObj.File) + if err != nil { + return newError("could not read file %s", file, line, fileObj.File.Name()) + } + var elements []object.Object + for _, line := range strings.Split(string(content), "\n") { + elements = append(elements, &object.String{Value: line}) + } + + return &object.Array{Elements: elements} + }, + }, } diff --git a/object/object.go b/object/object.go index ed2e9fb..d1d3e14 100644 --- a/object/object.go +++ b/object/object.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "hash/fnv" + "os" "strconv" "strings" "unicode/utf8" @@ -35,6 +36,7 @@ const ( BUILTIN_OBJ = "BUILTIN" QUOTE_OBJ = "QUOTE" MACRO_OBJ = "MACRO" + FILE_OBJ = "FILE" ) type Boolean struct { @@ -265,3 +267,10 @@ type Float struct { func (f *Float) Inspect() string { return strconv.FormatFloat(f.Value, 'f', -1, 64) } func (f *Float) Type() ObjectType { return FLOAT_OBJ } + +type File struct { + File *os.File +} + +func (f *File) Inspect() string { return f.File.Name() } +func (f *File) Type() ObjectType { return FILE_OBJ }