Skip to content

Commit 9e14387

Browse files
committed
extract JSON builtins from regex runtime file
1 parent 76a10cb commit 9e14387

3 files changed

Lines changed: 176 additions & 170 deletions

File tree

docs/architecture.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ Builtins are registered during engine initialization:
129129
- domain files:
130130
- `vibes/builtins.go` (core/id helpers)
131131
- `vibes/builtins_numeric.go`
132-
- `vibes/builtins_json_regex.go`
132+
- `vibes/builtins_json.go` (JSON parse/stringify builtins)
133+
- `vibes/builtins_json_regex.go` (Regex namespace builtins)
133134
- class/object registration helpers in `vibes/interpreter_builtins_data.go` (`JSON`/`Regex` namespace objects)
134135
- duration class registration in `vibes/interpreter_builtins_duration.go`
135136
- time class registration in `vibes/interpreter_builtins_time.go`

vibes/builtins_json.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package vibes
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"reflect"
8+
"strings"
9+
)
10+
11+
func builtinJSONParse(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
12+
if len(args) != 1 || args[0].Kind() != KindString {
13+
return NewNil(), fmt.Errorf("JSON.parse expects a single JSON string argument")
14+
}
15+
if len(kwargs) > 0 {
16+
return NewNil(), fmt.Errorf("JSON.parse does not accept keyword arguments")
17+
}
18+
if !block.IsNil() {
19+
return NewNil(), fmt.Errorf("JSON.parse does not accept blocks")
20+
}
21+
22+
raw := args[0].String()
23+
if len(raw) > maxJSONPayloadBytes {
24+
return NewNil(), fmt.Errorf("JSON.parse input exceeds limit %d bytes", maxJSONPayloadBytes)
25+
}
26+
27+
decoder := json.NewDecoder(strings.NewReader(raw))
28+
decoder.UseNumber()
29+
30+
var decoded any
31+
if err := decoder.Decode(&decoded); err != nil {
32+
return NewNil(), fmt.Errorf("JSON.parse invalid JSON: %v", err)
33+
}
34+
if err := decoder.Decode(&struct{}{}); err != io.EOF {
35+
return NewNil(), fmt.Errorf("JSON.parse invalid JSON: trailing data")
36+
}
37+
38+
value, err := jsonValueToVibeValue(decoded)
39+
if err != nil {
40+
return NewNil(), err
41+
}
42+
return value, nil
43+
}
44+
45+
func builtinJSONStringify(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
46+
if len(args) != 1 {
47+
return NewNil(), fmt.Errorf("JSON.stringify expects a single value argument")
48+
}
49+
if len(kwargs) > 0 {
50+
return NewNil(), fmt.Errorf("JSON.stringify does not accept keyword arguments")
51+
}
52+
if !block.IsNil() {
53+
return NewNil(), fmt.Errorf("JSON.stringify does not accept blocks")
54+
}
55+
56+
state := &jsonStringifyState{
57+
seenArrays: map[uintptr]struct{}{},
58+
seenHashes: map[uintptr]struct{}{},
59+
}
60+
encoded, err := vibeValueToJSONValue(args[0], state)
61+
if err != nil {
62+
return NewNil(), err
63+
}
64+
65+
payload, err := json.Marshal(encoded)
66+
if err != nil {
67+
return NewNil(), fmt.Errorf("JSON.stringify failed: %v", err)
68+
}
69+
if len(payload) > maxJSONPayloadBytes {
70+
return NewNil(), fmt.Errorf("JSON.stringify output exceeds limit %d bytes", maxJSONPayloadBytes)
71+
}
72+
return NewString(string(payload)), nil
73+
}
74+
75+
func jsonValueToVibeValue(val any) (Value, error) {
76+
switch v := val.(type) {
77+
case nil:
78+
return NewNil(), nil
79+
case bool:
80+
return NewBool(v), nil
81+
case string:
82+
return NewString(v), nil
83+
case json.Number:
84+
if i, err := v.Int64(); err == nil {
85+
return NewInt(i), nil
86+
}
87+
f, err := v.Float64()
88+
if err != nil {
89+
return NewNil(), fmt.Errorf("JSON.parse invalid number %q", v.String())
90+
}
91+
return NewFloat(f), nil
92+
case float64:
93+
return NewFloat(v), nil
94+
case []any:
95+
arr := make([]Value, len(v))
96+
for i, item := range v {
97+
converted, err := jsonValueToVibeValue(item)
98+
if err != nil {
99+
return NewNil(), err
100+
}
101+
arr[i] = converted
102+
}
103+
return NewArray(arr), nil
104+
case map[string]any:
105+
obj := make(map[string]Value, len(v))
106+
for key, item := range v {
107+
converted, err := jsonValueToVibeValue(item)
108+
if err != nil {
109+
return NewNil(), err
110+
}
111+
obj[key] = converted
112+
}
113+
return NewHash(obj), nil
114+
default:
115+
return NewNil(), fmt.Errorf("JSON.parse unsupported value type %T", val)
116+
}
117+
}
118+
119+
func vibeValueToJSONValue(val Value, state *jsonStringifyState) (any, error) {
120+
switch val.Kind() {
121+
case KindNil:
122+
return nil, nil
123+
case KindBool:
124+
return val.Bool(), nil
125+
case KindInt:
126+
return val.Int(), nil
127+
case KindFloat:
128+
return val.Float(), nil
129+
case KindString, KindSymbol:
130+
return val.String(), nil
131+
case KindArray:
132+
arr := val.Array()
133+
id := reflect.ValueOf(arr).Pointer()
134+
if id != 0 {
135+
if _, seen := state.seenArrays[id]; seen {
136+
return nil, fmt.Errorf("JSON.stringify does not support cyclic arrays")
137+
}
138+
state.seenArrays[id] = struct{}{}
139+
defer delete(state.seenArrays, id)
140+
}
141+
142+
out := make([]any, len(arr))
143+
for i, item := range arr {
144+
converted, err := vibeValueToJSONValue(item, state)
145+
if err != nil {
146+
return nil, err
147+
}
148+
out[i] = converted
149+
}
150+
return out, nil
151+
case KindHash, KindObject:
152+
hash := val.Hash()
153+
id := reflect.ValueOf(hash).Pointer()
154+
if id != 0 {
155+
if _, seen := state.seenHashes[id]; seen {
156+
return nil, fmt.Errorf("JSON.stringify does not support cyclic objects")
157+
}
158+
state.seenHashes[id] = struct{}{}
159+
defer delete(state.seenHashes, id)
160+
}
161+
162+
out := make(map[string]any, len(hash))
163+
for key, item := range hash {
164+
converted, err := vibeValueToJSONValue(item, state)
165+
if err != nil {
166+
return nil, err
167+
}
168+
out[key] = converted
169+
}
170+
return out, nil
171+
default:
172+
return nil, fmt.Errorf("JSON.stringify unsupported value type %s", val.Kind())
173+
}
174+
}

