Skip to content

Commit

Permalink
Merge branch 'objectmethod-arg-validation' into rework-methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Flipez committed Dec 19, 2021
2 parents 8661e67 + 6159f3a commit e9dafc0
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 84 deletions.
78 changes: 78 additions & 0 deletions object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package object

import (
"bytes"
"fmt"
"strings"

"github.com/flipez/rocket-lang/ast"
Expand Down Expand Up @@ -88,3 +89,80 @@ func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() }
func (rv *ReturnValue) InvokeMethod(method string, env Environment, args ...Object) Object {
return nil
}

type ObjectMethod struct {
argsOptional bool
argOverloading bool
argPattern [][]string
method func(*String, []Object) Object
}

func (om *ObjectMethod) validateArgs(args []Object) error {
if (len(args) < len(om.argPattern)) && !om.argsOptional {
return fmt.Errorf("To few arguments: want=%d, got=%d", len(om.argPattern), len(args))
}

if len(args) > len(om.argPattern) && !om.argOverloading {
return fmt.Errorf("To many arguments: want=%d, got=%d", len(om.argPattern), len(args))
}

if !om.argsOptional || (om.argsOptional && len(args) > 0) {
for idx, pattern := range om.argPattern {
var valid bool
for _, argType := range pattern {
if ObjectType(argType) == args[idx].Type() {
valid = true
break
}
}
if !valid {
return fmt.Errorf("Wrong argument type on position %d: got=%s, want=%s", idx, args[idx].Type(), strings.Join(pattern, "|"))
}
}
}

/* not needed for now, as there is no string method with flexible argument count
if om.argOverloading {
lastPattern := om.argPattern[len(om.argPattern)-1]
for idx, arg := range args[len(om.argPattern)-1:] {
var valid bool
for _, argType := range lastPattern {
if argType == args.Type() {
valid = true
break
}
}
if !valid {
return fmt.Errorf("FALSCHER TYPE AUF POSITION %d got=%s, want=%s", idx, arg.Type(), strings.Join(lastPattern, "|"))
}
}
}
*/

return nil
}

func (om *ObjectMethod) Usage(name string) Object {
var args string

if len(om.argPattern) > 0 {
types := make([]string, len(om.argPattern))
for idx, pattern := range om.argPattern {
types[idx] = strings.Join(pattern, "|")
}
args = strings.Join(types, ", ")

if om.argOverloading {
args += "..."
}
}

return &String{Value: fmt.Sprintf("%s(%s)", name, args)}
}

func (om *ObjectMethod) Call(s *String, args []Object) Object {
if err := om.validateArgs(args); err != nil {
return &Error{Message: err.Error()}
}
return om.method(s, args)
}
202 changes: 118 additions & 84 deletions object/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,98 +11,132 @@ type String struct {
Value string
}

