taurus 0.1.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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +518 -0
- data/CLAUDE.md +104 -0
- data/LICENSE.md +33 -0
- data/README.adoc +1529 -0
- data/Rakefile +7 -0
- data/TODO.impl/01-architecture.md +217 -0
- data/TODO.impl/02-ffi-declarations.md +236 -0
- data/TODO.impl/03-document-node-element-nodeset.md +382 -0
- data/TODO.impl/04-sax-parser.md +203 -0
- data/TODO.impl/05-serialize-c14n-memory-specs-css.md +276 -0
- data/benchmark/README.md +168 -0
- data/benchmark/taurus_vs_nokogiri.rb +105 -0
- data/docs/ARCHITECTURE.adoc +559 -0
- data/docs/BUILD.md +395 -0
- data/docs/ERROR_MESSAGES.md +458 -0
- data/docs/FFI_ARCHITECTURE.md +439 -0
- data/docs/FUTURE_VISION.md +303 -0
- data/docs/GITHUB_ACTIONS.md +293 -0
- data/docs/OPTIMIZATIONS_IMPLEMENTED.adoc +459 -0
- data/docs/PERFORMANCE.adoc +668 -0
- data/docs/PERFORMANCE.md +448 -0
- data/docs/RELEASE_NOTES_v1.0.0.md +515 -0
- data/docs/XPATH_SPEC_COMPLIANCE.md +298 -0
- data/docs/completion/taurus.bash +86 -0
- data/docs/completion/taurus.zsh +74 -0
- data/docs/man/taurus-format.1 +227 -0
- data/docs/man/taurus-parse.1 +178 -0
- data/docs/man/taurus-xpath.1 +312 -0
- data/docs/man/taurus.1 +160 -0
- data/docs/v0.9.0_PERFORMANCE_IMPROVEMENTS.md +217 -0
- data/docs/v0.9.0_RELEASE_SUMMARY.md +281 -0
- data/docs/v1.0.0_CONTINUATION_PLAN.md +172 -0
- data/docs/v1.0.0_CONTINUATION_PROMPT.md +382 -0
- data/docs/v1.0.0_SESSION_6_CONTINUATION.md +434 -0
- data/docs/v1.0.0_SESSION_6_PROMPT.md +231 -0
- data/docs/v1.0.0_STATUS_TRACKER.md +224 -0
- data/docs/v1.1.0_CONTINUATION_PLAN.md +299 -0
- data/docs/v1.1.0_FINAL_CONTINUATION_PLAN.md +201 -0
- data/docs/v1.1.0_SESSION_3_PROMPT.md +223 -0
- data/docs/v1.1.0_STATUS_TRACKER.md +355 -0
- data/docs/xml-performance.adoc +115 -0
- data/docs/xpath-performance.adoc +379 -0
- data/lib/taurus/version.rb +5 -0
- data/lib/taurus/xml/attr.rb +43 -0
- data/lib/taurus/xml/c14n.rb +23 -0
- data/lib/taurus/xml/cdata.rb +16 -0
- data/lib/taurus/xml/comment.rb +16 -0
- data/lib/taurus/xml/css_to_xpath.rb +177 -0
- data/lib/taurus/xml/doc_type.rb +54 -0
- data/lib/taurus/xml/document.rb +202 -0
- data/lib/taurus/xml/document_fragment.rb +42 -0
- data/lib/taurus/xml/element.rb +278 -0
- data/lib/taurus/xml/ffi.rb +420 -0
- data/lib/taurus/xml/namespace.rb +43 -0
- data/lib/taurus/xml/node.rb +221 -0
- data/lib/taurus/xml/node_set.rb +143 -0
- data/lib/taurus/xml/parse_options.rb +19 -0
- data/lib/taurus/xml/processing_instruction.rb +26 -0
- data/lib/taurus/xml/sax/document.rb +45 -0
- data/lib/taurus/xml/sax/parser.rb +148 -0
- data/lib/taurus/xml/sax.rb +12 -0
- data/lib/taurus/xml/searchable.rb +93 -0
- data/lib/taurus/xml/text.rb +16 -0
- data/lib/taurus/xml.rb +29 -0
- data/lib/taurus.rb +7 -0
- data/taurus.gemspec +42 -0
- metadata +157 -0
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
= Taurus Architecture
|
|
2
|
+
:toc:
|
|
3
|
+
:toclevels: 3
|
|
4
|
+
|
|
5
|
+
== Overview
|
|
6
|
+
|
|
7
|
+
Taurus is designed as a clean, modular XML parser and XPath engine implemented entirely in C for maximum performance. The architecture follows object-oriented principles with clear separation of concerns.
|
|
8
|
+
|
|
9
|
+
== System Architecture
|
|
10
|
+
|
|
11
|
+
=== High-Level Design
|
|
12
|
+
|
|
13
|
+
[source]
|
|
14
|
+
----
|
|
15
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
16
|
+
│ Ruby Layer │
|
|
17
|
+
│ ┌─────────────┐ ┌─────────────┐ ┌────────────────┐ │
|
|
18
|
+
│ │ Document │ │ Element │ │ AttributesHash │ │
|
|
19
|
+
│ └─────────────┘ └─────────────┘ └────────────────┘ │
|
|
20
|
+
└─────────────────────────────────────────────────────────────┘
|
|
21
|
+
▲
|
|
22
|
+
│ Ruby C Extension API
|
|
23
|
+
▼
|
|
24
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
25
|
+
│ C Extension │
|
|
26
|
+
│ ┌──────────────────────────────────────────────────────┐ │
|
|
27
|
+
│ │ XML Parser (parse.c, namespace.c, element.c) │ │
|
|
28
|
+
│ └──────────────────────────────────────────────────────┘ │
|
|
29
|
+
│ ┌──────────────────────────────────────────────────────┐ │
|
|
30
|
+
│ │ XPath Engine │ │
|
|
31
|
+
│ │ ├─ Lexer (lexer_xpath.c) │ │
|
|
32
|
+
│ │ ├─ Parser (parser_xpath.c + 3 modules) │ │
|
|
33
|
+
│ │ ├─ Evaluator (evaluator_xpath.c + 4 modules) │ │
|
|
34
|
+
│ │ ├─ Functions (xpath_functions.c) │ │
|
|
35
|
+
│ │ └─ AST Cache (xpath_ast_cache.c) │ │
|
|
36
|
+
│ └──────────────────────────────────────────────────────┘ │
|
|
37
|
+
└─────────────────────────────────────────────────────────────┘
|
|
38
|
+
----
|
|
39
|
+
|
|
40
|
+
== Module Structure
|
|
41
|
+
|
|
42
|
+
=== XML Parser Layer (Core)
|
|
43
|
+
|
|
44
|
+
==== parse.c (670 lines)
|
|
45
|
+
|
|
46
|
+
**Responsibility**: SAX-style XML parsing with DOM construction
|
|
47
|
+
|
|
48
|
+
* Tokenizes XML into elements, attributes, text
|
|
49
|
+
* Handles CDATA, comments, processing instructions
|
|
50
|
+
* Tracks namespace declarations during parsing
|
|
51
|
+
* Builds internal C DOM structures
|
|
52
|
+
* Error recovery for malformed XML
|
|
53
|
+
* SIMD-optimized for ARM NEON and x86 SSE2
|
|
54
|
+
|
|
55
|
+
**Key Optimizations**:
|
|
56
|
+
|
|
57
|
+
* Character classification lookup table (256 bytes)
|
|
58
|
+
* SIMD vectorization for whitespace skipping
|
|
59
|
+
* SIMD vectorization for name parsing
|
|
60
|
+
* Zero-copy string handling where possible
|
|
61
|
+
|
|
62
|
+
==== namespace.c (104 lines)
|
|
63
|
+
|
|
64
|
+
**Responsibility**: Namespace management
|
|
65
|
+
|
|
66
|
+
* Create/free namespace structures
|
|
67
|
+
* Prefix-to-URI resolution
|
|
68
|
+
* Namespace inheritance chain traversal
|
|
69
|
+
* Scope management
|
|
70
|
+
|
|
71
|
+
==== element.c (98 lines)
|
|
72
|
+
|
|
73
|
+
**Responsibility**: Element structure management
|
|
74
|
+
|
|
75
|
+
* Create/free element structures
|
|
76
|
+
* Parent-child relationships
|
|
77
|
+
* Attribute storage
|
|
78
|
+
* Memory management
|
|
79
|
+
|
|
80
|
+
==== taurus.h (103 lines)
|
|
81
|
+
|
|
82
|
+
**Responsibility**: Shared declarations for parser
|
|
83
|
+
|
|
84
|
+
* Element and attribute structure definitions
|
|
85
|
+
* Function declarations
|
|
86
|
+
* Ruby integration macros
|
|
87
|
+
|
|
88
|
+
=== XPath Lexer Layer
|
|
89
|
+
|
|
90
|
+
==== lexer_xpath.c (538 lines)
|
|
91
|
+
|
|
92
|
+
**Responsibility**: Tokenization of XPath expressions
|
|
93
|
+
|
|
94
|
+
* Recognizes 47 token types
|
|
95
|
+
* Context-aware tokenization (operators vs names)
|
|
96
|
+
* String literal handling with escape sequences
|
|
97
|
+
* Number literal parsing
|
|
98
|
+
* Axis, node test, and operator recognition
|
|
99
|
+
* Error reporting with line/column information
|
|
100
|
+
|
|
101
|
+
**Token Types**:
|
|
102
|
+
|
|
103
|
+
* Operators: `+`, `-`, `*`, `div`, `mod`, `=`, `!=`, `<`, `<=`, `>`, `>=`, `and`, `or`, `|`
|
|
104
|
+
* Delimiters: `(`, `)`, `[`, `]`, `,`, `::`, `/`, `//`
|
|
105
|
+
* Literals: String, Number
|
|
106
|
+
* Identifiers: NCName, QName
|
|
107
|
+
* Axes: `child::`, `descendant::`, etc.
|
|
108
|
+
* Node tests: `node()`, `text()`, `comment()`, `processing-instruction()`
|
|
109
|
+
* Special: `.`, `..`, `@`, `*`
|
|
110
|
+
|
|
111
|
+
=== XPath Parser Layer
|
|
112
|
+
|
|
113
|
+
==== parser_xpath.c (230 lines)
|
|
114
|
+
|
|
115
|
+
**Responsibility**: Core parser coordination
|
|
116
|
+
|
|
117
|
+
* Entry point for parsing
|
|
118
|
+
* Error handling and reporting
|
|
119
|
+
* Memory management
|
|
120
|
+
* AST root construction
|
|
121
|
+
|
|
122
|
+
==== xpath_parser_expressions.c (425 lines)
|
|
123
|
+
|
|
124
|
+
**Responsibility**: Expression parsing
|
|
125
|
+
|
|
126
|
+
* OrExpr → AndExpr → EqualityExpr chain (operator precedence)
|
|
127
|
+
* RelationalExpr, AdditiveExpr, MultiplicativeExpr
|
|
128
|
+
* UnaryExpr (negation)
|
|
129
|
+
* UnionExpr (`|`)
|
|
130
|
+
* FilterExpr (predicates on expressions)
|
|
131
|
+
* PrimaryExpr (literals, variables, function calls, grouped expressions)
|
|
132
|
+
|
|
133
|
+
**Operator Precedence** (lowest to highest):
|
|
134
|
+
|
|
135
|
+
1. `or`
|
|
136
|
+
2. `and`
|
|
137
|
+
3. `=`, `!=`
|
|
138
|
+
4. `<`, `<=`, `>`, `>=`
|
|
139
|
+
5. `+`, `-`
|
|
140
|
+
6. `*`, `div`, `mod`
|
|
141
|
+
7. unary `-`
|
|
142
|
+
8. union (`|`)
|
|
143
|
+
|
|
144
|
+
==== xpath_parser_paths.c (265 lines)
|
|
145
|
+
|
|
146
|
+
**Responsibility**: Path/step parsing
|
|
147
|
+
|
|
148
|
+
* LocationPath (absolute `/` vs relative)
|
|
149
|
+
* Step parsing (axis + node test + predicates)
|
|
150
|
+
* Axis specifier recognition (all 13 axes)
|
|
151
|
+
* Abbreviated syntax handling (`.` → self, `..` → parent, `@` → attribute)
|
|
152
|
+
* Function call parsing
|
|
153
|
+
|
|
154
|
+
==== xpath_parser_node_tests.c (80 lines)
|
|
155
|
+
|
|
156
|
+
**Responsibility**: Node tests and predicates
|
|
157
|
+
|
|
158
|
+
* NameTest (element names)
|
|
159
|
+
* NodeType tests (`node()`, `text()`, `comment()`, `processing-instruction()`)
|
|
160
|
+
* Wildcard tests (`*`, `prefix:*`)
|
|
161
|
+
* Predicate parsing (`[...]`)
|
|
162
|
+
|
|
163
|
+
==== xpath_parser_internal.h (69 lines)
|
|
164
|
+
|
|
165
|
+
**Responsibility**: Shared parser infrastructure
|
|
166
|
+
|
|
167
|
+
* Parser context structure
|
|
168
|
+
* Token traversal utilities
|
|
169
|
+
* Peek/advance functions
|
|
170
|
+
* Expect/match helpers
|
|
171
|
+
* AST node creation utilities
|
|
172
|
+
|
|
173
|
+
=== XPath Evaluator Layer
|
|
174
|
+
|
|
175
|
+
==== evaluator_xpath.c (419 lines)
|
|
176
|
+
|
|
177
|
+
**Responsibility**: Core evaluation coordination
|
|
178
|
+
|
|
179
|
+
* Context management (position, size)
|
|
180
|
+
* Result type conversions
|
|
181
|
+
* Main evaluation dispatcher
|
|
182
|
+
* Path expression evaluation (location paths)
|
|
183
|
+
* Step evaluation (axis + node test + predicates)
|
|
184
|
+
* Function call dispatching
|
|
185
|
+
|
|
186
|
+
**Evaluation Flow**:
|
|
187
|
+
|
|
188
|
+
1. Parse → AST (or retrieve from cache)
|
|
189
|
+
2. Create evaluation context (document, context node, position, size)
|
|
190
|
+
3. Evaluate AST recursively
|
|
191
|
+
4. Apply type conversions
|
|
192
|
+
5. Return typed result (Boolean, Number, String, NodeSet)
|
|
193
|
+
|
|
194
|
+
==== xpath_axes.c (411 lines)
|
|
195
|
+
|
|
196
|
+
**Responsibility**: All 13 axis implementations
|
|
197
|
+
|
|
198
|
+
* `child` - Direct children
|
|
199
|
+
* `descendant` - All descendants (recursive)
|
|
200
|
+
* `descendant-or-self` - Context + descendants
|
|
201
|
+
* `parent` - Parent node
|
|
202
|
+
* `ancestor` - All ancestors
|
|
203
|
+
* `ancestor-or-self` - Context + ancestors
|
|
204
|
+
* `self` - Context node only
|
|
205
|
+
* `following-sibling` - Siblings after context
|
|
206
|
+
* `preceding-sibling` - Siblings before context
|
|
207
|
+
* `following` - All nodes after context in document order
|
|
208
|
+
* `preceding` - All nodes before context in document order
|
|
209
|
+
* `attribute` - Attributes of context node
|
|
210
|
+
* `namespace` - Namespace nodes (stub)
|
|
211
|
+
|
|
212
|
+
**Document Order**: All axes maintain proper document order automatically
|
|
213
|
+
|
|
214
|
+
==== xpath_operators.c (312 lines)
|
|
215
|
+
|
|
216
|
+
**Responsibility**: All operator implementations
|
|
217
|
+
|
|
218
|
+
* Logical: `or`, `and`
|
|
219
|
+
* Equality: `=`, `!=`
|
|
220
|
+
* Relational: `<`, `<=`, `>`, `>=`
|
|
221
|
+
* Arithmetic: `+`, `-`, `*`, `div`, `mod`
|
|
222
|
+
* Union: `|`
|
|
223
|
+
* Negation: unary `-`
|
|
224
|
+
|
|
225
|
+
**Type Conversions**: Automatic conversions per XPath 1.0 specification
|
|
226
|
+
|
|
227
|
+
==== xpath_node_test.c (99 lines)
|
|
228
|
+
|
|
229
|
+
**Responsibility**: Node matching logic
|
|
230
|
+
|
|
231
|
+
* Name matching (exact, wildcard, namespace-aware)
|
|
232
|
+
* Type matching (element, attribute)
|
|
233
|
+
* Result filtering based on node test
|
|
234
|
+
|
|
235
|
+
==== xpath_predicates.c (110 lines)
|
|
236
|
+
|
|
237
|
+
**Responsibility**: Predicate evaluation
|
|
238
|
+
|
|
239
|
+
* Position predicates: `[1]`, `[N]`, `[last()]`
|
|
240
|
+
* Boolean predicates: `[@attr]`, `[element]`
|
|
241
|
+
* Multiple predicates: `[1][@id]` (sequential)
|
|
242
|
+
* Context tracking (position/size)
|
|
243
|
+
|
|
244
|
+
==== xpath_functions.c (189 lines)
|
|
245
|
+
|
|
246
|
+
**Responsibility**: Function registry and implementations
|
|
247
|
+
|
|
248
|
+
* Function registry (extensible)
|
|
249
|
+
* Function lookup by name
|
|
250
|
+
* Argument validation
|
|
251
|
+
* All 27 XPath 1.0 functions
|
|
252
|
+
|
|
253
|
+
==== xpath_ast_cache.c (173 lines) - NEW in v0.1.0
|
|
254
|
+
|
|
255
|
+
**Responsibility**: AST caching for performance
|
|
256
|
+
|
|
257
|
+
* Global hash-based cache (64 buckets)
|
|
258
|
+
* O(1) cache lookup
|
|
259
|
+
* 256 entry limit (~154KB maximum)
|
|
260
|
+
* Stores optimized ASTs
|
|
261
|
+
* LRU-like eviction when full
|
|
262
|
+
|
|
263
|
+
**Performance Impact**: 80% speedup (95µs → 18µs) for repeated queries
|
|
264
|
+
|
|
265
|
+
== Data Flow
|
|
266
|
+
|
|
267
|
+
=== XML Parsing Flow
|
|
268
|
+
|
|
269
|
+
[source]
|
|
270
|
+
----
|
|
271
|
+
XML String
|
|
272
|
+
↓
|
|
273
|
+
[Lexical Analysis - parse.c]
|
|
274
|
+
↓
|
|
275
|
+
C Element Structures
|
|
276
|
+
↓
|
|
277
|
+
[Namespace Resolution - namespace.c]
|
|
278
|
+
↓
|
|
279
|
+
Namespace-Aware DOM
|
|
280
|
+
↓
|
|
281
|
+
[Ruby Conversion - taurus.c]
|
|
282
|
+
↓
|
|
283
|
+
Ruby Document/Element Objects
|
|
284
|
+
----
|
|
285
|
+
|
|
286
|
+
=== XPath Evaluation Flow
|
|
287
|
+
|
|
288
|
+
[source]
|
|
289
|
+
----
|
|
290
|
+
XPath Expression String
|
|
291
|
+
↓
|
|
292
|
+
[AST Cache Lookup - xpath_ast_cache.c]
|
|
293
|
+
│
|
|
294
|
+
├─ Cache Hit → Optimized AST
|
|
295
|
+
│
|
|
296
|
+
└─ Cache Miss ↓
|
|
297
|
+
[Tokenization - lexer_xpath.c]
|
|
298
|
+
↓
|
|
299
|
+
Token Stream
|
|
300
|
+
↓
|
|
301
|
+
[Parsing - parser_xpath.c + modules]
|
|
302
|
+
↓
|
|
303
|
+
Abstract Syntax Tree (AST)
|
|
304
|
+
↓
|
|
305
|
+
[AST Optimization - Session 66]
|
|
306
|
+
↓
|
|
307
|
+
Optimized AST → Store in Cache
|
|
308
|
+
↓
|
|
309
|
+
[Evaluation - evaluator_xpath.c + modules]
|
|
310
|
+
│
|
|
311
|
+
├─ [Axis Traversal - xpath_axes.c]
|
|
312
|
+
├─ [Node Testing - xpath_node_test.c]
|
|
313
|
+
├─ [Predicate Filtering - xpath_predicates.c]
|
|
314
|
+
├─ [Operator Evaluation - xpath_operators.c]
|
|
315
|
+
└─ [Function Calls - xpath_functions.c]
|
|
316
|
+
↓
|
|
317
|
+
XPathResult (typed)
|
|
318
|
+
↓
|
|
319
|
+
[Ruby Conversion]
|
|
320
|
+
↓
|
|
321
|
+
Ruby Array/Value
|
|
322
|
+
----
|
|
323
|
+
|
|
324
|
+
== Design Principles
|
|
325
|
+
|
|
326
|
+
=== MECE (Mutually Exclusive, Collectively Exhaustive)
|
|
327
|
+
|
|
328
|
+
* Each module has distinct, non-overlapping responsibility
|
|
329
|
+
* Together, all modules cover complete functionality
|
|
330
|
+
* No gaps, no redundancy
|
|
331
|
+
|
|
332
|
+
=== Single Responsibility
|
|
333
|
+
|
|
334
|
+
* Each file handles one cohesive concern
|
|
335
|
+
* Functions are focused and do one thing well
|
|
336
|
+
* Clear separation between:
|
|
337
|
+
** Parsing vs evaluation
|
|
338
|
+
** Axis traversal vs node testing
|
|
339
|
+
** Operator logic vs predicate logic
|
|
340
|
+
|
|
341
|
+
=== Open/Closed Principle
|
|
342
|
+
|
|
343
|
+
* Modules are open for extension (e.g., function registry)
|
|
344
|
+
* Closed for modification (core logic is stable)
|
|
345
|
+
* New functions added via registry without modifying core
|
|
346
|
+
|
|
347
|
+
=== DRY (Don't Repeat Yourself)
|
|
348
|
+
|
|
349
|
+
* Common utilities in shared headers
|
|
350
|
+
* Type conversions centralized
|
|
351
|
+
* Node traversal patterns reused
|
|
352
|
+
|
|
353
|
+
=== No Code Guards
|
|
354
|
+
|
|
355
|
+
* Uses architectural solutions instead of preprocessor conditionals
|
|
356
|
+
* Different modules/functions instead of `#ifdef`
|
|
357
|
+
* Higher-level design over compilation tricks
|
|
358
|
+
|
|
359
|
+
== Memory Management
|
|
360
|
+
|
|
361
|
+
=== Allocation Strategy
|
|
362
|
+
|
|
363
|
+
* Uses Ruby's memory macros (`ALLOC`, `ALLOC_N`, `REALLOC_N`, `FREE`)
|
|
364
|
+
* Integrates with Ruby's garbage collector
|
|
365
|
+
* Automatic cleanup on object destruction
|
|
366
|
+
|
|
367
|
+
=== Key Principles
|
|
368
|
+
|
|
369
|
+
* **Zero leaks**: All allocations have corresponding frees
|
|
370
|
+
* **Ownership**: Clear ownership of memory (context owns results, etc.)
|
|
371
|
+
* **Minimal allocations**: Reuse structures where safe
|
|
372
|
+
* **Dynamic growth**: Arrays grow 2× to reduce reallocation
|
|
373
|
+
|
|
374
|
+
=== Critical Structures
|
|
375
|
+
|
|
376
|
+
* `XPathContext`: Created per evaluation, freed after
|
|
377
|
+
* `XPathResult`: Created by evaluation, freed by caller
|
|
378
|
+
* `XPathNodeSet`: Dynamically sized, freed with result
|
|
379
|
+
* `XPathASTNode`: Created by parser, cached or freed after evaluation
|
|
380
|
+
* `XPathASTCache`: Global singleton, cleaned up on process exit
|
|
381
|
+
|
|
382
|
+
== Performance Optimizations
|
|
383
|
+
|
|
384
|
+
=== SIMD Vectorization (Session 48)
|
|
385
|
+
|
|
386
|
+
**Techniques**:
|
|
387
|
+
|
|
388
|
+
* ARM NEON implementation for Apple Silicon
|
|
389
|
+
* x86 SSE2 implementation for Intel/AMD
|
|
390
|
+
* Scalar fallback for other platforms
|
|
391
|
+
|
|
392
|
+
**Optimized Operations**:
|
|
393
|
+
|
|
394
|
+
* Whitespace detection and skipping
|
|
395
|
+
* Character classification
|
|
396
|
+
* Name parsing (element/attribute names)
|
|
397
|
+
* Namespace prefix detection
|
|
398
|
+
|
|
399
|
+
**Impact**: 300% parsing speedup (24.2µs → 6.0µs)
|
|
400
|
+
|
|
401
|
+
=== Character Classification Tables (Session 58)
|
|
402
|
+
|
|
403
|
+
**Technique** (inspired by pugixml):
|
|
404
|
+
|
|
405
|
+
* 256-byte lookup table for character classes
|
|
406
|
+
* Zero branch mispredictions
|
|
407
|
+
* Perfect cache locality
|
|
408
|
+
* Branch probability ordering
|
|
409
|
+
|
|
410
|
+
**Impact**: 78% parsing speedup (6.0µs → 5.87µs)
|
|
411
|
+
|
|
412
|
+
=== AST Pattern Optimization (Session 66)
|
|
413
|
+
|
|
414
|
+
**Technique**:
|
|
415
|
+
|
|
416
|
+
* Rewrites inefficient patterns before evaluation
|
|
417
|
+
* Example: `//foo` → `/descendant::foo` (eliminates redundant step)
|
|
418
|
+
* Applied at parse time, not runtime
|
|
419
|
+
|
|
420
|
+
**Impact**: 8-10× XPath speedup (~900µs → 95µs)
|
|
421
|
+
|
|
422
|
+
=== AST Caching (Session 67)
|
|
423
|
+
|
|
424
|
+
**Technique**:
|
|
425
|
+
|
|
426
|
+
* Global hash-based cache with 64 buckets
|
|
427
|
+
* O(1) lookup for cache hits
|
|
428
|
+
* Stores already-optimized ASTs
|
|
429
|
+
* Parse once, use forever for repeated queries
|
|
430
|
+
|
|
431
|
+
**Impact**: 5.2× XPath speedup (95µs → 18µs), **now faster than Nokogiri!**
|
|
432
|
+
|
|
433
|
+
== Extension Points
|
|
434
|
+
|
|
435
|
+
=== Adding a New XPath Function
|
|
436
|
+
|
|
437
|
+
1. **Define handler** in `xpath_functions.c`:
|
|
438
|
+
+
|
|
439
|
+
[source,c]
|
|
440
|
+
----
|
|
441
|
+
static XPathResult xpath_func_myfunction(
|
|
442
|
+
XPathContext context,
|
|
443
|
+
XPathASTNode* args,
|
|
444
|
+
size_t arg_count
|
|
445
|
+
) {
|
|
446
|
+
// Implement function logic
|
|
447
|
+
// Return typed XPathResult
|
|
448
|
+
}
|
|
449
|
+
----
|
|
450
|
+
|
|
451
|
+
2. **Register** in `xpath_function_registry_init_standard()`:
|
|
452
|
+
+
|
|
453
|
+
[source,c]
|
|
454
|
+
----
|
|
455
|
+
xpath_function_registry_register(
|
|
456
|
+
registry,
|
|
457
|
+
"myfunction", // Name
|
|
458
|
+
xpath_func_myfunction, // Handler
|
|
459
|
+
1, // Min args
|
|
460
|
+
3 // Max args (-1 for unlimited)
|
|
461
|
+
);
|
|
462
|
+
----
|
|
463
|
+
|
|
464
|
+
3. **Test** in `spec/taurus/element_xpath_spec.rb`
|
|
465
|
+
|
|
466
|
+
=== Adding a New Axis
|
|
467
|
+
|
|
468
|
+
1. **Implement** in `xpath_axes.c`:
|
|
469
|
+
+
|
|
470
|
+
[source,c]
|
|
471
|
+
----
|
|
472
|
+
static VALUE axis_myaxis(VALUE context_node, XPathASTNode* node_test) {
|
|
473
|
+
VALUE result = rb_ary_new();
|
|
474
|
+
// Traverse and collect nodes
|
|
475
|
+
return result;
|
|
476
|
+
}
|
|
477
|
+
----
|
|
478
|
+
|
|
479
|
+
2. **Register** in `apply_axis()` switch statement
|
|
480
|
+
|
|
481
|
+
3. **Test** comprehensively with proper document order
|
|
482
|
+
|
|
483
|
+
== Testing Architecture
|
|
484
|
+
|
|
485
|
+
[source]
|
|
486
|
+
----
|
|
487
|
+
spec/ - Ruby tests (RSpec)
|
|
488
|
+
├── taurus/
|
|
489
|
+
│ ├── element_xpath_spec.rb - 250 XPath integration tests
|
|
490
|
+
│ ├── namespace_spec.rb - Namespace functionality
|
|
491
|
+
│ ├── element_spec.rb - Element API tests
|
|
492
|
+
│ └── ...
|
|
493
|
+
│
|
|
494
|
+
test/ - C unit tests (Google Test)
|
|
495
|
+
├── test_parser.cc - 25 parser tests
|
|
496
|
+
├── test_evaluator_*.cc - 57 evaluator tests
|
|
497
|
+
└── ...
|
|
498
|
+
----
|
|
499
|
+
|
|
500
|
+
== Critical Paths
|
|
501
|
+
|
|
502
|
+
=== Hot Path: Simple XPath Query
|
|
503
|
+
|
|
504
|
+
[source]
|
|
505
|
+
----
|
|
506
|
+
user code → Element#xpath()
|
|
507
|
+
↓
|
|
508
|
+
xpath_evaluate_expr() [Ruby C API]
|
|
509
|
+
↓
|
|
510
|
+
AST Cache Lookup
|
|
511
|
+
↓
|
|
512
|
+
xpath_evaluate() [Core dispatcher]
|
|
513
|
+
↓
|
|
514
|
+
evaluate_location_path() [Path handler]
|
|
515
|
+
↓
|
|
516
|
+
evaluate_step() [Per-step evaluation]
|
|
517
|
+
↓
|
|
518
|
+
apply_axis() [Node traversal]
|
|
519
|
+
↓
|
|
520
|
+
match_node_test() [Filtering]
|
|
521
|
+
↓
|
|
522
|
+
XPathNodeSet → Ruby Array
|
|
523
|
+
----
|
|
524
|
+
|
|
525
|
+
=== Performance Considerations
|
|
526
|
+
|
|
527
|
+
* Minimize Ruby↔C boundary crossings
|
|
528
|
+
* Batch operations where possible
|
|
529
|
+
* Early-exit for empty nodesets (step-aware, not axis-aware)
|
|
530
|
+
* Document order maintained implicitly (no sorting needed)
|
|
531
|
+
* AST caching eliminates parsing overhead for repeated queries
|
|
532
|
+
|
|
533
|
+
== Future Enhancements
|
|
534
|
+
|
|
535
|
+
=== Planned (v0.2.0+)
|
|
536
|
+
|
|
537
|
+
1. Namespace prefixes in XPath queries (`//ns:element`)
|
|
538
|
+
2. XPath 2.0 functions
|
|
539
|
+
3. Streaming API for large documents
|
|
540
|
+
4. Multi-threaded parsing (with GVL management)
|
|
541
|
+
|
|
542
|
+
=== Possible (v0.3.0+)
|
|
543
|
+
|
|
544
|
+
* XSLT 1.0 support (separate gem: taurus-xslt)
|
|
545
|
+
* XML Schema validation
|
|
546
|
+
* XPath 3.0 features
|
|
547
|
+
* JRuby support
|
|
548
|
+
|
|
549
|
+
== Conclusion
|
|
550
|
+
|
|
551
|
+
Taurus achieves its performance through careful architectural design:
|
|
552
|
+
|
|
553
|
+
* **Modular structure** - All files under 700 lines, clear responsibilities
|
|
554
|
+
* **SIMD optimization** - Leverages modern CPU features
|
|
555
|
+
* **Smart caching** - AST caching eliminates repeated parsing
|
|
556
|
+
* **Clean C code** - No external dependencies, pure C implementation
|
|
557
|
+
* **Comprehensive testing** - 469 tests ensure correctness
|
|
558
|
+
|
|
559
|
+
The result is an XML parser that matches Ox for parsing speed while providing complete XPath 1.0 support that exceeds Nokogiri's performance.
|