Skip to content

Commit

Permalink
Merge pull request #61 from JunNishimura/feature/add_fizz_buzz_program
Browse files Browse the repository at this point in the history
handle with fizz buzz problem
  • Loading branch information
JunNishimura authored Oct 14, 2024
2 parents 9b8815a + 8c95254 commit 8d955c6
Show file tree
Hide file tree
Showing 7 changed files with 615 additions and 65 deletions.
68 changes: 66 additions & 2 deletions evaluator/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,32 @@ var builtins = map[string]*object.Builtin{
return &object.Integer{Value: result}
},
},
"%": {
Fn: func(args object.Object) object.Object {
arrayArg, ok := args.(*object.Array)
if !ok {
return newError("argument to `%%` must be ARRAY, got %s", args.Type())
}
if len(arrayArg.Elements) != 2 {
return newError("number of arguments to '%%' must be 2, got %d", len(arrayArg.Elements))
}

first, ok := arrayArg.Elements[0].(*object.Integer)
if !ok {
return newError("first argument to '%%' must be INTEGER, got %s", arrayArg.Elements[0].Type())
}
second, ok := arrayArg.Elements[1].(*object.Integer)
if !ok {
return newError("second argument to '%%' must be INTEGER, got %s", arrayArg.Elements[1].Type())
}

if second.Value == 0 {
return newError("division by zero")
}

return &object.Integer{Value: first.Value % second.Value}
},
},
"==": {
Fn: func(args object.Object) object.Object {
arrayArg, ok := args.(*object.Array)
Expand All @@ -112,7 +138,7 @@ var builtins = map[string]*object.Builtin{
}

for i := 0; i < len(arrayArg.Elements)-1; i++ {
if arrayArg.Elements[i] != arrayArg.Elements[i+1] {
if arrayArg.Elements[i].Inspect() != arrayArg.Elements[i+1].Inspect() {
return False
}
}
Expand All @@ -131,7 +157,7 @@ var builtins = map[string]*object.Builtin{
}

for i := 0; i < len(arrayArg.Elements)-1; i++ {
if arrayArg.Elements[i] != arrayArg.Elements[i+1] {
if arrayArg.Elements[i].Inspect() != arrayArg.Elements[i+1].Inspect() {
return True
}
}
Expand Down Expand Up @@ -220,6 +246,44 @@ var builtins = map[string]*object.Builtin{
return True
},
},
"&&": {
Fn: func(args object.Object) object.Object {
arrayArg, ok := args.(*object.Array)
if !ok {
return newError("argument to '&&' must be ARRAY, got %s", args.Type())
}
if len(arrayArg.Elements) <= 1 {
return newError("number of arguments to '&&' must be more than 1, got %d", len(arrayArg.Elements))
}

for _, arg := range arrayArg.Elements {
if arg != True {
return False
}
}

return True
},
},
"||": {
Fn: func(args object.Object) object.Object {
arrayArg, ok := args.(*object.Array)
if !ok {
return newError("argument to '||' must be ARRAY, got %s", args.Type())
}
if len(arrayArg.Elements) <= 1 {
return newError("number of arguments to '||' must be more than 1, got %d", len(arrayArg.Elements))
}

for _, arg := range arrayArg.Elements {
if arg == True {
return True
}
}

return False
},
},
"<=": {
Fn: func(args object.Object) object.Object {
arrayArg, ok := args.(*object.Array)
Expand Down
32 changes: 31 additions & 1 deletion evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package evaluator

import (
"fmt"
"regexp"
"strings"

"github.com/JunNishimura/jsop/ast"
Expand All @@ -14,6 +15,8 @@ var (
False = &object.Boolean{Value: false}
)

const identEmbedPattern = `\{\s*\$\w+\s*\}`

func Eval(exp ast.Expression, env *object.Environment) object.Object {
switch expt := exp.(type) {
case *ast.Array:
Expand All @@ -25,7 +28,15 @@ func Eval(exp ast.Expression, env *object.Environment) object.Object {
return evalSymbol(expt, env)
}

return &object.String{Value: expt.Value}
re := regexp.MustCompile(identEmbedPattern)
matches := re.FindAllString(expt.Value, -1)

if len(matches) == 0 {
return &object.String{Value: expt.Value}
}

// expand embedded identifiers
return evalEmbeddedIdentifiers(expt, env, matches)
case *ast.Boolean:
return nativeBoolToBooleanObject(expt.Value)
case *ast.PrefixAtom:
Expand Down Expand Up @@ -501,3 +512,22 @@ func evalLambdaExpression(exp ast.Expression, env *object.Environment) object.Ob
Env: env,
}
}

func evalEmbeddedIdentifiers(strLiteral *ast.StringLiteral, env *object.Environment, matches []string) object.Object {
evaluatedIdents := make([]object.Object, 0)
for _, match := range matches {
identName := strings.TrimSpace(strings.Trim(match, "{}")) // remove leading/trailing spaces and {}
ident, isFound := env.Get(identName)
if !isFound {
return newError("identifier not found: %s", identName)
}
evaluatedIdents = append(evaluatedIdents, ident)
}

newStrValue := strLiteral.Value
for i, match := range matches {
newStrValue = strings.Replace(newStrValue, match, evaluatedIdents[i].Inspect(), 1)
}

return &object.String{Value: newStrValue}
}
89 changes: 89 additions & 0 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,17 @@ func TestEvalIntegerExpression(t *testing.T) {
`,
expected: 2,
},
{
name: "modulus",
input: `
{
"command": {
"symbol": "%",
"args": [5, 2]
}
}`,
expected: 1,
},
{
name: "single line comment",
input: `
Expand Down Expand Up @@ -304,6 +315,50 @@ func TestEvalBooleanExpression(t *testing.T) {
}`,
expected: true,
},
{
name: "logical AND: return true",
input: `
{
"command": {
"symbol": "&&",
"args": [true, true]
}
}`,
expected: true,
},
{
name: "logical AND: return false",
input: `
{
"command": {
"symbol": "&&",
"args": [true, false]
}
}`,
expected: false,
},
{
name: "logical OR: return true",
input: `
{
"command": {
"symbol": "||",
"args": [true, false]
}
}`,
expected: true,
},
{
name: "logical OR: return false",
input: `
{
"command": {
"symbol": "||",
"args": [false, false]
}
}`,
expected: false,
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -781,6 +836,40 @@ func TestArrayExpression(t *testing.T) {
]`,
expected: []any{[]any{10, 20, 30}, 3},
},
{
name: "embed an identifier in string",
input: `
[
{
"set": {
"var": "$x",
"val": 10
}
},
"{$x}: hello"
]`,
expected: []any{10, "10: hello"},
},
{
name: "embed identifiers in string",
input: `
[
{
"set": {
"var": "$x",
"val": 10
}
},
{
"set": {
"var": "$y",
"val": 20
}
},
"{$x}: hello, {$y}: world"
]`,
expected: []any{10, 20, "10: hello, 20: world"},
},
}

for _, tt := range tests {
Expand Down
114 changes: 114 additions & 0 deletions examples/fizz_buzz.jsop.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
[
{
"set": {
"var": "$num",
"val": 31
}
},
{
"loop": {
"for": "$i",
"from": 1,
"until": "$num",
"do": {
"if": {
"cond": {
"command": {
"symbol": "&&",
"args": [
{
"command": {
"symbol": "==",
"args": [
{
"command": {
"symbol": "%",
"args": ["$i", 3]
}
},
0
]
}
},
{
"command": {
"symbol": "==",
"args": [
{
"command": {
"symbol": "%",
"args": ["$i", 5]
}
},
0
]
}
}
]
}
},
"conseq": {
"command": {
"symbol": "print",
"args": "{$i}: FizzBuzz"
}
},
"alt": {
"if": {
"cond": {
"command": {
"symbol": "==",
"args": [
{
"command": {
"symbol": "%",
"args": ["$i", 3]
}
},
0
]
}
},
"conseq": {
"command": {
"symbol": "print",
"args": "{$i}: Fizz"
}
},
"alt": {
"if": {
"cond": {
"command": {
"symbol": "==",
"args": [
{
"command": {
"symbol": "%",
"args": ["$i", 5]
}
},
0
]
}
},
"conseq": {
"command": {
"symbol": "print",
"args": "{$i}: Buzz"
}
},
"alt": {
"command": {
"symbol": "print",
"args": "$i"
}
}
}
}
}
}
}
}
}
}
]
Loading

0 comments on commit 8d955c6

Please sign in to comment.