vibes/builtins_json_regex.go

Lines changed: 0 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -1,180 +1,11 @@
11
package vibes
22

33
import (
4-
"encoding/json"
54
"fmt"
6-
"io"
7-
"reflect"
85
"regexp"
9-
"strings"
106
"unicode/utf8"
117
)
128

13-
func builtinJSONParse(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
14-
if len(args) != 1 || args[0].Kind() != KindString {
15-
return NewNil(), fmt.Errorf("JSON.parse expects a single JSON string argument")
16-
}
17-
if len(kwargs) > 0 {
18-
return NewNil(), fmt.Errorf("JSON.parse does not accept keyword arguments")
19-
}
20-
if !block.IsNil() {
21-
return NewNil(), fmt.Errorf("JSON.parse does not accept blocks")
22-
}
23-
24-
raw := args[0].String()
25-
if len(raw) > maxJSONPayloadBytes {
26-
return NewNil(), fmt.Errorf("JSON.parse input exceeds limit %d bytes", maxJSONPayloadBytes)
27-
}
28-
29-
decoder := json.NewDecoder(strings.NewReader(raw))
30-
decoder.UseNumber()
31-
32-
var decoded any
33-
if err := decoder.Decode(&decoded); err != nil {
34-
return NewNil(), fmt.Errorf("JSON.parse invalid JSON: %v", err)
35-
}
36-
if err := decoder.Decode(&struct{}{}); err != io.EOF {
37-
return NewNil(), fmt.Errorf("JSON.parse invalid JSON: trailing data")
38-
}
39-
40-
value, err := jsonValueToVibeValue(decoded)
41-
if err != nil {
42-
return NewNil(), err
43-
}
44-
return value, nil
45-
}
46-
47-
func builtinJSONStringify(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
48-
if len(args) != 1 {
49-
return NewNil(), fmt.Errorf("JSON.stringify expects a single value argument")
50-
}
51-
if len(kwargs) > 0 {
52-
return NewNil(), fmt.Errorf("JSON.stringify does not accept keyword arguments")
53-
}
54-
if !block.IsNil() {
55-
return NewNil(), fmt.Errorf("JSON.stringify does not accept blocks")
56-
}
57-
58-
state := &jsonStringifyState{
59-
seenArrays: map[uintptr]struct{}{},
60-
seenHashes: map[uintptr]struct{}{},
61-
}
62-
encoded, err := vibeValueToJSONValue(args[0], state)
63-
if err != nil {
64-
return NewNil(), err
65-
}
66-
67-
payload, err := json.Marshal(encoded)
68-
if err != nil {
69-
return NewNil(), fmt.Errorf("JSON.stringify failed: %v", err)
70-
}
71-
if len(payload) > maxJSONPayloadBytes {
72-
return NewNil(), fmt.Errorf("JSON.stringify output exceeds limit %d bytes", maxJSONPayloadBytes)
73-
}
74-
return NewString(string(payload)), nil
75-
}
76-
77-
func jsonValueToVibeValue(val any) (Value, error) {
78-
switch v := val.(type) {
79-
case nil:
80-
return NewNil(), nil
81-
case bool:
82-
return NewBool(v), nil
83-
case string:
84-
return NewString(v), nil
85-
case json.Number:
86-
if i, err := v.Int64(); err == nil {
87-
return NewInt(i), nil
88-
}
89-
f, err := v.Float64()
90-
if err != nil {
91-
return NewNil(), fmt.Errorf("JSON.parse invalid number %q", v.String())
92-
}
93-
return NewFloat(f), nil
94-
case float64:
95-
return NewFloat(v), nil
96-
case []any:
97-
arr := make([]Value, len(v))
98-
for i, item := range v {
99-
converted, err := jsonValueToVibeValue(item)
100-
if err != nil {
101-
return NewNil(), err
102-
}
103-
arr[i] = converted
104-
}
105-
return NewArray(arr), nil
106-
case map[string]any:
107-
obj := make(map[string]Value, len(v))
108-
for key, item := range v {
109-
converted, err := jsonValueToVibeValue(item)
110-
if err != nil {
111-
return NewNil(), err
112-
}
113-
obj[key] = converted
114-
}
115-
return NewHash(obj), nil
116-
default:
117-
return NewNil(), fmt.Errorf("JSON.parse unsupported value type %T", val)
118-
}
119-
}
120-
121-
func vibeValueToJSONValue(val Value, state *jsonStringifyState) (any, error) {
122-
switch val.Kind() {
123-
case KindNil:
124-
return nil, nil
125-
case KindBool:
126-
return val.Bool(), nil
127-
case KindInt:
128-
return val.Int(), nil
129-
case KindFloat:
130-
return val.Float(), nil
131-
case KindString, KindSymbol:
132-
return val.String(), nil
133-
case KindArray:
134-
arr := val.Array()
135-
id := reflect.ValueOf(arr).Pointer()
136-
if id != 0 {
137-
if _, seen := state.seenArrays[id]; seen {
138-
return nil, fmt.Errorf("JSON.stringify does not support cyclic arrays")
139-
}
140-
state.seenArrays[id] = struct{}{}
141-
defer delete(state.seenArrays, id)
142-
}
143-
144-
out := make([]any, len(arr))
145-
for i, item := range arr {
146-
converted, err := vibeValueToJSONValue(item, state)
147-
if err != nil {
148-
return nil, err
149-
}
150-
out[i] = converted
151-
}
152-
return out, nil
153-
case KindHash, KindObject:
154-
hash := val.Hash()
155-
id := reflect.ValueOf(hash).Pointer()
156-
if id != 0 {
157-
if _, seen := state.seenHashes[id]; seen {
158-
return nil, fmt.Errorf("JSON.stringify does not support cyclic objects")
159-
}
160-
state.seenHashes[id] = struct{}{}
161-
defer delete(state.seenHashes, id)
162-
}
163-
164-
out := make(map[string]any, len(hash))
165-
for key, item := range hash {
166-
converted, err := vibeValueToJSONValue(item, state)
167-
if err != nil {
168-
return nil, err
169-
}
170-
out[key] = converted
171-
}
172-
return out, nil
173-
default:
174-
return nil, fmt.Errorf("JSON.stringify unsupported value type %s", val.Kind())
175-
}
176-
}
177-
1789
func builtinRegexMatch(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
17910
if len(args) != 2 {
18011
return NewNil(), fmt.Errorf("Regex.match expects pattern and text")

0 commit comments

Comments
 (0)