janet_sandbox 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/janet_sandbox/dsls/code_inspector.janet +283 -0
- data/lib/janet_sandbox/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5adec66ce21c7b331ee3909c772e72d400344d3290a3476dbcf227244a5d0ba1
|
|
4
|
+
data.tar.gz: c7b59916442200fe158320af167ec5eca20bc5e00b8eed23d9fd48520178ecad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 760a3cdf9d5c816f97b9eb1b537863a2ddbf0efe3d53c57ae285afadc15f1b50616d17fab7e022f70eccc3645a478f688eac8acfcace1f51e5f3897c6174cefa
|
|
7
|
+
data.tar.gz: 957146cdd157de22c3f5dda4f4ac7bfdda7782efe52962dcb32d07bdacecfe95b6d49643eaedd154b6c1b70f81735b4672c038b2e7331151294ffaa54090b36a
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# Code Inspector DSL
|
|
2
|
+
#
|
|
3
|
+
# Provides functions for parsing and inspecting Janet code structure
|
|
4
|
+
# without executing it. Useful for building visual logic editors,
|
|
5
|
+
# syntax validators, and code analysis tools.
|
|
6
|
+
# This also provides a way to take that tree and turn it back into source text.
|
|
7
|
+
#
|
|
8
|
+
# Register in your application:
|
|
9
|
+
#
|
|
10
|
+
# JanetSandbox.configure do |config|
|
|
11
|
+
# config.register_dsl(:code_inspector, JanetSandbox::BuiltinDsls::CODE_INSPECTOR)
|
|
12
|
+
# config.enable_dsl(:code_inspector)
|
|
13
|
+
# end
|
|
14
|
+
|
|
15
|
+
(defn- node-type [node]
|
|
16
|
+
"Determine the type string for a Janet value"
|
|
17
|
+
(cond
|
|
18
|
+
(tuple? node) (if (= (tuple/type node) :brackets) "array" "call")
|
|
19
|
+
(array? node) "array"
|
|
20
|
+
(struct? node) "struct"
|
|
21
|
+
(table? node) "table"
|
|
22
|
+
(symbol? node) "symbol"
|
|
23
|
+
(keyword? node) "keyword"
|
|
24
|
+
(number? node) "number"
|
|
25
|
+
(string? node) "string"
|
|
26
|
+
(boolean? node) "boolean"
|
|
27
|
+
(nil? node) "nil"
|
|
28
|
+
(fiber? node) "fiber"
|
|
29
|
+
(function? node) "function"
|
|
30
|
+
(cfunction? node) "cfunction"
|
|
31
|
+
(buffer? node) "buffer"
|
|
32
|
+
"unknown"))
|
|
33
|
+
|
|
34
|
+
(defn to-tree [node]
|
|
35
|
+
"Convert a Janet value into a structured tree representation.
|
|
36
|
+
|
|
37
|
+
Returns a table with :type and type-specific fields:
|
|
38
|
+
- call: {:type \"call\" :op <string> :args [<children>]}
|
|
39
|
+
- array: {:type \"array\" :items [<children>]}
|
|
40
|
+
- struct/table: {:type \"struct\"|\"table\" :pairs [{:key <tree> :value <tree>} ...]}
|
|
41
|
+
- symbol: {:type \"symbol\" :name <string>}
|
|
42
|
+
- keyword: {:type \"keyword\" :name <string>}
|
|
43
|
+
- number: {:type \"number\" :value <number>}
|
|
44
|
+
- string: {:type \"string\" :value <string>}
|
|
45
|
+
- boolean: {:type \"boolean\" :value <boolean>}
|
|
46
|
+
- nil: {:type \"nil\"}
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
(to-tree (parse \"(if (> x 3) (foo) (bar))\"))
|
|
50
|
+
# => {:type \"call\" :op \"if\" :args [...]}"
|
|
51
|
+
(def t (node-type node))
|
|
52
|
+
(case t
|
|
53
|
+
"call"
|
|
54
|
+
{:type "call"
|
|
55
|
+
:op (string (first node))
|
|
56
|
+
:args (map to-tree (tuple/slice node 1))}
|
|
57
|
+
|
|
58
|
+
"array"
|
|
59
|
+
{:type "array"
|
|
60
|
+
:items (map to-tree node)}
|
|
61
|
+
|
|
62
|
+
"struct"
|
|
63
|
+
{:type "struct"
|
|
64
|
+
:pairs (seq [[k v] :pairs node]
|
|
65
|
+
{:key (to-tree k) :value (to-tree v)})}
|
|
66
|
+
|
|
67
|
+
"table"
|
|
68
|
+
{:type "table"
|
|
69
|
+
:pairs (seq [[k v] :pairs node]
|
|
70
|
+
{:key (to-tree k) :value (to-tree v)})}
|
|
71
|
+
|
|
72
|
+
"symbol"
|
|
73
|
+
{:type "symbol" :name (string node)}
|
|
74
|
+
|
|
75
|
+
"keyword"
|
|
76
|
+
{:type "keyword" :name (string node)}
|
|
77
|
+
|
|
78
|
+
"number"
|
|
79
|
+
{:type "number" :value node}
|
|
80
|
+
|
|
81
|
+
"string"
|
|
82
|
+
{:type "string" :value node}
|
|
83
|
+
|
|
84
|
+
"boolean"
|
|
85
|
+
{:type "boolean" :value node}
|
|
86
|
+
|
|
87
|
+
"nil"
|
|
88
|
+
{:type "nil"}
|
|
89
|
+
|
|
90
|
+
# fallback
|
|
91
|
+
{:type t :value (string node)}))
|
|
92
|
+
|
|
93
|
+
(defn parse-to-tree [code-string]
|
|
94
|
+
"Parse a Janet code string and return its tree representation.
|
|
95
|
+
|
|
96
|
+
Example:
|
|
97
|
+
(parse-to-tree \"(if (> x 3) (some-function) (other-function))\")
|
|
98
|
+
# => {:type \"call\" :op \"if\" :args [
|
|
99
|
+
# {:type \"call\" :op \">\" :args [
|
|
100
|
+
# {:type \"symbol\" :name \"x\"}
|
|
101
|
+
# {:type \"number\" :value 3}]}
|
|
102
|
+
# {:type \"call\" :op \"some-function\" :args []}
|
|
103
|
+
# {:type \"call\" :op \"other-function\" :args []}]}"
|
|
104
|
+
(to-tree (parse code-string)))
|
|
105
|
+
|
|
106
|
+
(defn parse-all-to-tree [code-string]
|
|
107
|
+
"Parse a Janet code string containing multiple expressions
|
|
108
|
+
and return an array of tree representations.
|
|
109
|
+
|
|
110
|
+
Example:
|
|
111
|
+
(parse-all-to-tree \"(def x 1) (+ x 2)\")
|
|
112
|
+
# => [{:type \"call\" :op \"def\" ...} {:type \"call\" :op \"+\" ...}]"
|
|
113
|
+
(def p (parser/new))
|
|
114
|
+
(parser/consume p code-string)
|
|
115
|
+
(def results @[])
|
|
116
|
+
(while (parser/has-more p)
|
|
117
|
+
(array/push results (to-tree (parser/produce p))))
|
|
118
|
+
results)
|
|
119
|
+
|
|
120
|
+
(defn extract-symbols [node]
|
|
121
|
+
"Extract all symbol names referenced in a parsed expression.
|
|
122
|
+
Returns an array of unique symbol name strings.
|
|
123
|
+
|
|
124
|
+
Example:
|
|
125
|
+
(extract-symbols (parse \"(if (> x y) (foo x) (bar y))\"))
|
|
126
|
+
# => [\"if\" \">\" \"x\" \"y\" \"foo\" \"bar\"]"
|
|
127
|
+
(def seen @{})
|
|
128
|
+
(def results @[])
|
|
129
|
+
|
|
130
|
+
(defn walk [n]
|
|
131
|
+
(cond
|
|
132
|
+
(symbol? n)
|
|
133
|
+
(let [name (string n)]
|
|
134
|
+
(unless (seen name)
|
|
135
|
+
(put seen name true)
|
|
136
|
+
(array/push results name)))
|
|
137
|
+
|
|
138
|
+
(tuple? n)
|
|
139
|
+
(each child n (walk child))
|
|
140
|
+
|
|
141
|
+
(array? n)
|
|
142
|
+
(each child n (walk child))
|
|
143
|
+
|
|
144
|
+
(or (struct? n) (table? n))
|
|
145
|
+
(eachp [k v] n
|
|
146
|
+
(walk k)
|
|
147
|
+
(walk v))))
|
|
148
|
+
|
|
149
|
+
(walk node)
|
|
150
|
+
results)
|
|
151
|
+
|
|
152
|
+
(defn extract-calls [node]
|
|
153
|
+
"Extract all function/macro call names from a parsed expression.
|
|
154
|
+
Returns an array of unique call names (first element of each parentheses tuple).
|
|
155
|
+
Does not include bracket tuples (e.g., parameter lists like [x y]).
|
|
156
|
+
|
|
157
|
+
Example:
|
|
158
|
+
(extract-calls (parse \"(if (> x 3) (foo) (bar))\"))
|
|
159
|
+
# => [\"if\" \">\" \"foo\" \"bar\"]"
|
|
160
|
+
(def seen @{})
|
|
161
|
+
(def results @[])
|
|
162
|
+
|
|
163
|
+
(defn walk [n]
|
|
164
|
+
(when (tuple? n)
|
|
165
|
+
# Only process parentheses tuples (actual calls), not bracket tuples
|
|
166
|
+
(when (= (tuple/type n) :parens)
|
|
167
|
+
(when (and (> (length n) 0) (symbol? (first n)))
|
|
168
|
+
(let [name (string (first n))]
|
|
169
|
+
(unless (seen name)
|
|
170
|
+
(put seen name true)
|
|
171
|
+
(array/push results name)))))
|
|
172
|
+
(each child n (walk child))))
|
|
173
|
+
|
|
174
|
+
(walk node)
|
|
175
|
+
results)
|
|
176
|
+
|
|
177
|
+
(defn- escape-string [s]
|
|
178
|
+
"Escape a string for Janet source output"
|
|
179
|
+
(def buf @"")
|
|
180
|
+
(each byte s
|
|
181
|
+
(case byte
|
|
182
|
+
(chr "\"") (buffer/push buf "\\\"")
|
|
183
|
+
(chr "\\") (buffer/push buf "\\\\")
|
|
184
|
+
(chr "\n") (buffer/push buf "\\n")
|
|
185
|
+
(chr "\r") (buffer/push buf "\\r")
|
|
186
|
+
(chr "\t") (buffer/push buf "\\t")
|
|
187
|
+
(buffer/push buf byte)))
|
|
188
|
+
(string buf))
|
|
189
|
+
|
|
190
|
+
(defn tree-to-source [tree]
|
|
191
|
+
"Convert a tree representation back to Janet source code.
|
|
192
|
+
This is the inverse of parse-to-tree.
|
|
193
|
+
|
|
194
|
+
Example:
|
|
195
|
+
(tree-to-source {:type \"call\" :op \"+\" :args [
|
|
196
|
+
{:type \"number\" :value 1}
|
|
197
|
+
{:type \"number\" :value 2}]})
|
|
198
|
+
# => \"(+ 1 2)\"
|
|
199
|
+
|
|
200
|
+
(def tree (parse-to-tree \"(if (> x 3) (foo) (bar))\"))
|
|
201
|
+
(tree-to-source tree)
|
|
202
|
+
# => \"(if (> x 3) (foo) (bar))\""
|
|
203
|
+
(case (tree :type)
|
|
204
|
+
"call"
|
|
205
|
+
(let [op (tree :op)
|
|
206
|
+
args (tree :args)]
|
|
207
|
+
(if (empty? args)
|
|
208
|
+
(string "(" op ")")
|
|
209
|
+
(string "(" op " " (string/join (map tree-to-source args) " ") ")")))
|
|
210
|
+
|
|
211
|
+
"array"
|
|
212
|
+
(let [items (tree :items)]
|
|
213
|
+
(if (empty? items)
|
|
214
|
+
"[]"
|
|
215
|
+
(string "[" (string/join (map tree-to-source items) " ") "]")))
|
|
216
|
+
|
|
217
|
+
"struct"
|
|
218
|
+
(let [pairs (tree :pairs)]
|
|
219
|
+
(if (empty? pairs)
|
|
220
|
+
"{}"
|
|
221
|
+
(string "{"
|
|
222
|
+
(string/join
|
|
223
|
+
(map (fn [p] (string (tree-to-source (p :key)) " " (tree-to-source (p :value))))
|
|
224
|
+
pairs)
|
|
225
|
+
" ")
|
|
226
|
+
"}")))
|
|
227
|
+
|
|
228
|
+
"table"
|
|
229
|
+
(let [pairs (tree :pairs)]
|
|
230
|
+
(if (empty? pairs)
|
|
231
|
+
"@{}"
|
|
232
|
+
(string "@{"
|
|
233
|
+
(string/join
|
|
234
|
+
(map (fn [p] (string (tree-to-source (p :key)) " " (tree-to-source (p :value))))
|
|
235
|
+
pairs)
|
|
236
|
+
" ")
|
|
237
|
+
"}")))
|
|
238
|
+
|
|
239
|
+
"symbol"
|
|
240
|
+
(tree :name)
|
|
241
|
+
|
|
242
|
+
"keyword"
|
|
243
|
+
(string ":" (tree :name))
|
|
244
|
+
|
|
245
|
+
"number"
|
|
246
|
+
(string (tree :value))
|
|
247
|
+
|
|
248
|
+
"string"
|
|
249
|
+
(string "\"" (escape-string (tree :value)) "\"")
|
|
250
|
+
|
|
251
|
+
"boolean"
|
|
252
|
+
(if (tree :value) "true" "false")
|
|
253
|
+
|
|
254
|
+
"nil"
|
|
255
|
+
"nil"
|
|
256
|
+
|
|
257
|
+
# fallback for unknown types
|
|
258
|
+
(string "<unknown:" (tree :type) ">")))
|
|
259
|
+
|
|
260
|
+
(defn validate-syntax [code-string]
|
|
261
|
+
"Check if a code string is syntactically valid Janet.
|
|
262
|
+
Returns {:valid true} on success, or {:valid false :error <message>} on failure.
|
|
263
|
+
|
|
264
|
+
Example:
|
|
265
|
+
(validate-syntax \"(+ 1 2)\") # => {:valid true}
|
|
266
|
+
(validate-syntax \"(+ 1 2\") # => {:valid false :error \"...\"}"
|
|
267
|
+
(def p (parser/new))
|
|
268
|
+
(def [ok err] (protect (parser/consume p code-string)))
|
|
269
|
+
(cond
|
|
270
|
+
# Parse error during consume
|
|
271
|
+
(not ok)
|
|
272
|
+
{:valid false :error (string err)}
|
|
273
|
+
|
|
274
|
+
# Check for parser errors
|
|
275
|
+
(parser/error p)
|
|
276
|
+
{:valid false :error (parser/error p)}
|
|
277
|
+
|
|
278
|
+
# Check for incomplete input (unclosed parens, strings, etc.)
|
|
279
|
+
(not= (parser/status p) :root)
|
|
280
|
+
{:valid false :error (string "unexpected end of input, parser status: " (parser/status p))}
|
|
281
|
+
|
|
282
|
+
# All good
|
|
283
|
+
{:valid true}))
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: janet_sandbox
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michael Harris
|
|
@@ -91,6 +91,7 @@ files:
|
|
|
91
91
|
- lib/janet_sandbox.rb
|
|
92
92
|
- lib/janet_sandbox/builtin_dsls.rb
|
|
93
93
|
- lib/janet_sandbox/configuration.rb
|
|
94
|
+
- lib/janet_sandbox/dsls/code_inspector.janet
|
|
94
95
|
- lib/janet_sandbox/engine.rb
|
|
95
96
|
- lib/janet_sandbox/errors.rb
|
|
96
97
|
- lib/janet_sandbox/rails/railtie.rb
|