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,345 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Parsanol::StringView do
6
+ let(:input) { "Hello World" }
7
+
8
+ describe '#initialize' do
9
+ it 'creates view with offset and length' do
10
+ view = described_class.new(input, offset: 6, length: 5)
11
+ expect(view.offset).to eq(6)
12
+ expect(view.length).to eq(5)
13
+ expect(view.string).to eq(input)
14
+ end
15
+
16
+ it 'defaults to full string when no offset given' do
17
+ view = described_class.new(input)
18
+ expect(view.offset).to eq(0)
19
+ expect(view.length).to eq(input.bytesize)
20
+ end
21
+
22
+ it 'calculates length from offset when not provided' do
23
+ view = described_class.new(input, offset: 6)
24
+ expect(view.length).to eq(5) # "Hello World" - 6 = 5
25
+ end
26
+
27
+ it 'accepts explicit length' do
28
+ view = described_class.new(input, offset: 0, length: 5)
29
+ expect(view.length).to eq(5)
30
+ end
31
+
32
+ it 'does not materialize string on initialization' do
33
+ view = described_class.new(input, offset: 0, length: 5)
34
+ expect(view.inspect).not_to include('cached')
35
+ end
36
+ end
37
+
38
+ describe '#to_s' do
39
+ it 'materializes substring' do
40
+ view = described_class.new(input, offset: 0, length: 5)
41
+ expect(view.to_s).to eq("Hello")
42
+ end
43
+
44
+ it 'materializes full string' do
45
+ view = described_class.new(input)
46
+ expect(view.to_s).to eq("Hello World")
47
+ end
48
+
49
+ it 'materializes substring from middle' do
50
+ view = described_class.new(input, offset: 6, length: 5)
51
+ expect(view.to_s).to eq("World")
52
+ end
53
+
54
+ it 'caches materialized string' do
55
+ view = described_class.new(input, offset: 6, length: 5)
56
+ str1 = view.to_s
57
+ str2 = view.to_s
58
+ expect(str1.object_id).to eq(str2.object_id)
59
+ end
60
+
61
+ it 'handles empty view' do
62
+ view = described_class.new(input, offset: 0, length: 0)
63
+ expect(view.to_s).to eq("")
64
+ end
65
+
66
+ it 'handles single character' do
67
+ view = described_class.new(input, offset: 0, length: 1)
68
+ expect(view.to_s).to eq("H")
69
+ end
70
+ end
71
+
72
+ describe '#[]' do
73
+ it 'accesses character without materialization' do
74
+ view = described_class.new(input, offset: 6, length: 5)
75
+ expect(view[0]).to eq("W")
76
+ expect(view[4]).to eq("d")
77
+ expect(view.inspect).not_to include('cached')
78
+ end
79
+
80
+ it 'returns first character' do
81
+ view = described_class.new(input, offset: 0, length: 5)
82
+ expect(view[0]).to eq("H")
83
+ end
84
+
85
+ it 'returns last character' do
86
+ view = described_class.new(input, offset: 0, length: 5)
87
+ expect(view[4]).to eq("o")
88
+ end
89
+
90
+ it 'returns nil for out of bounds positive index' do
91
+ view = described_class.new(input, offset: 0, length: 5)
92
+ expect(view[10]).to be_nil
93
+ end
94
+
95
+ it 'returns nil for negative index' do
96
+ view = described_class.new(input, offset: 0, length: 5)
97
+ expect(view[-1]).to be_nil
98
+ end
99
+
100
+ it 'returns nil for index at length boundary' do
101
+ view = described_class.new(input, offset: 0, length: 5)
102
+ expect(view[5]).to be_nil
103
+ end
104
+
105
+ it 'works with offset view' do
106
+ view = described_class.new(input, offset: 6, length: 5)
107
+ expect(view[0]).to eq("W")
108
+ expect(view[1]).to eq("o")
109
+ expect(view[2]).to eq("r")
110
+ end
111
+ end
112
+
113
+ describe '#slice' do
114
+ it 'creates substring view without copying' do
115
+ view = described_class.new(input, offset: 0, length: 11)
116
+ sub = view.slice(6, 5)
117
+ expect(sub.to_s).to eq("World")
118
+ expect(sub.offset).to eq(6)
119
+ expect(sub.length).to eq(5)
120
+ end
121
+
122
+ it 'shares same string reference' do
123
+ view = described_class.new(input, offset: 0, length: 11)
124
+ sub = view.slice(6, 5)
125
+ expect(sub.string.object_id).to eq(view.string.object_id)
126
+ end
127
+
128
+ it 'creates view from middle of view' do
129
+ view = described_class.new(input, offset: 6, length: 5)
130
+ sub = view.slice(1, 3)
131
+ expect(sub.to_s).to eq("orl")
132
+ end
133
+
134
+ it 'handles zero-length slice' do
135
+ view = described_class.new(input, offset: 0, length: 5)
136
+ sub = view.slice(0, 0)
137
+ expect(sub.to_s).to eq("")
138
+ expect(sub.empty?).to be true
139
+ end
140
+
141
+ it 'handles negative length' do
142
+ view = described_class.new(input, offset: 0, length: 5)
143
+ sub = view.slice(0, -1)
144
+ expect(sub.to_s).to eq("")
145
+ end
146
+
147
+ it 'clamps to valid range when slicing beyond end' do
148
+ view = described_class.new(input, offset: 0, length: 5)
149
+ sub = view.slice(3, 10)
150
+ expect(sub.to_s).to eq("lo")
151
+ end
152
+
153
+ it 'returns empty view when start is beyond length' do
154
+ view = described_class.new(input, offset: 0, length: 5)
155
+ sub = view.slice(10, 5)
156
+ expect(sub.to_s).to eq("")
157
+ end
158
+ end
159
+
160
+ describe '#bytesize, #size, #length' do
161
+ it 'returns length in bytes' do
162
+ view = described_class.new(input, offset: 0, length: 5)
163
+ expect(view.bytesize).to eq(5)
164
+ expect(view.size).to eq(5)
165
+ expect(view.length).to eq(5)
166
+ end
167
+
168
+ it 'returns zero for empty view' do
169
+ view = described_class.new(input, offset: 0, length: 0)
170
+ expect(view.bytesize).to eq(0)
171
+ end
172
+ end
173
+
174
+ describe '#empty?' do
175
+ it 'returns true for zero-length view' do
176
+ view = described_class.new(input, offset: 0, length: 0)
177
+ expect(view.empty?).to be true
178
+ end
179
+
180
+ it 'returns false for non-empty view' do
181
+ view = described_class.new(input, offset: 0, length: 5)
182
+ expect(view.empty?).to be false
183
+ end
184
+ end
185
+
186
+ describe '#==' do
187
+ it 'compares with String by materializing' do
188
+ view = described_class.new(input, offset: 0, length: 5)
189
+ expect(view == "Hello").to be true
190
+ expect(view == "World").to be false
191
+ end
192
+
193
+ it 'compares two StringViews without materializing' do
194
+ view1 = described_class.new(input, offset: 0, length: 5)
195
+ view2 = described_class.new(input, offset: 0, length: 5)
196
+ expect(view1 == view2).to be true
197
+ expect(view1.inspect).not_to include('cached')
198
+ expect(view2.inspect).not_to include('cached')
199
+ end
200
+
201
+ it 'distinguishes different ranges on same string' do
202
+ view1 = described_class.new(input, offset: 0, length: 5)
203
+ view2 = described_class.new(input, offset: 6, length: 5)
204
+ expect(view1 == view2).to be false
205
+ end
206
+
207
+ it 'distinguishes different strings' do
208
+ other_input = "Hello World".dup # Ensure different object
209
+ view1 = described_class.new(input, offset: 0, length: 5)
210
+ view2 = described_class.new(other_input, offset: 0, length: 5)
211
+ # Verify they are actually different objects
212
+ expect(input.object_id).not_to eq(other_input.object_id)
213
+ # StringViews should not be equal (different string objects)
214
+ expect(view1 == view2).to be false
215
+ end
216
+
217
+ it 'eql? works same as ==' do
218
+ view = described_class.new(input, offset: 0, length: 5)
219
+ expect(view.eql?("Hello")).to be true
220
+ end
221
+ end
222
+
223
+ describe '#hash' do
224
+ it 'returns hash code for hashing' do
225
+ view = described_class.new(input, offset: 0, length: 5)
226
+ expect(view.hash).to be_a(Integer)
227
+ end
228
+
229
+ it 'same views have same hash' do
230
+ view1 = described_class.new(input, offset: 0, length: 5)
231
+ view2 = described_class.new(input, offset: 0, length: 5)
232
+ expect(view1.hash).to eq(view2.hash)
233
+ end
234
+
235
+ it 'different views have different hashes' do
236
+ view1 = described_class.new(input, offset: 0, length: 5)
237
+ view2 = described_class.new(input, offset: 6, length: 5)
238
+ expect(view1.hash).not_to eq(view2.hash)
239
+ end
240
+
241
+ it 'can be used in Hash' do
242
+ view = described_class.new(input, offset: 0, length: 5)
243
+ hash = { view => "value" }
244
+ expect(hash[view]).to eq("value")
245
+ end
246
+ end
247
+
248
+ describe '#inspect' do
249
+ it 'shows offset and length' do
250
+ view = described_class.new(input, offset: 6, length: 5)
251
+ result = view.inspect
252
+ expect(result).to include('StringView')
253
+ expect(result).to include('@offset=6')
254
+ expect(result).to include('@length=5')
255
+ end
256
+
257
+ it 'shows cached status when materialized' do
258
+ view = described_class.new(input, offset: 0, length: 5)
259
+ view.to_s
260
+ result = view.inspect
261
+ expect(result).to include('cached="Hello"')
262
+ end
263
+
264
+ it 'does not show cached when not materialized' do
265
+ view = described_class.new(input, offset: 0, length: 5)
266
+ result = view.inspect
267
+ expect(result).not_to include('cached')
268
+ end
269
+ end
270
+
271
+ describe '#reset!' do
272
+ it 'resets view with new values' do
273
+ view = described_class.new(input, offset: 0, length: 5)
274
+ view.to_s # Materialize
275
+
276
+ new_input = "Goodbye"
277
+ view.reset!(new_input, 0, 4)
278
+
279
+ expect(view.string).to eq(new_input)
280
+ expect(view.offset).to eq(0)
281
+ expect(view.length).to eq(4)
282
+ expect(view.to_s).to eq("Good")
283
+ end
284
+
285
+ it 'clears cached materialization' do
286
+ view = described_class.new(input, offset: 0, length: 5)
287
+ view.to_s # Materialize
288
+ expect(view.inspect).to include('cached')
289
+
290
+ view.reset!(input, 6, 5)
291
+ expect(view.inspect).not_to include('cached')
292
+ expect(view.to_s).to eq("World")
293
+ end
294
+
295
+ it 'returns self for chaining' do
296
+ view = described_class.new(input, offset: 0, length: 5)
297
+ result = view.reset!(input, 6, 5)
298
+ expect(result).to be(view)
299
+ end
300
+ end
301
+
302
+ describe 'UTF-8 support' do
303
+ let(:utf8_input) { "Hello 世界" }
304
+
305
+ it 'handles UTF-8 strings' do
306
+ view = described_class.new(utf8_input, offset: 0, length: utf8_input.bytesize)
307
+ expect(view.to_s).to eq(utf8_input)
308
+ end
309
+
310
+ it 'uses byte offsets not character offsets' do
311
+ # "Hello " is 6 bytes, "世" is 3 bytes, "界" is 3 bytes
312
+ view = described_class.new(utf8_input, offset: 6, length: 6)
313
+ expect(view.to_s).to eq("世界")
314
+ end
315
+ end
316
+
317
+ describe 'zero-copy performance characteristics' do
318
+ it 'does not create strings during slicing operations' do
319
+ view = described_class.new(input, offset: 0, length: 11)
320
+
321
+ # Chain multiple slice operations
322
+ sub1 = view.slice(0, 5)
323
+ sub2 = sub1.slice(0, 3)
324
+ sub3 = sub2.slice(1, 1)
325
+
326
+ # No strings created yet
327
+ expect(view.inspect).not_to include('cached')
328
+ expect(sub1.inspect).not_to include('cached')
329
+ expect(sub2.inspect).not_to include('cached')
330
+
331
+ # Only when we call to_s
332
+ expect(sub3.to_s).to eq("e")
333
+ end
334
+
335
+ it 'shares string reference across all views' do
336
+ view = described_class.new(input, offset: 0, length: 11)
337
+ sub1 = view.slice(0, 5)
338
+ sub2 = sub1.slice(0, 3)
339
+
340
+ expect(view.string.object_id).to eq(input.object_id)
341
+ expect(sub1.string.object_id).to eq(input.object_id)
342
+ expect(sub2.string.object_id).to eq(input.object_id)
343
+ end
344
+ end
345
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parsanol::Context do
4
+ def context(*args)
5
+ described_class.new(*args)
6
+ end
7
+
8
+ it "binds hash keys as variable like things" do
9
+ context(:a => 'value').instance_eval { a }.
10
+ should == 'value'
11
+ end
12
+ it "one contexts variables aren't the next ones" do
13
+ ca = context(:a => 'b')
14
+ cb = context(:b => 'c')
15
+
16
+ ca.methods.should_not include(:b)
17
+ cb.methods.should_not include(:a)
18
+ end
19
+
20
+ describe 'works as a Ruby object should' do
21
+ let(:obj) { context(a: 1) }
22
+
23
+ it 'responds_to? :a' do
24
+ expect(obj.respond_to?(:a)).to be_truthy
25
+ end
26
+ it 'includes :a in #methods' do
27
+ expect(obj.methods).to include(:a)
28
+ end
29
+ it 'allows inspection' do
30
+ expect(obj.inspect).to match(/@a=1/)
31
+ end
32
+ it 'allows conversion to string' do
33
+ expect(obj.to_s).to match(/Parsanol::Context:0x/)
34
+ end
35
+
36
+ context 'when the context is enhanced' do
37
+ before(:each) do
38
+ class << obj
39
+ def foo
40
+ 'foo'
41
+ end
42
+ end
43
+ end
44
+
45
+ it 'responds_to correctly' do
46
+ expect(obj.respond_to?(:foo)).to be_truthy
47
+ end
48
+ it 'includes :foo also in methods' do
49
+ expect(obj.methods).to include(:foo)
50
+ end
51
+ it 'allows calling #foo' do
52
+ expect(obj.foo).to eq('foo')
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,183 @@
1
+ require 'spec_helper'
2
+
3
+ require 'parsanol/parslet'
4
+
5
+ describe Parsanol::Transform do
6
+ include Parsanol
7
+
8
+ let(:transform) { Parsanol::Transform.new }
9
+
10
+ class A < Struct.new(:elt); end
11
+ class B < Struct.new(:elt); end
12
+ class C < Struct.new(:elt); end
13
+ class Bi < Struct.new(:a, :b); end
14
+
15
+ describe "delayed construction" do
16
+ context "given simple(:x) => A.new(x)" do
17
+ before(:each) do
18
+ transform.rule(simple(:x)) { |d| A.new(d[:x]) }
19
+ end
20
+
21
+ it "should transform 'a' into A.new('a')" do
22
+ transform.apply('a').should == A.new('a')
23
+ end
24
+ it "should transform ['a', 'b'] into [A.new('a'), A.new('b')]" do
25
+ transform.apply(['a', 'b']).should ==
26
+ [A.new('a'), A.new('b')]
27
+ end
28
+ end
29
+ context "given rules on {:a => simple(:x)} and {:b => :_x}" do
30
+ before(:each) do
31
+ transform.rule(:a => simple(:x)) { |d| A.new(d[:x]) }
32
+ transform.rule(:b => simple(:x)) { |d| B.new(d[:x]) }
33
+ end
34
+
35
+ it "should transform {:d=>{:b=>'c'}} into d => B('c')" do
36
+ transform.apply({:d=>{:b=>'c'}}).should == {:d => B.new('c')}
37
+ end
38
+ it "should transform {:a=>{:b=>'c'}} into A(B('c'))" do
39
+ transform.apply({:a=>{:b=>'c'}}).should == A.new(B.new('c'))
40
+ end
41
+ end
42
+ describe "pulling out subbranches" do
43
+ before(:each) do
44
+ transform.rule(:a => {:b => simple(:x)}, :d => {:e => simple(:y)}) { |d|
45
+ Bi.new(*d.values_at(:x, :y))
46
+ }
47
+ end
48
+
49
+ it "should yield Bi.new('c', 'f')" do
50
+ transform.apply(:a => {:b => 'c'}, :d => {:e => 'f'}).should ==
51
+ Bi.new('c', 'f')
52
+ end
53
+ end
54
+ end
55
+ describe "dsl construction" do
56
+ let(:transform) { Parsanol::Transform.new do
57
+ rule(simple(:x)) { A.new(x) }
58
+ end
59
+ }
60
+
61
+ it "should still evaluate rules correctly" do
62
+ transform.apply('a').should == A.new('a')
63
+ end
64
+ end
65
+ describe "class construction" do
66
+ class OptimusPrime < Parsanol::Transform
67
+ rule(:a => simple(:x)) { A.new(x) }
68
+ rule(:b => simple(:x)) { B.new(x) }
69
+ end
70
+ let(:transform) { OptimusPrime.new }
71
+
72
+ it "should evaluate rules" do
73
+ transform.apply(:a => 'a').should == A.new('a')
74
+ end
75
+
76
+ context "optionally raise when no match found" do
77
+ class BumbleBee < Parsanol::Transform
78
+ def initialize(&block)
79
+ super(raise_on_unmatch: true, &block)
80
+ end
81
+ rule(:a => simple(:x)) { A.new(x) }
82
+ end
83
+ let(:transform) { BumbleBee.new }
84
+
85
+ it "should evaluate rules" do
86
+ transform.apply(:a => 'a').should == A.new('a')
87
+ end
88
+
89
+ it "should raise when no rules are matched" do
90
+ lambda {
91
+ transform.apply(:z => 'z')
92
+ }.should raise_error(NotImplementedError, /Failed to match/)
93
+ end
94
+ end
95
+
96
+ context "with inheritance" do
97
+ class OptimusPrimeJunior < OptimusPrime
98
+ rule(:b => simple(:x)) { B.new(x.upcase) }
99
+ rule(:c => simple(:x)) { C.new(x) }
100
+ end
101
+ let(:transform) { OptimusPrimeJunior.new }
102
+
103
+ it "should inherit rules from its parent" do
104
+ transform.apply(:a => 'a').should == A.new('a')
105
+ end
106
+
107
+ it "should be able to override rules from its parent" do
108
+ transform.apply(:b => 'b').should == B.new('B')
109
+ end
110
+
111
+ it "should be able to define new rules" do
112
+ transform.apply(:c => 'c').should == C.new('c')
113
+ end
114
+ end
115
+ end
116
+ describe "<- #call_on_match" do
117
+ let(:bindings) { { :foo => 'test' } }
118
+ context "when given a block of arity 1" do
119
+ it "should call the block" do
120
+ called = false
121
+ transform.call_on_match(bindings, lambda do |dict|
122
+ called = true
123
+ end)
124
+
125
+ called.should == true
126
+ end
127
+ it "should yield the bindings" do
128
+ transform.call_on_match(bindings, lambda do |dict|
129
+ dict.should == bindings
130
+ end)
131
+ end
132
+ it "should execute in the current context" do
133
+ foo = 'test'
134
+ transform.call_on_match(bindings, lambda do |dict|
135
+ foo.should == 'test'
136
+ end)
137
+ end
138
+ end
139
+ context "when given a block of arity 0" do
140
+ it "should call the block" do
141
+ called = false
142
+ transform.call_on_match(bindings, proc do
143
+ called = true
144
+ end)
145
+
146
+ called.should == true
147
+ end
148
+ it "should have bindings as local variables" do
149
+ transform.call_on_match(bindings, proc do
150
+ foo.should == 'test'
151
+ end)
152
+ end
153
+ it "should execute in its own context" do
154
+ @bar = 'test'
155
+ transform.call_on_match(bindings, proc do
156
+ if instance_variable_defined?("@bar")
157
+ instance_variable_get("@bar").should_not == 'test'
158
+ end
159
+ end)
160
+ end
161
+ end
162
+ end
163
+
164
+ context "various transformations (regression)" do
165
+ context "hashes" do
166
+ it "are matched completely" do
167
+ transform.rule(:a => simple(:x)) { fail }
168
+ transform.apply(:a => 'a', :b => 'b')
169
+ end
170
+ end
171
+ end
172
+
173
+ context "when not using the bindings as hash, but as local variables" do
174
+ it "should access the variables" do
175
+ transform.rule(simple(:x)) { A.new(x) }
176
+ transform.apply('a').should == A.new('a')
177
+ end
178
+ it "should allow context as local variable" do
179
+ transform.rule(simple(:x)) { foo }
180
+ transform.apply('a', :foo => 'bar').should == 'bar'
181
+ end
182
+ end
183
+ end