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,154 @@
1
+ # INI Parser Example - RubyTransform
2
+ #
3
+ # This example demonstrates parsing INI configuration files.
4
+ # Shows section headers, key-value pairs, and comments.
5
+ #
6
+ # Run with: ruby -Ilib example/ini_ruby_transform.rb
7
+
8
+ $:.unshift File.dirname(__FILE__) + "/../lib"
9
+
10
+ require 'parsanol'
11
+
12
+ # Step 1: Define the INI file grammar
13
+ class IniParser < Parsanol::Parser
14
+ root :ini
15
+
16
+ rule(:ini) {
17
+ (section | key_value | comment).repeat
18
+ }
19
+
20
+ # Section header: [section_name]
21
+ rule(:section) {
22
+ (space? >> str('[') >> section_name.as(:name) >> str(']') >> space? >> str("\n").maybe).as(:section)
23
+ }
24
+
25
+ rule(:section_name) {
26
+ (match('[^]\n]').repeat(1))
27
+ }
28
+
29
+ # Key-Value pair: key = value
30
+ rule(:key_value) {
31
+ (space? >> key.as(:key) >> space? >> str('=') >> space? >> value.as(:value) >> space? >> str("\n").maybe).as(:kv)
32
+ }
33
+
34
+ rule(:key) {
35
+ match('[^\s=]').repeat(1)
36
+ }
37
+
38
+ rule(:value) {
39
+ (match('[^\n]').repeat)
40
+ }
41
+
42
+ # Comment: # or ; at start of line
43
+ rule(:comment) {
44
+ (space? >> (str('#') | str(';')) >> match('[^\n]').repeat >> str("\n").maybe).as(:comment)
45
+ }
46
+
47
+ rule(:space?) { match('\s').repeat }
48
+ end
49
+
50
+ # Step 2: INI data structures
51
+ class IniFile
52
+ attr_reader :sections
53
+
54
+ def initialize
55
+ @sections = {}
56
+ @current_section = nil
57
+ end
58
+
59
+ def set_section(name)
60
+ @current_section = name
61
+ @sections[name] ||= {}
62
+ end
63
+
64
+ def set_key_value(key, value)
65
+ section = @sections[@current_section] || {}
66
+ section[key.to_s.strip] = value.to_s.strip
67
+ @sections[@current_section] = section
68
+ end
69
+
70
+ def get(section, key = nil)
71
+ if key
72
+ @sections[section]&.[](key)
73
+ else
74
+ @sections[section] || {}
75
+ end
76
+ end
77
+
78
+ def to_h
79
+ @sections
80
+ end
81
+ end
82
+
83
+ # Step 3: Parse and transform
84
+ def parse_ini(input)
85
+ parser = IniParser.new
86
+ tree = parser.parse(input)
87
+
88
+ puts "Parse tree: #{tree.inspect[0..500]}..."
89
+
90
+ ini = IniFile.new
91
+
92
+ tree.each do |item|
93
+ case item
94
+ when Hash
95
+ if item[:section]
96
+ name = item[:section][:name].to_s.strip
97
+ ini.set_section(name)
98
+ puts " Section: [#{name}]"
99
+ elsif item[:kv]
100
+ key = item[:kv][:key].to_s
101
+ value = item[:kv][:value].to_s
102
+ ini.set_key_value(key, value)
103
+ puts " #{key} = #{value}"
104
+ elsif item[:comment]
105
+ # Skip comments
106
+ end
107
+ end
108
+ end
109
+
110
+ ini
111
+ end
112
+
113
+ # Example usage
114
+ if __FILE__ == $0
115
+ puts "=" * 60
116
+ puts "INI Parser - RubyTransform"
117
+ puts "=" * 60
118
+ puts
119
+
120
+ ini_content = <<~INI
121
+ # This is a comment
122
+ ; This is also a comment
123
+
124
+ [database]
125
+ host = localhost
126
+ port = 5432
127
+
128
+ [server]
129
+ host = 0.0.0.0
130
+ port = 8080
131
+ debug = true
132
+
133
+ [cache]
134
+ enabled = true
135
+ ttl = 3600
136
+ INI
137
+
138
+ puts "Input:"
139
+ puts "-" * 40
140
+ puts ini_content
141
+ puts
142
+ puts "Parsed:"
143
+ puts "-" * 40
144
+
145
+ ini = parse_ini(ini_content)
146
+
147
+ puts
148
+ puts "=" * 60
149
+ puts "Accessing parsed data:"
150
+ puts "=" * 60
151
+ puts "database.host: #{ini.get('database', 'host')}"
152
+ puts "database.port: #{ini.get('database', 'port')}"
153
+ puts "server.debug: #{ini.get('server', 'debug')}"
154
+ end
@@ -0,0 +1,129 @@
1
+ # INI Parser - Ruby Implementation
2
+
3
+ ## How to Run
4
+
5
+ ```bash
6
+ cd parsanol-ruby/example/ini
7
+ ruby basic.rb
8
+ ```
9
+
10
+ ## Code Walkthrough
11
+
12
+ ### Section Header Rule
13
+
14
+ Sections are enclosed in square brackets:
15
+
16
+ ```ruby
17
+ rule(:section) {
18
+ (space? >> str('[') >> section_name.as(:name) >> str(']') >> space? >> str("\n").maybe).as(:section)
19
+ }
20
+
21
+ rule(:section_name) {
22
+ (match('[^]\n]').repeat(1))
23
+ }
24
+ ```
25
+
26
+ Section names exclude brackets and newlines for valid syntax.
27
+
28
+ ### Key-Value Pair Rule
29
+
30
+ Configuration entries follow `key = value` format:
31
+
32
+ ```ruby
33
+ rule(:key_value) {
34
+ (space? >> key.as(:key) >> space? >> str('=') >> space? >> value.as(:value) >> space? >> str("\n").maybe).as(:kv)
35
+ }
36
+
37
+ rule(:key) {
38
+ match('[^\s=]').repeat(1)
39
+ }
40
+
41
+ rule(:value) {
42
+ (match('[^\n]').repeat)
43
+ }
44
+ ```
45
+
46
+ Keys cannot contain whitespace or equals; values extend to end of line.
47
+
48
+ ### Comment Rule
49
+
50
+ Comments start with `#` or `;`:
51
+
52
+ ```ruby
53
+ rule(:comment) {
54
+ (space? >> (str('#') | str(';')) >> match('[^\n]').repeat >> str("\n").maybe).as(:comment)
55
+ }
56
+ ```
57
+
58
+ Both comment styles are common in INI files.
59
+
60
+ ### Top-Level Grammar
61
+
62
+ The INI file is a sequence of sections, key-value pairs, and comments:
63
+
64
+ ```ruby
65
+ rule(:ini) {
66
+ (section | key_value | comment).repeat
67
+ }
68
+ ```
69
+
70
+ Key-value pairs before any section header belong to no section.
71
+
72
+ ### IniFile Helper Class
73
+
74
+ A helper class manages parsed data:
75
+
76
+ ```ruby
77
+ class IniFile
78
+ attr_reader :sections
79
+
80
+ def initialize
81
+ @sections = {}
82
+ @current_section = nil
83
+ end
84
+
85
+ def set_section(name)
86
+ @current_section = name
87
+ @sections[name] ||= {}
88
+ end
89
+
90
+ def set_key_value(key, value)
91
+ section = @sections[@current_section] || {}
92
+ section[key.to_s.strip] = value.to_s.strip
93
+ @sections[@current_section] = section
94
+ end
95
+ end
96
+ ```
97
+
98
+ Track current section and build nested hash structure.
99
+
100
+ ## Output Types
101
+
102
+ ```ruby
103
+ # Parse tree:
104
+ [
105
+ {:section=>{:name=>"database"@s}},
106
+ {:kv=>{:key=>"host"@s, :value=>"localhost"@s}},
107
+ {:kv=>{:key=>"port"@s, :value=>"5432"@s}}
108
+ ]
109
+
110
+ # After processing:
111
+ {
112
+ "database" => {"host"=>"localhost", "port"=>"5432"},
113
+ "server" => {"host"=>"0.0.0.0", "port"=>"8080", "debug"=>"true"}
114
+ }
115
+ ```
116
+
117
+ ## Design Decisions
118
+
119
+ ### Why Track Current Section in Helper Class?
120
+
121
+ INI files are inherently sectioned; the helper maintains context as we iterate through parse results.
122
+
123
+ ### Why Allow Both Comment Prefixes?
124
+
125
+ `#` is Unix-style, `;` is Windows-style. Supporting both maximizes compatibility.
126
+
127
+ ### Why Not Use Transform?
128
+
129
+ Manual iteration provides more control for stateful processing (tracking current section).
@@ -0,0 +1,154 @@
1
+ # INI Parser Example - RubyTransform
2
+ #
3
+ # This example demonstrates parsing INI configuration files.
4
+ # Shows section headers, key-value pairs, and comments.
5
+ #
6
+ # Run with: ruby -Ilib example/ini_ruby_transform.rb
7
+
8
+ $:.unshift File.dirname(__FILE__) + "/../lib"
9
+
10
+ require 'parsanol'
11
+
12
+ # Step 1: Define the INI file grammar
13
+ class IniParser < Parsanol::Parser
14
+ root :ini
15
+
16
+ rule(:ini) {
17
+ (section | key_value | comment).repeat
18
+ }
19
+
20
+ # Section header: [section_name]
21
+ rule(:section) {
22
+ (space? >> str('[') >> section_name.as(:name) >> str(']') >> space? >> str("\n").maybe).as(:section)
23
+ }
24
+
25
+ rule(:section_name) {
26
+ (match('[^]\n]').repeat(1))
27
+ }
28
+
29
+ # Key-Value pair: key = value
30
+ rule(:key_value) {
31
+ (space? >> key.as(:key) >> space? >> str('=') >> space? >> value.as(:value) >> space? >> str("\n").maybe).as(:kv)
32
+ }
33
+
34
+ rule(:key) {
35
+ match('[^\s=]').repeat(1)
36
+ }
37
+
38
+ rule(:value) {
39
+ (match('[^\n]').repeat)
40
+ }
41
+
42
+ # Comment: # or ; at start of line
43
+ rule(:comment) {
44
+ (space? >> (str('#') | str(';')) >> match('[^\n]').repeat >> str("\n").maybe).as(:comment)
45
+ }
46
+
47
+ rule(:space?) { match('\s').repeat }
48
+ end
49
+
50
+ # Step 2: INI data structures
51
+ class IniFile
52
+ attr_reader :sections
53
+
54
+ def initialize
55
+ @sections = {}
56
+ @current_section = nil
57
+ end
58
+
59
+ def set_section(name)
60
+ @current_section = name
61
+ @sections[name] ||= {}
62
+ end
63
+
64
+ def set_key_value(key, value)
65
+ section = @sections[@current_section] || {}
66
+ section[key.to_s.strip] = value.to_s.strip
67
+ @sections[@current_section] = section
68
+ end
69
+
70
+ def get(section, key = nil)
71
+ if key
72
+ @sections[section]&.[](key)
73
+ else
74
+ @sections[section] || {}
75
+ end
76
+ end
77
+
78
+ def to_h
79
+ @sections
80
+ end
81
+ end
82
+
83
+ # Step 3: Parse and transform
84
+ def parse_ini(input)
85
+ parser = IniParser.new
86
+ tree = parser.parse(input)
87
+
88
+ puts "Parse tree: #{tree.inspect[0..500]}..."
89
+
90
+ ini = IniFile.new
91
+
92
+ tree.each do |item|
93
+ case item
94
+ when Hash
95
+ if item[:section]
96
+ name = item[:section][:name].to_s.strip
97
+ ini.set_section(name)
98
+ puts " Section: [#{name}]"
99
+ elsif item[:kv]
100
+ key = item[:kv][:key].to_s
101
+ value = item[:kv][:value].to_s
102
+ ini.set_key_value(key, value)
103
+ puts " #{key} = #{value}"
104
+ elsif item[:comment]
105
+ # Skip comments
106
+ end
107
+ end
108
+ end
109
+
110
+ ini
111
+ end
112
+
113
+ # Example usage
114
+ if __FILE__ == $0
115
+ puts "=" * 60
116
+ puts "INI Parser - RubyTransform"
117
+ puts "=" * 60
118
+ puts
119
+
120
+ ini_content = <<~INI
121
+ # This is a comment
122
+ ; This is also a comment
123
+
124
+ [database]
125
+ host = localhost
126
+ port = 5432
127
+
128
+ [server]
129
+ host = 0.0.0.0
130
+ port = 8080
131
+ debug = true
132
+
133
+ [cache]
134
+ enabled = true
135
+ ttl = 3600
136
+ INI
137
+
138
+ puts "Input:"
139
+ puts "-" * 40
140
+ puts ini_content
141
+ puts
142
+ puts "Parsed:"
143
+ puts "-" * 40
144
+
145
+ ini = parse_ini(ini_content)
146
+
147
+ puts
148
+ puts "=" * 60
149
+ puts "Accessing parsed data:"
150
+ puts "=" * 60
151
+ puts "database.host: #{ini.get('database', 'host')}"
152
+ puts "database.port: #{ini.get('database', 'port')}"
153
+ puts "server.debug: #{ini.get('server', 'debug')}"
154
+ end
@@ -0,0 +1,125 @@
1
+ # This example is heavily inspired by citrus' ip.citrus. Have a look at both
2
+ # of these to get some choice!
3
+
4
+ # The grammars in this file conform to the ABNF given in Appendix A of RFC 3986
5
+ # Uniform Resource Identifier (URI): Generic Syntax.
6
+ #
7
+ # See http://tools.ietf.org/html/rfc3986#appendix-A for more information.
8
+
9
+ $:.unshift File.dirname(__FILE__) + "/../lib"
10
+
11
+ require 'pp'
12
+ require 'parsanol/parslet'
13
+
14
+ module IPv4
15
+ include Parsanol::Parslet
16
+
17
+ # A host identified by an IPv4 literal address is represented in
18
+ # dotted-decimal notation (a sequence of four decimal numbers in the range 0
19
+ # to 255, separated by "."), as described in [RFC1123] by reference to
20
+ # [RFC0952]. Note that other forms of dotted notation may be interpreted on
21
+ # some platforms, as described in Section 7.4, but only the dotted-decimal
22
+ # form of four octets is allowed by this grammar.
23
+ rule(:ipv4) {
24
+ (dec_octet >> str('.') >> dec_octet >> str('.') >>
25
+ dec_octet >> str('.') >> dec_octet).as(:ipv4)
26
+ }
27
+
28
+ rule(:dec_octet) {
29
+ str('25') >> match("[0-5]") |
30
+ str('2') >> match("[0-4]") >> digit |
31
+ str('1') >> digit >> digit |
32
+ match('[1-9]') >> digit |
33
+ digit
34
+ }
35
+
36
+ rule(:digit) {
37
+ match('[0-9]')
38
+ }
39
+ end
40
+
41
+ # Must be used in concert with IPv4
42
+ module IPv6
43
+ include Parsanol::Parslet
44
+
45
+ rule(:colon) { str(':') }
46
+ rule(:dcolon) { colon >> colon }
47
+
48
+ # h16 :
49
+ def h16r(times)
50
+ (h16 >> colon).repeat(times, times)
51
+ end
52
+
53
+ # : h16
54
+ def h16l(times)
55
+ (colon >> h16).repeat(0,times)
56
+ end
57
+
58
+ # A 128-bit IPv6 address is divided into eight 16-bit pieces. Each piece is
59
+ # represented numerically in case-insensitive hexadecimal, using one to four
60
+ # hexadecimal digits (leading zeroes are permitted). The eight encoded
61
+ # pieces are given most-significant first, separated by colon characters.
62
+ # Optionally, the least-significant two pieces may instead be represented in
63
+ # IPv4 address textual format. A sequence of one or more consecutive
64
+ # zero-valued 16-bit pieces within the address may be elided, omitting all
65
+ # their digits and leaving exactly two consecutive colons in their place to
66
+ # mark the elision.
67
+ rule(:ipv6) {
68
+ (
69
+ (
70
+ h16r(6) |
71
+ dcolon >> h16r(5) |
72
+ h16.maybe >> dcolon >> h16r(4) |
73
+ (h16 >> h16l(1)).maybe >> dcolon >> h16r(3) |
74
+ (h16 >> h16l(2)).maybe >> dcolon >> h16r(2) |
75
+ (h16 >> h16l(3)).maybe >> dcolon >> h16r(1) |
76
+ (h16 >> h16l(4)).maybe >> dcolon
77
+ ) >> ls32 |
78
+ (h16 >> h16l(5)).maybe >> dcolon >> h16 |
79
+ (h16 >> h16l(6)).maybe >> dcolon
80
+ ).as(:ipv6)
81
+ }
82
+
83
+ rule(:h16) {
84
+ hexdigit.repeat(1,4)
85
+ }
86
+
87
+ rule(:ls32) {
88
+ (h16 >> colon >> h16) |
89
+ ipv4
90
+ }
91
+
92
+ rule(:hexdigit) {
93
+ digit | match("[a-fA-F]")
94
+ }
95
+ end
96
+
97
+ class Parser
98
+ include IPv4
99
+ include IPv6
100
+
101
+ def parse(str)
102
+ (ipv4 | ipv6).parse(str)
103
+ end
104
+ end
105
+
106
+ %W(
107
+ 0.0.0.0
108
+ 255.255.255.255
109
+ 255.255.255
110
+ 1:2:3:4:5:6:7:8
111
+ 12AD:34FC:A453:1922::
112
+ 12AD::34FC
113
+ 12AD::
114
+ ::
115
+ 1:2
116
+ ).each do |address|
117
+ parser = Parser.new
118
+ printf "%30s -> ", address
119
+ begin
120
+ result = parser.parse(address)
121
+ puts result.inspect
122
+ rescue Parsanol::ParseFailed => m
123
+ puts "Failed: #{m}"
124
+ end
125
+ end
@@ -0,0 +1,139 @@
1
+ # IP Address Parser - Ruby Implementation
2
+
3
+ ## How to Run
4
+
5
+ ```bash
6
+ cd parsanol-ruby/example/ip-address
7
+ ruby basic.rb
8
+ ```
9
+
10
+ ## Code Walkthrough
11
+
12
+ ### IPv4 DecOctet Rule
13
+
14
+ Each octet must be 0-255:
15
+
16
+ ```ruby
17
+ rule(:dec_octet) {
18
+ str('25') >> match("[0-5]") |
19
+ str('2') >> match("[0-4]") >> digit |
20
+ str('1') >> digit >> digit |
21
+ match('[1-9]') >> digit |
22
+ digit
23
+ }
24
+ ```
25
+
26
+ Ordered alternatives ensure correct matching: 250-255, 200-249, 100-199, 10-99, 0-9.
27
+
28
+ ### IPv4 Address Rule
29
+
30
+ Four octets separated by dots:
31
+
32
+ ```ruby
33
+ rule(:ipv4) {
34
+ (dec_octet >> str('.') >> dec_octet >> str('.') >>
35
+ dec_octet >> str('.') >> dec_octet).as(:ipv4)
36
+ }
37
+ ```
38
+
39
+ This matches the dotted-decimal notation from RFC 1123.
40
+
41
+ ### IPv6 H16 Rule
42
+
43
+ Hexadecimal groups are 1-4 digits:
44
+
45
+ ```ruby
46
+ rule(:h16) {
47
+ hexdigit.repeat(1,4)
48
+ }
49
+
50
+ rule(:hexdigit) {
51
+ digit | match("[a-fA-F]")
52
+ }
53
+ ```
54
+
55
+ Case-insensitive hex matching follows RFC 3986.
56
+
57
+ ### IPv6 Address Rule
58
+
59
+ IPv6 allows zero compression with `::`:
60
+
61
+ ```ruby
62
+ rule(:ipv6) {
63
+ (
64
+ (
65
+ h16r(6) |
66
+ dcolon >> h16r(5) |
67
+ h16.maybe >> dcolon >> h16r(4) |
68
+ # ... more patterns
69
+ ) >> ls32 |
70
+ (h16 >> h16l(5)).maybe >> dcolon >> h16 |
71
+ (h16 >> h16l(6)).maybe >> dcolon
72
+ ).as(:ipv6)
73
+ }
74
+ ```
75
+
76
+ Multiple alternatives handle different compression positions.
77
+
78
+ ### LS32 Rule
79
+
80
+ The least-significant 32 bits can be IPv4 or two h16 groups:
81
+
82
+ ```ruby
83
+ rule(:ls32) {
84
+ (h16 >> colon >> h16) |
85
+ ipv4
86
+ }
87
+ ```
88
+
89
+ IPv4-mapped IPv6 addresses are supported.
90
+
91
+ ### Module Composition
92
+
93
+ Grammar modules are mixed into the parser:
94
+
95
+ ```ruby
96
+ module IPv4
97
+ include Parsanol::Parslet
98
+ # IPv4 rules...
99
+ end
100
+
101
+ module IPv6
102
+ include Parsanol::Parslet
103
+ # IPv6 rules...
104
+ end
105
+
106
+ class Parser
107
+ include IPv4
108
+ include IPv6
109
+ end
110
+ ```
111
+
112
+ Modular organization keeps complex grammar manageable.
113
+
114
+ ## Output Types
115
+
116
+ ```ruby
117
+ # IPv4:
118
+ {:ipv4=>"192.168.1.1"@s}
119
+
120
+ # IPv6:
121
+ {:ipv6=>"2001:db8::1"@s}
122
+
123
+ # Invalid:
124
+ # Raises Parsanol::ParseFailed
125
+ ```
126
+
127
+ ## Design Decisions
128
+
129
+ ### Why Separate IPv4 and IPv6 Modules?
130
+
131
+ RFC 3986 defines them separately; modules allow independent testing and reuse.
132
+
133
+ ### Why So Many IPv6 Alternatives?
134
+
135
+ Zero compression (`::`) can appear at any position; each alternative represents a valid compression point.
136
+
137
+ ### Why Ordered Alternatives for DecOctet?
138
+
139
+ PEG parsers try alternatives in order. Largest ranges must come first to prevent premature matching.