parsanol 3.0.0

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.

Potentially problematic release.


This version of parsanol might be problematic. Click here for more details.

Files changed (336) hide show
  1. checksums.yaml +7 -0
  2. data/HISTORY.txt +25 -0
  3. data/LICENSE +23 -0
  4. data/README.adoc +643 -0
  5. data/Rakefile +189 -0
  6. data/example/balanced-parens/basic.rb +42 -0
  7. data/example/balanced-parens/basic.rb.md +86 -0
  8. data/example/balanced-parens/parens.rb +42 -0
  9. data/example/balanced-parens/ruby_transform.rb +162 -0
  10. data/example/big.erb +73 -0
  11. data/example/boolean-algebra/basic.rb +70 -0
  12. data/example/boolean-algebra/basic.rb.md +108 -0
  13. data/example/boolean-algebra/ruby_transform.rb +263 -0
  14. data/example/calculator/basic.rb +153 -0
  15. data/example/calculator/basic.rb.md +120 -0
  16. data/example/calculator/pattern.rb +153 -0
  17. data/example/calculator/ruby_transform.rb +156 -0
  18. data/example/calculator/ruby_transform.rb.md +32 -0
  19. data/example/calculator/serialized.rb +257 -0
  20. data/example/calculator/serialized.rb.md +32 -0
  21. data/example/calculator/transform.rb +153 -0
  22. data/example/calculator/zero_copy.rb +269 -0
  23. data/example/calculator/zero_copy.rb.md +36 -0
  24. data/example/capture/basic.rb +49 -0
  25. data/example/capture/basic.rb.md +106 -0
  26. data/example/capture/example.json +39 -0
  27. data/example/comments/basic.rb +35 -0
  28. data/example/comments/basic.rb.md +110 -0
  29. data/example/csv/ruby_transform.rb +148 -0
  30. data/example/csv/ruby_transform.rb.md +131 -0
  31. data/example/csv/serialized.rb +201 -0
  32. data/example/csv/serialized.rb.md +31 -0
  33. data/example/csv/zero_copy.rb +276 -0
  34. data/example/csv/zero_copy.rb.md +36 -0
  35. data/example/custom_atoms/indent_atom.rb +79 -0
  36. data/example/deepest-errors/basic.rb +131 -0
  37. data/example/deepest-errors/basic.rb.md +152 -0
  38. data/example/documentation/basic.rb +18 -0
  39. data/example/documentation/basic.rb.md +97 -0
  40. data/example/email/basic.rb +55 -0
  41. data/example/email/basic.rb.md +102 -0
  42. data/example/email/ruby_transform.rb +106 -0
  43. data/example/empty/basic.rb +13 -0
  44. data/example/empty/basic.rb.md +73 -0
  45. data/example/empty/example.json +38 -0
  46. data/example/erb/basic.rb +47 -0
  47. data/example/erb/basic.rb.md +103 -0
  48. data/example/erb/optimized.rb +42 -0
  49. data/example/error-reporting/basic.rb +132 -0
  50. data/example/error-reporting/basic.rb.md +122 -0
  51. data/example/expression-evaluator/basic.rb +284 -0
  52. data/example/expression-evaluator/basic.rb.md +138 -0
  53. data/example/ini/basic.rb +154 -0
  54. data/example/ini/basic.rb.md +129 -0
  55. data/example/ini/ruby_transform.rb +154 -0
  56. data/example/ip-address/basic.rb +125 -0
  57. data/example/ip-address/basic.rb.md +139 -0
  58. data/example/iso-6709/basic.rb +231 -0
  59. data/example/iso-6709/basic.rb.md +143 -0
  60. data/example/iso-8601/basic.rb +275 -0
  61. data/example/iso-8601/basic.rb.md +149 -0
  62. data/example/json/basic.rb +128 -0
  63. data/example/json/basic.rb.md +121 -0
  64. data/example/json/pattern.rb +128 -0
  65. data/example/json/ruby_transform.rb +200 -0
  66. data/example/json/ruby_transform.rb.md +32 -0
  67. data/example/json/serialized.rb +233 -0
  68. data/example/json/serialized.rb.md +31 -0
  69. data/example/json/transform.rb +128 -0
  70. data/example/json/zero_copy.rb +316 -0
  71. data/example/json/zero_copy.rb.md +36 -0
  72. data/example/local/basic.rb +34 -0
  73. data/example/local/basic.rb.md +91 -0
  74. data/example/local/example.json +38 -0
  75. data/example/markdown/basic.rb +287 -0
  76. data/example/markdown/basic.rb.md +160 -0
  77. data/example/markup/basic.rb +173 -0
  78. data/example/markup/basic.rb.md +118 -0
  79. data/example/mathn/basic.rb +47 -0
  80. data/example/mathn/basic.rb.md +96 -0
  81. data/example/mathn/example.json +39 -0
  82. data/example/minilisp/basic.rb +94 -0
  83. data/example/minilisp/basic.rb.md +133 -0
  84. data/example/modularity/basic.rb +47 -0
  85. data/example/modularity/basic.rb.md +152 -0
  86. data/example/nested-errors/basic.rb +132 -0
  87. data/example/nested-errors/basic.rb.md +157 -0
  88. data/example/output/boolean_algebra.out +4 -0
  89. data/example/output/calc.out +1 -0
  90. data/example/output/capture.out +3 -0
  91. data/example/output/comments.out +8 -0
  92. data/example/output/deepest_errors.out +54 -0
  93. data/example/output/documentation.err +4 -0
  94. data/example/output/documentation.out +1 -0
  95. data/example/output/email_parser.out +2 -0
  96. data/example/output/empty.err +1 -0
  97. data/example/output/erb.out +7 -0
  98. data/example/output/ignore.out +1 -0
  99. data/example/output/ignore_whitespace.out +1 -0
  100. data/example/output/ip_address.out +9 -0
  101. data/example/output/json.out +5 -0
  102. data/example/output/local.out +3 -0
  103. data/example/output/mathn.out +4 -0
  104. data/example/output/minilisp.out +5 -0
  105. data/example/output/modularity.out +0 -0
  106. data/example/output/nested_errors.out +54 -0
  107. data/example/output/optimized_erb.out +1 -0
  108. data/example/output/parens.out +8 -0
  109. data/example/output/prec_calc.out +5 -0
  110. data/example/output/readme.out +1 -0
  111. data/example/output/scopes.out +1 -0
  112. data/example/output/seasons.out +28 -0
  113. data/example/output/sentence.out +1 -0
  114. data/example/output/simple_xml.out +2 -0
  115. data/example/output/string_parser.out +3 -0
  116. data/example/prec-calc/basic.rb +71 -0
  117. data/example/prec-calc/basic.rb.md +114 -0
  118. data/example/readme/basic.rb +30 -0
  119. data/example/readme/basic.rb.md +80 -0
  120. data/example/scopes/basic.rb +15 -0
  121. data/example/scopes/basic.rb.md +73 -0
  122. data/example/scopes/example.json +38 -0
  123. data/example/seasons/basic.rb +46 -0
  124. data/example/seasons/basic.rb.md +117 -0
  125. data/example/seasons/example.json +40 -0
  126. data/example/sentence/basic.rb +36 -0
  127. data/example/sentence/basic.rb.md +81 -0
  128. data/example/sexp/ruby_transform.rb +180 -0
  129. data/example/sexp/ruby_transform.rb.md +143 -0
  130. data/example/simple-xml/basic.rb +54 -0
  131. data/example/simple-xml/basic.rb.md +125 -0
  132. data/example/simple.lit +3 -0
  133. data/example/string-literal/basic.rb +77 -0
  134. data/example/string-literal/basic.rb.md +128 -0
  135. data/example/test.lit +4 -0
  136. data/example/toml/basic.rb +226 -0
  137. data/example/toml/basic.rb.md +173 -0
  138. data/example/url/basic.rb +219 -0
  139. data/example/url/basic.rb.md +142 -0
  140. data/example/url/ruby_transform.rb +219 -0
  141. data/example/yaml/basic.rb +216 -0
  142. data/example/yaml/basic.rb.md +148 -0
  143. data/ext/parsanol_native/extconf.rb +4 -0
  144. data/lib/parsanol/accelerator/application.rb +62 -0
  145. data/lib/parsanol/accelerator/engine.rb +112 -0
  146. data/lib/parsanol/accelerator.rb +162 -0
  147. data/lib/parsanol/ast_visitor.rb +122 -0
  148. data/lib/parsanol/atoms/alternative.rb +97 -0
  149. data/lib/parsanol/atoms/base.rb +214 -0
  150. data/lib/parsanol/atoms/can_flatten.rb +192 -0
  151. data/lib/parsanol/atoms/capture.rb +41 -0
  152. data/lib/parsanol/atoms/context.rb +351 -0
  153. data/lib/parsanol/atoms/context_optimized.rb +42 -0
  154. data/lib/parsanol/atoms/custom.rb +110 -0
  155. data/lib/parsanol/atoms/cut.rb +62 -0
  156. data/lib/parsanol/atoms/dsl.rb +130 -0
  157. data/lib/parsanol/atoms/dynamic.rb +33 -0
  158. data/lib/parsanol/atoms/entity.rb +55 -0
  159. data/lib/parsanol/atoms/ignored.rb +28 -0
  160. data/lib/parsanol/atoms/infix.rb +121 -0
  161. data/lib/parsanol/atoms/lookahead.rb +64 -0
  162. data/lib/parsanol/atoms/named.rb +50 -0
  163. data/lib/parsanol/atoms/re.rb +61 -0
  164. data/lib/parsanol/atoms/repetition.rb +241 -0
  165. data/lib/parsanol/atoms/scope.rb +28 -0
  166. data/lib/parsanol/atoms/sequence.rb +157 -0
  167. data/lib/parsanol/atoms/str.rb +90 -0
  168. data/lib/parsanol/atoms/visitor.rb +91 -0
  169. data/lib/parsanol/atoms.rb +36 -0
  170. data/lib/parsanol/buffer.rb +130 -0
  171. data/lib/parsanol/builder_callbacks.rb +353 -0
  172. data/lib/parsanol/cause.rb +101 -0
  173. data/lib/parsanol/context.rb +23 -0
  174. data/lib/parsanol/convenience.rb +35 -0
  175. data/lib/parsanol/edit_tracker.rb +107 -0
  176. data/lib/parsanol/error_reporter/contextual.rb +122 -0
  177. data/lib/parsanol/error_reporter/deepest.rb +106 -0
  178. data/lib/parsanol/error_reporter/tree.rb +68 -0
  179. data/lib/parsanol/error_reporter.rb +98 -0
  180. data/lib/parsanol/export.rb +163 -0
  181. data/lib/parsanol/expression/treetop.rb +94 -0
  182. data/lib/parsanol/expression.rb +51 -0
  183. data/lib/parsanol/fast_mode.rb +145 -0
  184. data/lib/parsanol/first_set.rb +75 -0
  185. data/lib/parsanol/grammar_builder.rb +177 -0
  186. data/lib/parsanol/graphviz.rb +97 -0
  187. data/lib/parsanol/incremental_parser.rb +179 -0
  188. data/lib/parsanol/interval_tree.rb +215 -0
  189. data/lib/parsanol/lazy_result.rb +178 -0
  190. data/lib/parsanol/lexer.rb +146 -0
  191. data/lib/parsanol/native/parser.rb +630 -0
  192. data/lib/parsanol/native/serializer.rb +245 -0
  193. data/lib/parsanol/native/transformer.rb +438 -0
  194. data/lib/parsanol/native/types.rb +41 -0
  195. data/lib/parsanol/native.rb +217 -0
  196. data/lib/parsanol/optimizer.rb +86 -0
  197. data/lib/parsanol/optimizers/choice_optimizer.rb +78 -0
  198. data/lib/parsanol/optimizers/cut_inserter.rb +175 -0
  199. data/lib/parsanol/optimizers/lookahead_optimizer.rb +58 -0
  200. data/lib/parsanol/optimizers/quantifier_optimizer.rb +62 -0
  201. data/lib/parsanol/optimizers/sequence_optimizer.rb +97 -0
  202. data/lib/parsanol/options/ruby_transform.rb +109 -0
  203. data/lib/parsanol/options/serialized.rb +94 -0
  204. data/lib/parsanol/options/zero_copy.rb +130 -0
  205. data/lib/parsanol/options.rb +20 -0
  206. data/lib/parsanol/parallel.rb +133 -0
  207. data/lib/parsanol/parsanol_native.bundle +0 -0
  208. data/lib/parsanol/parser.rb +151 -0
  209. data/lib/parsanol/parslet.rb +148 -0
  210. data/lib/parsanol/parslet_native.bundle +0 -0
  211. data/lib/parsanol/pattern/binding.rb +49 -0
  212. data/lib/parsanol/pattern.rb +115 -0
  213. data/lib/parsanol/pool.rb +220 -0
  214. data/lib/parsanol/pools/array_pool.rb +75 -0
  215. data/lib/parsanol/pools/buffer_pool.rb +173 -0
  216. data/lib/parsanol/pools/position_pool.rb +92 -0
  217. data/lib/parsanol/pools/slice_pool.rb +64 -0
  218. data/lib/parsanol/position.rb +89 -0
  219. data/lib/parsanol/result.rb +44 -0
  220. data/lib/parsanol/result_builder.rb +208 -0
  221. data/lib/parsanol/result_stream.rb +262 -0
  222. data/lib/parsanol/rig/rspec.rb +52 -0
  223. data/lib/parsanol/rope.rb +78 -0
  224. data/lib/parsanol/scope.rb +42 -0
  225. data/lib/parsanol/slice.rb +172 -0
  226. data/lib/parsanol/source/line_cache.rb +99 -0
  227. data/lib/parsanol/source.rb +171 -0
  228. data/lib/parsanol/source_location.rb +164 -0
  229. data/lib/parsanol/streaming_parser.rb +124 -0
  230. data/lib/parsanol/string_view.rb +192 -0
  231. data/lib/parsanol/transform.rb +267 -0
  232. data/lib/parsanol/version.rb +5 -0
  233. data/lib/parsanol/wasm/README.md +80 -0
  234. data/lib/parsanol/wasm/package.json +51 -0
  235. data/lib/parsanol/wasm/parsanol.js +252 -0
  236. data/lib/parsanol/wasm/parslet.d.ts +129 -0
  237. data/lib/parsanol/wasm_parser.rb +239 -0
  238. data/lib/parsanol.rb +408 -0
  239. data/parsanol-ruby.gemspec +56 -0
  240. data/spec/acceptance/examples_spec.rb +96 -0
  241. data/spec/acceptance/infix_parser_spec.rb +145 -0
  242. data/spec/acceptance/mixing_parsers_spec.rb +74 -0
  243. data/spec/acceptance/regression_spec.rb +329 -0
  244. data/spec/acceptance/repetition_and_maybe_spec.rb +44 -0
  245. data/spec/acceptance/unconsumed_input_spec.rb +21 -0
  246. data/spec/benchmark/comparative/runner_spec.rb +105 -0
  247. data/spec/integration/array_pooling_spec.rb +193 -0
  248. data/spec/integration/buffer_allocation_spec.rb +324 -0
  249. data/spec/integration/position_pooling_spec.rb +184 -0
  250. data/spec/integration/result_builder_spec.rb +282 -0
  251. data/spec/integration/rope_stringview_integration_spec.rb +188 -0
  252. data/spec/integration/slice_pooling_spec.rb +63 -0
  253. data/spec/integration/string_view_integration_spec.rb +125 -0
  254. data/spec/lexer_spec.rb +231 -0
  255. data/spec/parsanol/atom_results_spec.rb +39 -0
  256. data/spec/parsanol/atoms/alternative_spec.rb +26 -0
  257. data/spec/parsanol/atoms/base_spec.rb +127 -0
  258. data/spec/parsanol/atoms/capture_spec.rb +21 -0
  259. data/spec/parsanol/atoms/combinations_spec.rb +5 -0
  260. data/spec/parsanol/atoms/custom_spec.rb +79 -0
  261. data/spec/parsanol/atoms/dsl_spec.rb +7 -0
  262. data/spec/parsanol/atoms/entity_spec.rb +77 -0
  263. data/spec/parsanol/atoms/ignored_spec.rb +15 -0
  264. data/spec/parsanol/atoms/infix_spec.rb +5 -0
  265. data/spec/parsanol/atoms/lookahead_spec.rb +22 -0
  266. data/spec/parsanol/atoms/named_spec.rb +4 -0
  267. data/spec/parsanol/atoms/re_spec.rb +14 -0
  268. data/spec/parsanol/atoms/repetition_spec.rb +24 -0
  269. data/spec/parsanol/atoms/scope_spec.rb +26 -0
  270. data/spec/parsanol/atoms/sequence_spec.rb +28 -0
  271. data/spec/parsanol/atoms/str_spec.rb +15 -0
  272. data/spec/parsanol/atoms/visitor_spec.rb +101 -0
  273. data/spec/parsanol/atoms_spec.rb +488 -0
  274. data/spec/parsanol/auto_optimize_spec.rb +334 -0
  275. data/spec/parsanol/buffer_spec.rb +219 -0
  276. data/spec/parsanol/builder_callbacks_spec.rb +377 -0
  277. data/spec/parsanol/choice_optimizer_spec.rb +231 -0
  278. data/spec/parsanol/convenience_spec.rb +54 -0
  279. data/spec/parsanol/cut_inserter_spec.rb +248 -0
  280. data/spec/parsanol/cut_spec.rb +66 -0
  281. data/spec/parsanol/edit_tracker_spec.rb +218 -0
  282. data/spec/parsanol/error_reporter/contextual_spec.rb +122 -0
  283. data/spec/parsanol/error_reporter/deepest_spec.rb +82 -0
  284. data/spec/parsanol/error_reporter/tree_spec.rb +7 -0
  285. data/spec/parsanol/export_spec.rb +67 -0
  286. data/spec/parsanol/expression/treetop_spec.rb +75 -0
  287. data/spec/parsanol/first_set_spec.rb +298 -0
  288. data/spec/parsanol/interval_tree_spec.rb +205 -0
  289. data/spec/parsanol/lazy_result_spec.rb +288 -0
  290. data/spec/parsanol/lookahead_optimizer_spec.rb +252 -0
  291. data/spec/parsanol/minilisp.citrus +29 -0
  292. data/spec/parsanol/minilisp.tt +29 -0
  293. data/spec/parsanol/optimizer_spec.rb +459 -0
  294. data/spec/parsanol/options/parslet_compat_spec.rb +166 -0
  295. data/spec/parsanol/options/ruby_transform_spec.rb +70 -0
  296. data/spec/parsanol/options/serialized_spec.rb +69 -0
  297. data/spec/parsanol/options/zero_copy_spec.rb +230 -0
  298. data/spec/parsanol/parser_spec.rb +36 -0
  299. data/spec/parsanol/parslet_spec.rb +38 -0
  300. data/spec/parsanol/pattern_spec.rb +272 -0
  301. data/spec/parsanol/pool_spec.rb +392 -0
  302. data/spec/parsanol/pools/array_pool_spec.rb +356 -0
  303. data/spec/parsanol/pools/buffer_pool_spec.rb +365 -0
  304. data/spec/parsanol/pools/position_pool_spec.rb +118 -0
  305. data/spec/parsanol/pools/slice_pool_spec.rb +262 -0
  306. data/spec/parsanol/position_spec.rb +14 -0
  307. data/spec/parsanol/result_builder_spec.rb +391 -0
  308. data/spec/parsanol/rig/rspec_spec.rb +54 -0
  309. data/spec/parsanol/rope_spec.rb +207 -0
  310. data/spec/parsanol/scope_spec.rb +45 -0
  311. data/spec/parsanol/slice_spec.rb +249 -0
  312. data/spec/parsanol/source/line_cache_spec.rb +74 -0
  313. data/spec/parsanol/source_spec.rb +207 -0
  314. data/spec/parsanol/string_view_spec.rb +345 -0
  315. data/spec/parsanol/transform/context_spec.rb +56 -0
  316. data/spec/parsanol/transform_spec.rb +183 -0
  317. data/spec/parsanol/tree_memoization_spec.rb +149 -0
  318. data/spec/parslet_compatibility/expressir_edge_cases_spec.rb +153 -0
  319. data/spec/parslet_compatibility/minimal_reproduction.rb +199 -0
  320. data/spec/parslet_compatibility_spec.rb +399 -0
  321. data/spec/parslet_imported/atom_spec.rb +93 -0
  322. data/spec/parslet_imported/combinator_spec.rb +161 -0
  323. data/spec/parslet_imported/spec_helper.rb +73 -0
  324. data/spec/performance/batch_parsing_benchmark.rb +129 -0
  325. data/spec/performance/complete_optimization_summary.rb +143 -0
  326. data/spec/performance/grammar_caching_analysis.rb +121 -0
  327. data/spec/performance/grammar_caching_benchmark.rb +80 -0
  328. data/spec/performance/native_benchmark_spec.rb +230 -0
  329. data/spec/performance/phase5_benchmark.rb +144 -0
  330. data/spec/performance/profiling_benchmark.rb +131 -0
  331. data/spec/performance/ruby_improvements_benchmark.rb +171 -0
  332. data/spec/performance_spec.rb +374 -0
  333. data/spec/spec_helper.rb +79 -0
  334. data/spec/support/opal.rb +8 -0
  335. data/spec/support/opal.rb.erb +14 -0
  336. metadata +485 -0