func (s *String) Type() ObjectType { return STRING_OBJ }
func (s *String) Inspect() string { return s.Value }
func (s *String) InvokeMethod(method string, env Environment, args ...Object) Object {
switch method {
case "count":
if len(args) < 1 {
return &Error{Message: "Missing argument to count()!"}
}
// Note that this coerces into a string :)
arg := args[0].Inspect()
return &Integer{Value: int64(strings.Count(s.Value, arg))}
case "find":
if len(args) < 1 {
return &Error{Message: "Missing argument to find()!"}
}
var stringObjectMethods = map[string]ObjectMethod{
"count": ObjectMethod{
argPattern: [][]string{
[]string{STRING_OBJ, INTEGER_OBJ}, // first argument can be string or int
},
method: func(s *String, args []Object) Object {
arg := args[0].Inspect()
return &Integer{Value: int64(strings.Count(s.Value, arg))}
},
},

// Note that this coerces into a string :)
arg := args[0].Inspect()
return &Integer{Value: int64(strings.Index(s.Value, arg))}
case "size":
return &Integer{Value: int64(utf8.RuneCountInString(s.Value))}
case "plz_i":
i, err := strconv.ParseInt(s.Value, 10, 64)
if err != nil {
i = 0
}
return &Integer{Value: i}
case "methods":
names := []string{"count", "find", "size", "methods", "replace", "reverse", "split", "toupper", "tolower", "type"}
"find": ObjectMethod{
argPattern: [][]string{
[]string{STRING_OBJ, INTEGER_OBJ}, // first argument can be string or int
},
method: func(s *String, args []Object) Object {
arg := args[0].Inspect()
return &Integer{Value: int64(strings.Index(s.Value, arg))}
},
},

result := make([]Object, len(names), len(names))
for i, txt := range names {
result[i] = &String{Value: txt}
}
return &Array{Elements: result}
case "replace":
if len(args) < 2 {
return &Error{Message: "Missing arguments to replace()!"}
}
"size": ObjectMethod{
method: func(s *String, _ []Object) Object {
return &Integer{Value: int64(utf8.RuneCountInString(s.Value))}
},
},
"type": ObjectMethod{
method: func(s *String, _ []Object) Object {
return &String{Value: STRING_OBJ}
},
},
"plz_i": ObjectMethod{
method: func(s *String, _ []Object) Object {
i, _ := strconv.ParseInt(s.Value, 10, 64)
return &Integer{Value: i}
},
},
"replace": ObjectMethod{
argPattern: [][]string{
[]string{STRING_OBJ},
[]string{STRING_OBJ},
},
method: func(s *String, args []Object) Object {
oldS := args[0].Inspect()
newS := args[1].Inspect()
return &String{Value: strings.Replace(s.Value, oldS, newS, -1)}
},
},
"reverse": ObjectMethod{
method: func(s *String, _ []Object) Object {
out := make([]rune, utf8.RuneCountInString(s.Value))
i := len(out)
for _, c := range s.Value {
i--
out[i] = c
}
return &String{Value: string(out)}
},
},
"split": ObjectMethod{
argsOptional: true,
argPattern: [][]string{
[]string{STRING_OBJ},
},
method: func(s *String, args []Object) Object {
sep := " "

oldS := args[0].Inspect()
newS := args[1].Inspect()
return &String{Value: strings.Replace(s.Value, oldS, newS, -1)}
case "reverse":
out := make([]rune, utf8.RuneCountInString(s.Value))
i := len(out)
for _, c := range s.Value {
i--
out[i] = c
}
return &String{Value: string(out)}
case "reverse!":
out := make([]rune, utf8.RuneCountInString(s.Value))
i := len(out)
for _, c := range s.Value {
i--
out[i] = c
}
s.Value = string(out)
return &Null{}
case "split":
sep := " "
if len(args) > 0 {
sep = args[0].(*String).Value
}

if len(args) >= 1 {
sep = args[0].(*String).Value
}
fields := strings.Split(s.Value, sep)

fields := strings.Split(s.Value, sep)
l := len(fields)
result := make([]Object, l, l)
for i, txt := range fields {
result[i] = &String{Value: txt}
}
return &Array{Elements: result}
},
},
"strip": ObjectMethod{
method: func(s *String, _ []Object) Object {
return &String{Value: strings.TrimSpace(s.Value)}
},
},
"downcase": ObjectMethod{
method: func(s *String, _ []Object) Object {
return &String{Value: strings.ToLower(s.Value)}
},
},
"downcase!": ObjectMethod{
method: func(s *String, _ []Object) Object {
s.Value = strings.ToLower(s.Value)
return &Null{}
},
},
"upcase": ObjectMethod{
method: func(s *String, _ []Object) Object {
return &String{Value: strings.ToUpper(s.Value)}
},
},
"upcase!": ObjectMethod{
method: func(s *String, _ []Object) Object {
s.Value = strings.ToUpper(s.Value)
return &Null{}
},
},
}

l := len(fields)
result := make([]Object, l, l)
for i, txt := range fields {
result[i] = &String{Value: txt}
func (s *String) Type() ObjectType { return STRING_OBJ }
func (s *String) Inspect() string { return s.Value }
func (s *String) InvokeMethod(method string, env Environment, args ...Object) Object {
switch method {
case "methods":
result := make([]Object, len(stringObjectMethods), len(stringObjectMethods))
var i int
for name := range stringObjectMethods {
result[i] = &String{Value: name}
i++
}
return &Array{Elements: result}
case "strip":
return &String{Value: strings.TrimSpace(s.Value)}
case "strip!":
s.Value = strings.TrimSpace(s.Value)
return &Null{}
case "downcase":
return &String{Value: strings.ToLower(s.Value)}
case "downcase!":
s.Value = strings.ToLower(s.Value)
return &Null{}
case "upcase":
return &String{Value: strings.ToUpper(s.Value)}
case "upcase!":
s.Value = strings.ToUpper(s.Value)
return &Null{}
case "type":
return &String{Value: "string"}
default:
if objMethod, ok := stringObjectMethods[method]; ok {
return objMethod.Call(s, args)
}
}

return nil
Expand Down

0 comments on commit e9dafc0

Please sign in to comment.