@@ -0,0 +1,200 @@
1
+ # JSON Parser Example - Ruby Transform: Ruby Transform (Parslet-Compatible)
2
+ #
3
+ # This example demonstrates Ruby Transform for parsing JSON:
4
+ # 1. Rust parser (parsanol-rs) does the fast parsing
5
+ # 2. Returns a generic tree (hash/array/string structure)
6
+ # 3. Ruby transform converts tree to Ruby objects
7
+ #
8
+ # This is the most flexible option and is 100% Parslet API compatible.
9
+
10
+ $:.unshift File.dirname(__FILE__) + "/../lib"
11
+
12
+ require 'parsanol'
13
+
14
+ # Step 1: Define the JSON parser grammar
15
+ class JsonParser < Parsanol::Parser
16
+ root :json
17
+
18
+ rule(:json) { space? >> value >> space? }
19
+
20
+ rule(:value) {
21
+ object |
22
+ array |
23
+ string |
24
+ number |
25
+ true_value |
26
+ false_value |
27
+ null_value
28
+ }
29
+
30
+ # Object: { "key": value, ... }
31
+ rule(:object) {
32
+ str('{') >> space? >>
33
+ (entry >> (comma >> entry).repeat).maybe.as(:object) >>
34
+ space? >> str('}')
35
+ }
36
+
37
+ rule(:entry) {
38
+ (string.as(:key) >> space? >> colon >> space? >> value.as(:val)).as(:entry)
39
+ }
40
+
41
+ # Array: [ value, ... ]
42
+ rule(:array) {
43
+ str('[') >> space? >>
44
+ (value >> (comma >> value).repeat).maybe.as(:array) >>
45
+ space? >> str(']')
46
+ }
47
+
48
+ # String: "..."
49
+ rule(:string) {
50
+ str('"') >> (
51
+ str('\\') >> any | str('"').absent? >> any
52
+ ).repeat.as(:string) >> str('"')
53
+ }
54
+
55
+ # Number: -?[0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)?
56
+ rule(:number) {
57
+ (
58
+ str('-').maybe >>
59
+ (str('0') | (match('[1-9]') >> digit.repeat)) >>
60
+ (str('.') >> digit.repeat(1)).maybe >>
61
+ (match('[eE]') >> (str('+') | str('-')).maybe >> digit.repeat(1)).maybe
62
+ ).as(:number)
63
+ }
64
+
65
+ # Literals
66
+ rule(:true_value) { str('true').as(:true) }
67
+ rule(:false_value) { str('false').as(:false) }
68
+ rule(:null_value) { str('null').as(:null) }
69
+
70
+ # Helpers
71
+ rule(:digit) { match('[0-9]') }
72
+ rule(:space) { match('\s').repeat(1) }
73
+ rule(:space?) { space.maybe }
74
+ rule(:comma) { space? >> str(',') >> space? }
75
+ rule(:colon) { str(':') }
76
+ end
77
+
78
+ # Step 2: Define the transform (Parslet-style)
79
+ class JsonTransform < Parsanol::Transform
80
+ # Entry helper class
81
+ class Entry < Struct.new(:key, :val); end
82
+
83
+ # Transform arrays
84
+ rule(array: subtree(:ar)) {
85
+ ar.is_a?(Array) ? ar : [ar]
86
+ }
87
+
88
+ # Transform objects
89
+ rule(object: subtree(:ob)) {
90
+ (ob.is_a?(Array) ? ob : [ob]).each_with_object({}) { |e, h| h[e.key] = e.val }
91
+ }
92
+
93
+ # Transform entries
94
+ rule(entry: { key: simple(:ke), val: simple(:va) }) {
95
+ Entry.new(ke, va)
96
+ }
97
+
98
+ # Transform strings
99
+ rule(string: simple(:st)) {
100
+ st.to_s
101
+ }
102
+
103
+ # Transform numbers
104
+ rule(number: simple(:nb)) {
105
+ str = nb.to_s
106
+ str.match?(/[eE.]/) ? Float(str) : Integer(str)
107
+ }
108
+
109
+ # Transform literals
110
+ rule(null: simple(:_nu)) { nil }
111
+ rule(true: simple(:_tr)) { true }
112
+ rule(false: simple(:_fa)) { false }
113
+ end
114
+
115
+ # Step 3: Parse and transform
116
+ def parse_json(input)
117
+ parser = JsonParser.new
118
+ transform = JsonTransform.new
119
+
120
+ # Ruby Transform: Parse in Rust, transform in Ruby
121
+ tree = parser.parse(input)
122
+ puts "Parse tree (first 500 chars): #{tree.inspect[0..500]}..."
123
+
124
+ result = transform.apply(tree)
125
+ puts "Result: #{result.inspect[0..200]}..."
126
+
127
+ result
128
+ end
129
+
130
+ # Example usage
131
+ if __FILE__ == $0
132
+ puts "=" * 60
133
+ puts "JSON Parser Example - Ruby Transform: Ruby Transform"
134
+ puts "=" * 60
135
+
136
+ test_cases = [
137
+ ['"hello"', "hello"],
138
+ ['42', 42],
139
+ ['3.14', 3.14],
140
+ ['true', true],
141
+ ['false', false],
142
+ ['null', nil],
143
+ ['[1, 2, 3]', [1, 2, 3]],
144
+ ['{"a": 1}', { "a" => 1 }],
145
+ ['{"name": "test", "value": 42}', { "name" => "test", "value" => 42 }],
146
+ ]
147
+
148
+ test_cases.each do |input, expected|
149
+ puts
150
+ puts "-" * 40
151
+ puts "Input: #{input}"
152
+ begin
153
+ result = parse_json(input)
154
+ status = result == expected ? "✓ PASS" : "✗ FAIL"
155
+ puts "Expected: #{expected.inspect}, Got: #{result.inspect} - #{status}"
156
+ rescue => e
157
+ puts "Error: #{e.message}"
158
+ puts e.backtrace.first(3).join("\n")
159
+ puts "✗ FAIL"
160
+ end
161
+ end
162
+
163
+ # Complex example
164
+ puts
165
+ puts "-" * 40
166
+ puts "Complex JSON example:"
167
+ complex_json = <<~JSON
168
+ {
169
+ "users": [
170
+ {"name": "Alice", "age": 30, "active": true},
171
+ {"name": "Bob", "age": 25, "active": false}
172
+ ],
173
+ "count": 2,
174
+ "metadata": {
175
+ "version": "1.0",
176
+ "tags": ["admin", "test"]
177
+ }
178
+ }
179
+ JSON
180
+
181
+ begin
182
+ result = parse_json(complex_json)
183
+ puts "Parsed successfully!"
184
+ puts "Users: #{result['users'].map { |u| u['name'] }.join(', ')}"
185
+ puts "Count: #{result['count']}"
186
+ puts "Version: #{result['metadata']['version']}"
187
+ puts "✓ PASS"
188
+ rescue => e
189
+ puts "Error: #{e.message}"
190
+ puts "✗ FAIL"
191
+ end
192
+
193
+ puts
194
+ puts "=" * 60
195
+ puts "Ruby Transform Benefits for JSON:"
196
+ puts "- Flexible: Can add custom transform logic"
197
+ puts "- Debuggable: Inspect tree before transform"
198
+ puts "- Compatible: Works with existing Parslet code"
199
+ puts "=" * 60
200
+ end
@@ -0,0 +1,32 @@
1
+ # JSON (Ruby Transform - Option A)
2
+
3
+ ## Purpose
4
+
5
+ This implementation demonstrates Parslet-compatible JSON parsing: Rust parses,
6
+ Ruby transforms into domain objects.
7
+
8
+ ## When to Use
9
+
10
+ - Migrating from Parslet
11
+ - Custom JSON processing logic
12
+ - Flexible transformation needs
13
+
14
+ ## Key Concepts
15
+
16
+ 1. **Rust Parsing**: Fast native parsing engine
17
+ 2. **Ruby Transform**: Familiar transformation API
18
+ 3. **Custom Output**: Any Ruby object structure
19
+
20
+ ## Running
21
+
22
+ ```bash
23
+ ruby example/json/ruby_transform.rb
24
+ ```
25
+
26
+ ## Output
27
+
28
+ ```
29
+ Input: {"key": "value"}
30
+ Parse tree: {object: [{string: "key", value: {string: "value"}}]}
31
+ Result: {"key" => "value"}
32
+ ```
@@ -0,0 +1,233 @@
1
+ # JSON Parser Example - Serialized: JSON Serialization
2
+ #
3
+ # This example demonstrates Serialized for parsing JSON:
4
+ # 1. Rust parser (parsanol-rs) does the parsing
5
+ # 2. Rust transform converts to typed structs
6
+ # 3. Result is serialized to JSON (meta!)
7
+ # 4. Ruby deserializes JSON to Ruby objects
8
+ #
9
+ # Note: Since JSON output is already JSON, Serialized essentially
10
+ # validates and normalizes the input JSON.
11
+
12
+ $:.unshift File.dirname(__FILE__) + "/../lib"
13
+
14
+ require 'parsanol'
15
+ require 'json'
16
+
17
+ # NOTE: This example requires the native extension to support parse_to_json
18
+ # which is planned but not yet implemented. This serves as an API preview.
19
+
20
+ # Step 1: Define the JSON parser grammar (same as Option A)
21
+ class JsonParser < Parsanol::Parser
22
+ root :json
23
+
24
+ rule(:json) { space? >> value >> space? }
25
+
26
+ rule(:value) {
27
+ object |
28
+ array |
29
+ string |
30
+ number |
31
+ true_value |
32
+ false_value |
33
+ null_value
34
+ }
35
+
36
+ rule(:object) {
37
+ str('{') >> space? >>
38
+ (entry >> (comma >> entry).repeat).maybe.as(:object) >>
39
+ space? >> str('}')
40
+ }
41
+
42
+ rule(:entry) {
43
+ (string.as(:key) >> space? >> colon >> space? >> value.as(:val)).as(:entry)
44
+ }
45
+
46
+ rule(:array) {
47
+ str('[') >> space? >>
48
+ (value >> (comma >> value).repeat).maybe.as(:array) >>
49
+ space? >> str(']')
50
+ }
51
+
52
+ rule(:string) {
53
+ str('"') >> (
54
+ str('\\') >> any | str('"').absent? >> any
55
+ ).repeat.as(:string) >> str('"')
56
+ }
57
+
58
+ rule(:number) {
59
+ (
60
+ str('-').maybe >>
61
+ (str('0') | (match('[1-9]') >> digit.repeat)) >>
62
+ (str('.') >> digit.repeat(1)).maybe >>
63
+ (match('[eE]') >> (str('+') | str('-')).maybe >> digit.repeat(1)).maybe
64
+ ).as(:number)
65
+ }
66
+
67
+ rule(:true_value) { str('true').as(:true) }
68
+ rule(:false_value) { str('false').as(:false) }
69
+ rule(:null_value) { str('null').as(:null) }
70
+
71
+ rule(:digit) { match('[0-9]') }
72
+ rule(:space) { match('\s').repeat(1) }
73
+ rule(:space?) { space.maybe }
74
+ rule(:comma) { space? >> str(',') >> space? }
75
+ rule(:colon) { str(':') }
76
+ end
77
+
78
+ # Step 2: Define Ruby classes for typed output (optional, for structured access)
79
+ class JsonValue; end
80
+
81
+ class JsonString < JsonValue
82
+ attr_reader :value
83
+ def initialize(value:) @value = value end
84
+ def to_ruby = @value
85
+ end
86
+
87
+ class JsonNumber < JsonValue
88
+ attr_reader :value
89
+ def initialize(value:) @value = value end
90
+ def to_ruby = @value
91
+ end
92
+
93
+ class JsonBool < JsonValue
94
+ attr_reader :value
95
+ def initialize(value:) @value = value end
96
+ def to_ruby = @value
97
+ end
98
+
99
+ class JsonNull < JsonValue
100
+ def to_ruby = nil
101
+ end
102
+
103
+ class JsonArray < JsonValue
104
+ attr_reader :elements
105
+ def initialize(elements:) @elements = elements end
106
+ def to_ruby = @elements.map(&:to_ruby)
107
+ end
108
+
109
+ class JsonObject < JsonValue
110
+ attr_reader :members
111
+ def initialize(members:) @members = members end
112
+ def to_ruby = @members.transform_values(&:to_ruby)
113
+ end
114
+
115
+ # Step 3: Deserializer
116
+ class JsonValueDeserializer
117
+ def self.from_json(json_string)
118
+ data = JSON.parse(json_string)
119
+ from_ruby(data)
120
+ end
121
+
122
+ def self.from_ruby(data)
123
+ case data
124
+ when String
125
+ JsonString.new(value: data)
126
+ when Integer
127
+ JsonNumber.new(value: data)
128
+ when Float
129
+ JsonNumber.new(value: data)
130
+ when true
131
+ JsonBool.new(value: true)
132
+ when false
133
+ JsonBool.new(value: false)
134
+ when nil
135
+ JsonNull.new
136
+ when Array
137
+ JsonArray.new(elements: data.map { |e| from_ruby(e) })
138
+ when Hash
139
+ JsonObject.new(members: data.transform_values { |v| from_ruby(v) })
140
+ else
141
+ raise "Unknown type: #{data.class}"
142
+ end
143
+ end
144
+ end
145
+
146
+ # Step 4: Parse with JSON output
147
+ def parse_json(input)
148
+ parser = JsonParser.new
149
+
150
+ # Serialized: Parse and get JSON from Rust
151
+ # NOTE: This requires native extension support
152
+ # output_json = parser.parse_to_json(input)
153
+
154
+ # For now, simulate by using Option A then serializing
155
+ # Real implementation would call:
156
+ # Native.parse_to_json(grammar_json, input)
157
+
158
+ # Use the parser defined in this file
159
+ tree = parser.parse(input)
160
+ transform = JsonTransform.new
161
+ result = transform.apply(tree)
162
+
163
+ # This would come from Rust in Serialized
164
+ output_json = result.to_json
165
+ puts "Output JSON: #{output_json[0..100]}..."
166
+
167
+ # Deserialize to typed objects
168
+ typed = JsonValueDeserializer.from_json(output_json)
169
+ puts "Typed: #{typed.class}"
170
+
171
+ # Convert to Ruby native types
172
+ typed.to_ruby
173
+ end
174
+
175
+ # Transform class (needed for simulation)
176
+ class JsonTransform < Parsanol::Transform
177
+ class Entry < Struct.new(:key, :val); end
178
+ rule(array: subtree(:ar)) { ar.is_a?(Array) ? ar : [ar] }
179
+ rule(object: subtree(:ob)) { (ob.is_a?(Array) ? ob : [ob]).each_with_object({}) { |e, h| h[e.key] = e.val } }
180
+ rule(entry: { key: simple(:ke), val: simple(:va) }) { Entry.new(ke, va) }
181
+ rule(string: simple(:st)) { st.to_s }
182
+ rule(number: simple(:nb)) {
183
+ s = nb.to_s
184
+ s.match?(/[eE.]/) ? Float(s) : Integer(s)
185
+ }
186
+ rule(null: simple(:_nu)) { nil }
187
+ rule(true: simple(:_tr)) { true }
188
+ rule(false: simple(:_fa)) { false }
189
+ end
190
+
191
+ # Example usage
192
+ if __FILE__ == $0
193
+ puts "=" * 60
194
+ puts "JSON Parser Example - Serialized: JSON Serialization"
195
+ puts "=" * 60
196
+ puts
197
+ puts "NOTE: This example shows the planned API for Serialized."
198
+ puts "The native extension support for parse_to_json is coming soon."
199
+ puts
200
+
201
+ test_cases = [
202
+ ['"hello"', "hello"],
203
+ ['42', 42],
204
+ ['[1, 2, 3]', [1, 2, 3]],
205
+ ['{"a": 1}', { "a" => 1 }],
206
+ ]
207
+
208
+ test_cases.each do |input, expected|
209
+ puts
210
+ puts "-" * 40
211
+ puts "Input: #{input}"
212
+ begin
213
+ result = parse_json(input)
214
+ status = result == expected ? "✓ PASS" : "✗ FAIL"
215
+ puts "Expected: #{expected.inspect}, Got: #{result.inspect} - #{status}"
216
+ rescue => e
217
+ puts "Error: #{e.message}"
218
+ puts "✗ FAIL"
219
+ end
220
+ end
221
+
222
+ puts
223
+ puts "=" * 60
224
+ puts "Serialized Benefits for JSON:"
225
+ puts "- Validates JSON structure"
226
+ puts "- Normalizes formatting"
227
+ puts "- Type-safe output (with typed classes)"
228
+ puts "- Easy to cache serialized results"
229
+ puts
230
+ puts "Note: For simple JSON parsing, Serialized adds validation but"
231
+ puts "the output is essentially the same as the input."
232
+ puts "=" * 60
233
+ end
@@ -0,0 +1,31 @@
1
+ # JSON (Serialized - Option B)
2
+
3
+ ## Purpose
4
+
5
+ This implementation demonstrates full Rust processing with JSON output
6
+ for JSON parsing.
7
+
8
+ ## When to Use
9
+
10
+ - Cross-language compatibility
11
+ - Structured output required
12
+ - Performance-critical applications
13
+
14
+ ## Key Concepts
15
+
16
+ 1. **Rust Parsing + Transform**: All processing in Rust
17
+ 2. **JSON Serialization**: Language-agnostic output
18
+ 3. **Type Safety**: Schema-driven structure
19
+
20
+ ## Running
21
+
22
+ ```bash
23
+ ruby example/json/serialized.rb
24
+ ```
25
+
26
+ ## Output
27
+
28
+ ```
29
+ Input: {"name": "test", "value": 42}
30
+ JSON: {"name":"test","value":42}
31
+ ```
@@ -0,0 +1,128 @@
1
+ $:.unshift File.dirname(__FILE__) + "/../lib"
2
+
3
+ #
4
+ # MIT License - (c) 2011 John Mettraux
5
+ #
6
+
7
+ require 'rubygems'
8
+ require 'parsanol/parslet' # gem install parslet
9
+
10
+
11
+ module MyJson
12
+
13
+ class Parser < Parsanol::Parser
14
+
15
+ rule(:spaces) { match('\s').repeat(1) }
16
+ rule(:spaces?) { spaces.maybe }
17
+
18
+ rule(:comma) { spaces? >> str(',') >> spaces? }
19
+ rule(:digit) { match('[0-9]') }
20
+
21
+ rule(:number) {
22
+ (
23
+ str('-').maybe >> (
24
+ str('0') | (match('[1-9]') >> digit.repeat)
25
+ ) >> (
26
+ str('.') >> digit.repeat(1)
27
+ ).maybe >> (
28
+ match('[eE]') >> (str('+') | str('-')).maybe >> digit.repeat(1)
29
+ ).maybe
30
+ ).as(:number)
31
+ }
32
+
33
+ rule(:string) {
34
+ str('"') >> (
35
+ str('\\') >> any | str('"').absent? >> any
36
+ ).repeat.as(:string) >> str('"')
37
+ }
38
+
39
+ rule(:array) {
40
+ str('[') >> spaces? >>
41
+ (value >> (comma >> value).repeat).maybe.as(:array) >>
42
+ spaces? >> str(']')
43
+ }
44
+
45
+ rule(:object) {
46
+ str('{') >> spaces? >>
47
+ (entry >> (comma >> entry).repeat).maybe.as(:object) >>
48
+ spaces? >> str('}')
49
+ }
50
+
51
+ rule(:value) {
52
+ string | number |
53
+ object | array |
54
+ str('true').as(:true) | str('false').as(:false) |
55
+ str('null').as(:null)
56
+ }
57
+
58
+ rule(:entry) {
59
+ (
60
+ string.as(:key) >> spaces? >>
61
+ str(':') >> spaces? >>
62
+ value.as(:val)
63
+ ).as(:entry)
64
+ }
65
+
66
+ rule(:attribute) { (entry | value).as(:attribute) }
67
+
68
+ rule(:top) { spaces? >> value >> spaces? }
69
+
70
+ root(:top)
71
+ end
72
+
73
+ class Transformer < Parsanol::Transform
74
+
75
+ class Entry < Struct.new(:key, :val); end
76
+
77
+ rule(:array => subtree(:ar)) {
78
+ ar.is_a?(Array) ? ar : [ ar ]
79
+ }
80
+ rule(:object => subtree(:ob)) {
81
+ (ob.is_a?(Array) ? ob : [ ob ]).inject({}) { |h, e| h[e.key] = e.val; h }
82
+ }
83
+
84
+ rule(:entry => { :key => simple(:ke), :val => simple(:va) }) {
85
+ Entry.new(ke, va)
86
+ }
87
+
88
+ rule(:string => simple(:st)) {
89
+ st.to_s
90
+ }
91
+ rule(:number => simple(:nb)) {
92
+ nb.match(/[eE\.]/) ? Float(nb) : Integer(nb)
93
+ }
94
+
95
+ rule(:null => simple(:nu)) { nil }
96
+ rule(:true => simple(:tr)) { true }
97
+ rule(:false => simple(:fa)) { false }
98
+ end
99
+
100
+ def self.parse(s)
101
+
102
+ parser = Parser.new
103
+ transformer = Transformer.new
104
+
105
+ tree = parser.parse(s)
106
+ puts; p tree; puts
107
+ out = transformer.apply(tree)
108
+
109
+ out
110
+ end
111
+ end
112
+
113
+
114
+ s = %{
115
+ [ 1, 2, 3, null,
116
+ "asdfasdf asdfds", { "a": -1.2 }, { "b": true, "c": false },
117
+ 0.1e24, true, false, [ 1 ] ]
118
+ }
119
+
120
+ out = MyJson.parse(s)
121
+
122
+ p out; puts
123
+
124
+ out == [
125
+ 1, 2, 3, nil,
126
+ "asdfasdf asdfds", { "a" => -1.2 }, { "b" => true, "c" => false },
127
+ 0.1e24, true, false, [ 1 ]
128
+ ] || raise("MyJson is a failure")