cataract 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci-manual-rubies.yml +27 -0
- data/.overcommit.yml +1 -1
- data/.rubocop.yml +62 -0
- data/.rubocop_todo.yml +186 -0
- data/BENCHMARKS.md +60 -139
- data/CHANGELOG.md +14 -0
- data/README.md +30 -2
- data/Rakefile +49 -22
- data/cataract.gemspec +4 -1
- data/ext/cataract/cataract.c +47 -47
- data/ext/cataract/css_parser.c +17 -33
- data/ext/cataract/merge.c +58 -2
- data/lib/cataract/at_rule.rb +8 -9
- data/lib/cataract/declaration.rb +18 -0
- data/lib/cataract/import_resolver.rb +3 -4
- data/lib/cataract/pure/byte_constants.rb +69 -0
- data/lib/cataract/pure/helpers.rb +35 -0
- data/lib/cataract/pure/imports.rb +255 -0
- data/lib/cataract/pure/merge.rb +1146 -0
- data/lib/cataract/pure/parser.rb +1236 -0
- data/lib/cataract/pure/serializer.rb +590 -0
- data/lib/cataract/pure/specificity.rb +206 -0
- data/lib/cataract/pure.rb +130 -0
- data/lib/cataract/rule.rb +22 -13
- data/lib/cataract/stylesheet.rb +14 -9
- data/lib/cataract/version.rb +1 -1
- data/lib/cataract.rb +18 -5
- metadata +12 -25
- data/benchmarks/benchmark_harness.rb +0 -193
- data/benchmarks/benchmark_merging.rb +0 -121
- data/benchmarks/benchmark_optimization_comparison.rb +0 -168
- data/benchmarks/benchmark_parsing.rb +0 -153
- data/benchmarks/benchmark_ragel_removal.rb +0 -56
- data/benchmarks/benchmark_runner.rb +0 -70
- data/benchmarks/benchmark_serialization.rb +0 -180
- data/benchmarks/benchmark_shorthand.rb +0 -109
- data/benchmarks/benchmark_shorthand_expansion.rb +0 -176
- data/benchmarks/benchmark_specificity.rb +0 -124
- data/benchmarks/benchmark_string_allocation.rb +0 -151
- data/benchmarks/benchmark_stylesheet_to_s.rb +0 -62
- data/benchmarks/benchmark_to_s_cached.rb +0 -55
- data/benchmarks/benchmark_value_splitter.rb +0 -54
- data/benchmarks/benchmark_yjit.rb +0 -158
- data/benchmarks/benchmark_yjit_workers.rb +0 -61
- data/benchmarks/profile_to_s.rb +0 -23
- data/benchmarks/speedup_calculator.rb +0 -83
- data/benchmarks/system_metadata.rb +0 -81
- data/benchmarks/templates/benchmarks.md.erb +0 -221
- data/benchmarks/yjit_tests.rb +0 -141
- data/scripts/fuzzer/run.rb +0 -828
- data/scripts/fuzzer/worker.rb +0 -99
- data/scripts/generate_benchmarks_md.rb +0 -155
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b8c9aa122ff45945bef0a05d6fa6a5dcfd46252ffe5970bf288074db5d3514c9
|
|
4
|
+
data.tar.gz: cdafcb1ca599a58449dc03c239e5b8891f4399629dbdf7ca4e3ef23fb7f6ab94
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 35bbffc26c5eb757a90935fd0caf362818d80cc33af0860c738061ca3d0047f6b6d5c8124087d8283c0bb93b26699990279f98af96215456099a493d1e4720ee
|
|
7
|
+
data.tar.gz: 567ffe7ce56412c5913384ce8b6470b9d8d0e7e880463e2024a6d4cd6fca4bcb9b792ce0ca890aea1edf07ed218497a8369058f38b8b30ab94bee63694ef03e9
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: CI - Manual Rubies
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
test-alt-rubies:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
strategy:
|
|
10
|
+
fail-fast: false
|
|
11
|
+
matrix:
|
|
12
|
+
ruby: [jruby, truffleruby]
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Set up Ruby
|
|
18
|
+
uses: ruby/setup-ruby@v1
|
|
19
|
+
with:
|
|
20
|
+
ruby-version: ${{ matrix.ruby }}
|
|
21
|
+
bundler-cache: true
|
|
22
|
+
|
|
23
|
+
- name: Display Ruby version
|
|
24
|
+
run: ruby --version
|
|
25
|
+
|
|
26
|
+
- name: Run tests
|
|
27
|
+
run: bundle exec rake compile test
|
data/.overcommit.yml
CHANGED
data/.rubocop.yml
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
|
2
|
+
|
|
1
3
|
plugins:
|
|
2
4
|
- rubocop-performance
|
|
3
5
|
- rubocop-minitest
|
|
@@ -44,6 +46,12 @@ Metrics/BlockLength:
|
|
|
44
46
|
Metrics/ParameterLists:
|
|
45
47
|
Enabled: false
|
|
46
48
|
|
|
49
|
+
# Block nesting in byte-level parsers reflects CSS structure, not complexity
|
|
50
|
+
# Extracting helper methods would add method dispatch overhead in hot path
|
|
51
|
+
Metrics/BlockNesting:
|
|
52
|
+
Exclude:
|
|
53
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
54
|
+
|
|
47
55
|
# Keep line length for tests disabled - long test strings are fine
|
|
48
56
|
Layout/LineLength:
|
|
49
57
|
Exclude:
|
|
@@ -63,6 +71,8 @@ Style/Documentation:
|
|
|
63
71
|
Exclude:
|
|
64
72
|
- 'test/**/*'
|
|
65
73
|
- 'benchmarks/**/*'
|
|
74
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
75
|
+
- 'lib/cataract/pure.rb'
|
|
66
76
|
|
|
67
77
|
# Disable modifier if/unless enforcement - use it when it's clearer, not because a cop says so
|
|
68
78
|
Style/IfUnlessModifier:
|
|
@@ -81,3 +91,55 @@ Naming/VariableNumber:
|
|
|
81
91
|
Naming/PredicatePrefix:
|
|
82
92
|
Exclude:
|
|
83
93
|
- 'lib/**/*.rb'
|
|
94
|
+
|
|
95
|
+
# Performance-critical hot path exclusions for lib/cataract/pure
|
|
96
|
+
# These cosmetic style rules add overhead via method dispatch or allocation
|
|
97
|
+
|
|
98
|
+
# Disable numeric predicates in hot path - method dispatch overhead
|
|
99
|
+
# Benchmark shows `> 0` is faster than `.positive?` due to avoiding method call
|
|
100
|
+
Style/NumericPredicate:
|
|
101
|
+
Exclude:
|
|
102
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
103
|
+
|
|
104
|
+
# Disable multiple comparison style - avoid array allocation
|
|
105
|
+
# `||` chain is faster than `Array#include?` which allocates an array
|
|
106
|
+
Style/MultipleComparison:
|
|
107
|
+
Exclude:
|
|
108
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
109
|
+
|
|
110
|
+
# Disable zero-length predicate in hot path - method dispatch overhead
|
|
111
|
+
# Direct comparison `.length > 0` is faster than `.empty?` method call
|
|
112
|
+
Style/ZeroLengthPredicate:
|
|
113
|
+
Exclude:
|
|
114
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
115
|
+
|
|
116
|
+
# Benchmarked range vs offsets. Range has to allocated
|
|
117
|
+
# so not ideal in the hot path
|
|
118
|
+
Style/SlicingWithRange:
|
|
119
|
+
Exclude:
|
|
120
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
121
|
+
|
|
122
|
+
# Disable modifier while/until style in parsing code - readability
|
|
123
|
+
# Byte-level parsing loops are clearer with traditional while...end form
|
|
124
|
+
# Example: `while i < len && ident_char?(selector.getbyte(i)); i += 1; end`
|
|
125
|
+
# is more readable than `i += 1 while i < len && ident_char?(selector.getbyte(i))`
|
|
126
|
+
Style/WhileUntilModifier:
|
|
127
|
+
Exclude:
|
|
128
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
129
|
+
|
|
130
|
+
# Disable between? suggestion in hot path - method dispatch overhead
|
|
131
|
+
# Benchmark shows `byte >= X && byte <= Y` is 2.60-4.82x faster than `.between?(X, Y)`
|
|
132
|
+
Style/ComparableBetween:
|
|
133
|
+
Exclude:
|
|
134
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
135
|
+
|
|
136
|
+
# Disable map suggestion for array building - performance
|
|
137
|
+
# Benchmark shows `each { arr << x }` is 1.05-1.11x faster than `arr = map { x }`
|
|
138
|
+
# The << pattern is faster than map's implicit array return (even without YJIT)
|
|
139
|
+
Style/MapIntoArray:
|
|
140
|
+
Exclude:
|
|
141
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
142
|
+
|
|
143
|
+
Style/OptionalBooleanParameter:
|
|
144
|
+
Exclude:
|
|
145
|
+
- 'lib/cataract/pure/**/*.rb'
|
data/.rubocop_todo.yml
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# This configuration was generated by
|
|
2
|
+
# `rubocop --auto-gen-config`
|
|
3
|
+
# on 2025-11-12 02:24:55 UTC using RuboCop version 1.81.7.
|
|
4
|
+
# The point is for the user to remove these configuration records
|
|
5
|
+
# one by one as the offenses are removed from the code base.
|
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
|
8
|
+
|
|
9
|
+
# Offense count: 13
|
|
10
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
11
|
+
Layout/EmptyLineAfterGuardClause:
|
|
12
|
+
Exclude:
|
|
13
|
+
- 'benchmarks/benchmark_merging.rb'
|
|
14
|
+
- 'benchmarks/benchmark_parsing.rb'
|
|
15
|
+
- 'benchmarks/benchmark_serialization.rb'
|
|
16
|
+
- 'benchmarks/benchmark_specificity.rb'
|
|
17
|
+
- 'scripts/generate_benchmarks_md.rb'
|
|
18
|
+
|
|
19
|
+
# Offense count: 1
|
|
20
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
21
|
+
Layout/EmptyLinesAfterModuleInclusion:
|
|
22
|
+
Exclude:
|
|
23
|
+
- 'benchmarks/parsing_tests.rb'
|
|
24
|
+
|
|
25
|
+
# Offense count: 1
|
|
26
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
27
|
+
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
|
28
|
+
# SupportedStyles: aligned, indented
|
|
29
|
+
Layout/MultilineOperationIndentation:
|
|
30
|
+
Exclude:
|
|
31
|
+
- 'lib/cataract/pure/helpers.rb'
|
|
32
|
+
|
|
33
|
+
# Offense count: 1
|
|
34
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
35
|
+
Lint/AmbiguousOperatorPrecedence:
|
|
36
|
+
Exclude:
|
|
37
|
+
- 'lib/cataract/pure/merge.rb'
|
|
38
|
+
|
|
39
|
+
# Offense count: 2
|
|
40
|
+
# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
|
|
41
|
+
Lint/DuplicateBranch:
|
|
42
|
+
Exclude:
|
|
43
|
+
- 'lib/cataract/pure/merge.rb'
|
|
44
|
+
|
|
45
|
+
# Offense count: 1
|
|
46
|
+
Lint/IneffectiveAccessModifier:
|
|
47
|
+
Exclude:
|
|
48
|
+
- 'benchmarks/speedup_calculator.rb'
|
|
49
|
+
|
|
50
|
+
# Offense count: 1
|
|
51
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
52
|
+
# Configuration parameters: ContextCreatingMethods, MethodCreatingMethods.
|
|
53
|
+
Lint/UselessAccessModifier:
|
|
54
|
+
Exclude:
|
|
55
|
+
- 'benchmarks/speedup_calculator.rb'
|
|
56
|
+
|
|
57
|
+
# Offense count: 12
|
|
58
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
59
|
+
Lint/UselessAssignment:
|
|
60
|
+
Exclude:
|
|
61
|
+
- 'benchmarks/benchmark_merging.rb'
|
|
62
|
+
- 'benchmarks/benchmark_parsing.rb'
|
|
63
|
+
- 'benchmarks/benchmark_serialization.rb'
|
|
64
|
+
- 'benchmarks/benchmark_specificity.rb'
|
|
65
|
+
|
|
66
|
+
# Offense count: 1
|
|
67
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
68
|
+
Minitest/EmptyLineBeforeAssertionMethods:
|
|
69
|
+
Exclude:
|
|
70
|
+
- 'test/test_stylesheet.rb'
|
|
71
|
+
|
|
72
|
+
# Offense count: 7
|
|
73
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
74
|
+
Performance/UnfreezeString:
|
|
75
|
+
Exclude:
|
|
76
|
+
- 'lib/cataract/pure.rb'
|
|
77
|
+
- 'lib/cataract/pure/merge.rb'
|
|
78
|
+
- 'lib/cataract/pure/parser.rb'
|
|
79
|
+
|
|
80
|
+
# Offense count: 4
|
|
81
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
82
|
+
# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions.
|
|
83
|
+
# SupportedStyles: assign_to_condition, assign_inside_condition
|
|
84
|
+
Style/ConditionalAssignment:
|
|
85
|
+
Exclude:
|
|
86
|
+
- 'lib/cataract/pure/imports.rb'
|
|
87
|
+
- 'lib/cataract/pure/merge.rb'
|
|
88
|
+
|
|
89
|
+
# Offense count: 4
|
|
90
|
+
# Configuration parameters: AllowedConstants.
|
|
91
|
+
Style/Documentation:
|
|
92
|
+
Exclude:
|
|
93
|
+
- 'test/**/*'
|
|
94
|
+
- 'benchmarks/**/*'
|
|
95
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
96
|
+
- 'lib/cataract/pure.rb'
|
|
97
|
+
- 'lib/cataract/at_rule.rb'
|
|
98
|
+
- 'lib/cataract/declaration.rb'
|
|
99
|
+
- 'lib/cataract/rule.rb'
|
|
100
|
+
|
|
101
|
+
# Offense count: 4
|
|
102
|
+
# Configuration parameters: MinBranchesCount.
|
|
103
|
+
Style/HashLikeCase:
|
|
104
|
+
Exclude:
|
|
105
|
+
- 'benchmarks/merging_tests.rb'
|
|
106
|
+
- 'benchmarks/parsing_tests.rb'
|
|
107
|
+
- 'benchmarks/serialization_tests.rb'
|
|
108
|
+
- 'benchmarks/specificity_tests.rb'
|
|
109
|
+
|
|
110
|
+
# Offense count: 14
|
|
111
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
112
|
+
Style/IdenticalConditionalBranches:
|
|
113
|
+
Exclude:
|
|
114
|
+
- 'benchmarks/benchmark_merging.rb'
|
|
115
|
+
- 'benchmarks/benchmark_parsing.rb'
|
|
116
|
+
- 'benchmarks/benchmark_serialization.rb'
|
|
117
|
+
- 'benchmarks/benchmark_specificity.rb'
|
|
118
|
+
- 'lib/cataract/pure/parser.rb'
|
|
119
|
+
- 'lib/cataract/pure/serializer.rb'
|
|
120
|
+
|
|
121
|
+
# Offense count: 5
|
|
122
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
123
|
+
# Configuration parameters: AllowIfModifier.
|
|
124
|
+
Style/IfInsideElse:
|
|
125
|
+
Exclude:
|
|
126
|
+
- 'lib/cataract/pure/merge.rb'
|
|
127
|
+
- 'lib/cataract/pure/serializer.rb'
|
|
128
|
+
- 'lib/cataract/pure/specificity.rb'
|
|
129
|
+
|
|
130
|
+
# Offense count: 1
|
|
131
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
132
|
+
Style/NegatedIfElseCondition:
|
|
133
|
+
Exclude:
|
|
134
|
+
- 'lib/cataract/pure/merge.rb'
|
|
135
|
+
|
|
136
|
+
# Offense count: 1
|
|
137
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
138
|
+
Style/RedundantAssignment:
|
|
139
|
+
Exclude:
|
|
140
|
+
- 'lib/cataract/pure/specificity.rb'
|
|
141
|
+
|
|
142
|
+
# Offense count: 2
|
|
143
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
144
|
+
Style/RedundantParentheses:
|
|
145
|
+
Exclude:
|
|
146
|
+
- 'lib/cataract/pure/imports.rb'
|
|
147
|
+
- 'lib/cataract/pure/parser.rb'
|
|
148
|
+
|
|
149
|
+
# Offense count: 1
|
|
150
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
151
|
+
Style/RedundantRegexpArgument:
|
|
152
|
+
Exclude:
|
|
153
|
+
- 'test/test_stylesheet.rb'
|
|
154
|
+
|
|
155
|
+
# Offense count: 3
|
|
156
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
157
|
+
# Configuration parameters: AllowModifier.
|
|
158
|
+
Style/SoleNestedConditional:
|
|
159
|
+
Exclude:
|
|
160
|
+
- 'lib/cataract/pure/merge.rb'
|
|
161
|
+
- 'lib/cataract/pure/parser.rb'
|
|
162
|
+
|
|
163
|
+
# Offense count: 3
|
|
164
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
165
|
+
# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
|
|
166
|
+
# SupportedStyles: single_quotes, double_quotes
|
|
167
|
+
Style/StringLiterals:
|
|
168
|
+
Exclude:
|
|
169
|
+
- 'benchmarks/merging_tests.rb'
|
|
170
|
+
- 'scripts/generate_benchmarks_md.rb'
|
|
171
|
+
- 'test/test_stylesheet.rb'
|
|
172
|
+
|
|
173
|
+
# Offense count: 1
|
|
174
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
175
|
+
# Configuration parameters: .
|
|
176
|
+
# SupportedStyles: percent, brackets
|
|
177
|
+
Style/SymbolArray:
|
|
178
|
+
EnforcedStyle: percent
|
|
179
|
+
MinSize: 3
|
|
180
|
+
|
|
181
|
+
# Offense count: 4
|
|
182
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
183
|
+
# Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
|
|
184
|
+
# URISchemes: http, https
|
|
185
|
+
Layout/LineLength:
|
|
186
|
+
Max: 184
|
data/BENCHMARKS.md
CHANGED
|
@@ -4,176 +4,97 @@
|
|
|
4
4
|
|
|
5
5
|
# Performance Benchmarks
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Performance comparison between Cataract's C extension and pure Ruby implementations, with css_parser as a reference.
|
|
8
8
|
|
|
9
9
|
## Test Environment
|
|
10
10
|
|
|
11
|
-
- **Ruby**: ruby 3.4.
|
|
11
|
+
- **Ruby**: ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +YJIT +PRISM [arm64-darwin23]
|
|
12
12
|
- **CPU**: Apple M1 Pro
|
|
13
13
|
- **Memory**: 32GB
|
|
14
14
|
- **OS**: macOS 14.5
|
|
15
|
-
- **Generated**: 2025-
|
|
15
|
+
- **Generated**: 2025-11-11T16:02:08-06:00
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
<summary><h2>CSS Parsing</h2></summary>
|
|
19
|
-
|
|
20
|
-
Performance of parsing CSS into internal data structures.
|
|
17
|
+
## CSS Parsing
|
|
21
18
|
|
|
22
19
|
Time to parse CSS into internal data structures
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
|
28
|
-
|--------|-------|-------------------|
|
|
29
|
-
| css_parser | 6.16K i/s | 162.34 μs |
|
|
30
|
-
| **Cataract** | **63.79K i/s** | **15.68 μs** |
|
|
31
|
-
| **Speedup** | **10.36x faster** | |
|
|
32
|
-
|
|
33
|
-
### Medium CSS with @media (139 lines, 1.6KB)
|
|
34
|
-
|
|
21
|
+
| Test Case | Native | Pure (no YJIT) | Pure (YJIT) | css_parser (no YJIT) | css_parser (YJIT) |
|
|
22
|
+
|-----------|--------|----------------|-------------|----------------------|-------------------|
|
|
23
|
+
| Small CSS (64 lines, 1.0KB) | 63.2K i/s | 3.56K i/s | 15.49K i/s | 4.7K i/s | 6.39K i/s |
|
|
24
|
+
| Medium CSS with @media (139 lines, 1.6KB) | 40.14K i/s | 2.07K i/s | 8.76K i/s | 2.81K i/s | 3.54K i/s |
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
|--------|-------|-------------------|
|
|
38
|
-
| css_parser | 3.44K i/s | 290.64 μs |
|
|
39
|
-
| **Cataract** | **41.45K i/s** | **24.13 μs** |
|
|
40
|
-
| **Speedup** | **12.05x faster** | |
|
|
26
|
+
### Speedups
|
|
41
27
|
|
|
42
|
-
|
|
43
|
-
|
|
28
|
+
| Comparison | Speedup |
|
|
29
|
+
|------------|---------|
|
|
30
|
+
| Native vs Pure (no YJIT) | 18.58x faster (avg) |
|
|
31
|
+
| Native vs Pure (YJIT) | 4.26x faster (avg) |
|
|
32
|
+
| YJIT impact on Pure Ruby | 4.31x faster (avg) |
|
|
44
33
|
|
|
45
34
|
---
|
|
46
35
|
|
|
47
|
-
|
|
48
|
-
<summary><h2>CSS Serialization (to_s)</h2></summary>
|
|
49
|
-
|
|
50
|
-
Performance of converting parsed CSS back to string format.
|
|
36
|
+
## CSS Serialization
|
|
51
37
|
|
|
52
38
|
Time to convert parsed CSS back to string format
|
|
53
39
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
|
58
|
-
|--------|-------|-------------------|
|
|
59
|
-
| css_parser | 34.0 i/s | 29.41 ms |
|
|
60
|
-
| **Cataract** | **714.8 i/s** | **1.4 ms** |
|
|
61
|
-
| **Speedup** | **21.02x faster** | |
|
|
40
|
+
| Test Case | Native | Pure (no YJIT) | Pure (YJIT) | css_parser (no YJIT) | css_parser (YJIT) |
|
|
41
|
+
|-----------|--------|----------------|-------------|----------------------|-------------------|
|
|
42
|
+
| Full Serialization (Bootstrap CSS - 191KB) | 1.79K i/s | 442.2 i/s | 716.9 i/s | 38.5 i/s | 39.7 i/s |
|
|
43
|
+
| Media Type Filtering (print only) | 282.98K i/s | 114.55K i/s | 167.51K i/s | 2.74K i/s | 4.32K i/s |
|
|
62
44
|
|
|
63
|
-
###
|
|
45
|
+
### Speedups
|
|
64
46
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
| **Speedup** | **57.26x faster** | |
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
</details>
|
|
47
|
+
| Comparison | Speedup |
|
|
48
|
+
|------------|---------|
|
|
49
|
+
| Native vs Pure (no YJIT) | 3.26x faster (avg) |
|
|
50
|
+
| Native vs Pure (YJIT) | 1.69x faster (avg) |
|
|
51
|
+
| YJIT impact on Pure Ruby | 1.46x faster (avg) |
|
|
74
52
|
|
|
75
53
|
---
|
|
76
54
|
|
|
77
|
-
|
|
78
|
-
<summary><h2>Specificity Calculation</h2></summary>
|
|
79
|
-
|
|
80
|
-
Performance of calculating CSS selector specificity values.
|
|
55
|
+
## Specificity Calculation
|
|
81
56
|
|
|
82
57
|
Time to calculate CSS selector specificity values
|
|
83
58
|
|
|
84
|
-
| Test Case |
|
|
85
|
-
|
|
86
|
-
| Simple Selectors |
|
|
87
|
-
| Compound Selectors |
|
|
88
|
-
| Combinators |
|
|
89
|
-
| Pseudo-classes & Pseudo-elements |
|
|
90
|
-
| :not() Pseudo-class (CSS3) |
|
|
91
|
-
| Complex Real-world Selectors |
|
|
59
|
+
| Test Case | Native | Pure (no YJIT) | Pure (YJIT) | css_parser (no YJIT) | css_parser (YJIT) |
|
|
60
|
+
|-----------|--------|----------------|-------------|----------------------|-------------------|
|
|
61
|
+
| Simple Selectors | 8.31M i/s | 499.15K i/s | 2.59M i/s | 370.14K i/s | 344.42K i/s |
|
|
62
|
+
| Compound Selectors | 6.57M i/s | 184.61K i/s | 403.41K i/s | 223.76K i/s | 204.19K i/s |
|
|
63
|
+
| Combinators | 5.24M i/s | 144.7K i/s | 253.9K i/s | 188.05K i/s | 177.88K i/s |
|
|
64
|
+
| Pseudo-classes & Pseudo-elements | 5.39M i/s | 115.46K i/s | 194.13K i/s | 116.96K i/s | 109.64K i/s |
|
|
65
|
+
| :not() Pseudo-class (CSS3) | 3.31M i/s | 101.48K i/s | 160.62K i/s | 144.35K i/s | 141.24K i/s |
|
|
66
|
+
| Complex Real-world Selectors | 4.09M i/s | 51.63K i/s | 77.37K i/s | 82.16K i/s | 80.6K i/s |
|
|
92
67
|
|
|
93
|
-
|
|
68
|
+
### Speedups
|
|
94
69
|
|
|
95
|
-
|
|
70
|
+
| Comparison | Speedup |
|
|
71
|
+
|------------|---------|
|
|
72
|
+
| Native vs Pure (no YJIT) | 41.16x faster (avg) |
|
|
73
|
+
| Native vs Pure (YJIT) | 8.94x faster (avg) |
|
|
74
|
+
| YJIT impact on Pure Ruby | 3.36x faster (avg) |
|
|
96
75
|
|
|
97
76
|
---
|
|
98
77
|
|
|
99
|
-
|
|
100
|
-
<summary><h2>CSS Merging</h2></summary>
|
|
101
|
-
|
|
102
|
-
Performance of merging multiple CSS rule sets with the same selector.
|
|
78
|
+
## CSS Merging
|
|
103
79
|
|
|
104
80
|
Time to merge multiple CSS rule sets with same selector
|
|
105
81
|
|
|
106
|
-
| Test Case |
|
|
107
|
-
|
|
108
|
-
| No shorthand properties (large) |
|
|
109
|
-
| Simple properties |
|
|
110
|
-
| Cascade with specificity |
|
|
111
|
-
| Important declarations |
|
|
112
|
-
| Shorthand expansion |
|
|
113
|
-
| Complex merging |
|
|
114
|
-
|
|
115
|
-
**Summary:** 3.07x faster to 6.1x faster (avg 4.51x faster)
|
|
116
|
-
|
|
117
|
-
### What's Being Tested
|
|
118
|
-
- Specificity-based CSS cascade (ID > class > element)
|
|
119
|
-
- `!important` declaration handling
|
|
120
|
-
- Shorthand property expansion (e.g., `margin` → `margin-top`, `margin-right`, etc.)
|
|
121
|
-
- Shorthand property creation from longhand properties
|
|
122
|
-
|
|
123
|
-
</details>
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
<details>
|
|
128
|
-
<summary><h2>YJIT Impact</h2></summary>
|
|
129
|
-
|
|
130
|
-
Impact of Ruby's YJIT JIT compiler on Ruby-side operations. The C extension performance is the same regardless of YJIT.
|
|
131
|
-
|
|
132
|
-
Ruby-side operations with and without YJIT
|
|
133
|
-
|
|
134
|
-
### Operations Per Second
|
|
135
|
-
|
|
136
|
-
| Operation | Without YJIT | With YJIT | YJIT Improvement |
|
|
137
|
-
|-----------|--------------|-----------|------------------|
|
|
138
|
-
| property access | 227.18K i/s | 322.32K i/s | **1.42x faster** (42% faster) |
|
|
139
|
-
| declaration merging | 204.26K i/s | 337.81K i/s | **1.65x faster** (65% faster) |
|
|
140
|
-
| to_s generation | 242.66K i/s | 391.16K i/s | **1.61x faster** (61% faster) |
|
|
141
|
-
| parse + iterate | 121.52K i/s | 142.77K i/s | **1.17x faster** (17% faster) |
|
|
142
|
-
|
|
143
|
-
### Key Takeaways
|
|
144
|
-
- YJIT provides significant performance boost for Ruby-side operations
|
|
145
|
-
- Greatest impact on declaration merging
|
|
146
|
-
- Parse + iterate benefits least since most work is in C
|
|
147
|
-
- Recommended: Enable YJIT in production (`--yjit` flag or `RUBY_YJIT_ENABLE=1`)
|
|
148
|
-
|
|
149
|
-
</details>
|
|
150
|
-
|
|
151
|
-
---
|
|
152
|
-
|
|
153
|
-
## Summary
|
|
154
|
-
|
|
155
|
-
### Performance Highlights
|
|
156
|
-
|
|
157
|
-
| Category | Min Speedup | Max Speedup | Avg Speedup |
|
|
158
|
-
|----------|-------------|-------------|-------------|
|
|
159
|
-
| **Parsing** | 10.36x faster | 12.05x faster | 11.2x faster |
|
|
160
|
-
| **Serialization** | 21.02x faster | 57.26x faster | 39.14x faster |
|
|
161
|
-
| **Specificity** | 22.03x faster | 49.17x faster | 33.3x faster |
|
|
162
|
-
| **Merging** | 3.07x faster | 6.1x faster | 4.51x faster |
|
|
163
|
-
|
|
164
|
-
### Implementation Notes
|
|
165
|
-
|
|
166
|
-
1. **C Extension**: Critical paths (parsing, specificity, merging, serialization) implemented in C
|
|
167
|
-
2. **Efficient Data Structures**: Rules grouped by media query for O(1) lookups
|
|
168
|
-
3. **Memory Efficient**: Pre-allocated string buffers, minimal Ruby object allocations
|
|
169
|
-
4. **Optimized Algorithms**: Purpose-built CSS specificity calculator
|
|
82
|
+
| Test Case | Native | Pure (no YJIT) | Pure (YJIT) | css_parser (no YJIT) | css_parser (YJIT) |
|
|
83
|
+
|-----------|--------|----------------|-------------|----------------------|-------------------|
|
|
84
|
+
| No shorthand properties (large) | 10.5K i/s | 3.46K i/s | 6.73K i/s | 1.51K i/s | 2.27K i/s |
|
|
85
|
+
| Simple properties | 125.51K i/s | 77.36K i/s | 110.86K i/s | 27.92K i/s | 40.42K i/s |
|
|
86
|
+
| Cascade with specificity | 140.46K i/s | 80.08K i/s | 116.35K i/s | 31.48K i/s | 45.99K i/s |
|
|
87
|
+
| Important declarations | 139.59K i/s | 80.29K i/s | 116.39K i/s | 30.84K i/s | 44.87K i/s |
|
|
88
|
+
| Shorthand expansion | 10.5K i/s | 3.46K i/s | 6.73K i/s | 1.51K i/s | 2.27K i/s |
|
|
89
|
+
| Complex merging | 24.74K i/s | 16.62K i/s | 23.57K i/s | 11.59K i/s | 16.48K i/s |
|
|
170
90
|
|
|
171
|
-
###
|
|
91
|
+
### Speedups
|
|
172
92
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
93
|
+
| Comparison | Speedup |
|
|
94
|
+
|------------|---------|
|
|
95
|
+
| Native vs Pure (no YJIT) | 1.87x faster (avg) |
|
|
96
|
+
| Native vs Pure (YJIT) | 1.16x faster (avg) |
|
|
97
|
+
| YJIT impact on Pure Ruby | 1.45x faster (avg) |
|
|
177
98
|
|
|
178
99
|
---
|
|
179
100
|
|
|
@@ -181,14 +102,13 @@ Ruby-side operations with and without YJIT
|
|
|
181
102
|
|
|
182
103
|
```bash
|
|
183
104
|
# All benchmarks
|
|
184
|
-
rake benchmark
|
|
105
|
+
rake benchmark
|
|
185
106
|
|
|
186
107
|
# Individual benchmarks
|
|
187
108
|
rake benchmark:parsing
|
|
188
109
|
rake benchmark:serialization
|
|
189
110
|
rake benchmark:specificity
|
|
190
111
|
rake benchmark:merging
|
|
191
|
-
rake benchmark:yjit
|
|
192
112
|
|
|
193
113
|
# Generate documentation
|
|
194
114
|
rake benchmark:generate_docs
|
|
@@ -196,6 +116,7 @@ rake benchmark:generate_docs
|
|
|
196
116
|
|
|
197
117
|
## Notes
|
|
198
118
|
|
|
199
|
-
-
|
|
200
|
-
- Measurements
|
|
201
|
-
- css_parser gem
|
|
119
|
+
- Benchmarks use benchmark-ips with 1-2s warmup and 2-5s measurement periods
|
|
120
|
+
- Measurements show median iterations per second (i/s)
|
|
121
|
+
- css_parser gem is included for reference comparison
|
|
122
|
+
- YJIT is enabled/disabled per subprocess for accurate comparison
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [0.1.4] - 2025-11-12
|
|
2
|
+
- Major: Pure Ruby implementation added (#12)
|
|
3
|
+
- Fix: Media query serialization bugs - parentheses now preserved per CSS spec (min-width: 768px), fixed media query ordering
|
|
4
|
+
- Fix: CSS merge declaration ordering made consistent between C and pure Ruby implementations
|
|
5
|
+
- Fix: Shorthand property recreation (margin, padding, border, font, background, list-style) ordering
|
|
6
|
+
- Fix: Rule equality comparisons (Rule#==, AtRule#==)
|
|
7
|
+
|
|
8
|
+
## [0.1.3] - 2025-11-11
|
|
9
|
+
- Fix: Proper handling of at-rules (@keyframes, @font-face, etc.) during CSS merge operations
|
|
10
|
+
|
|
11
|
+
## [0.1.2] - 2025-11-11
|
|
12
|
+
|
|
13
|
+
- Fix segfault in merge
|
|
14
|
+
|
|
1
15
|
## [0.1.1] - 2025-11-09
|
|
2
16
|
|
|
3
17
|
- Fix bugs with Stylesheet#merge resulting in wrong results (#11)
|
data/README.md
CHANGED
|
@@ -34,6 +34,32 @@ gem install cataract
|
|
|
34
34
|
|
|
35
35
|
- Ruby >= 3.1.0
|
|
36
36
|
|
|
37
|
+
### Pure Ruby Implementation
|
|
38
|
+
|
|
39
|
+
Cataract includes a pure Ruby implementation alongside the C extension. This is useful for:
|
|
40
|
+
- Platforms where C extensions cannot be compiled
|
|
41
|
+
- Development/debugging without needing to recompile C code
|
|
42
|
+
- Environments with restricted native code execution
|
|
43
|
+
|
|
44
|
+
**In your Gemfile:**
|
|
45
|
+
```ruby
|
|
46
|
+
gem 'cataract', require: 'cataract/pure'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Or set environment variable:**
|
|
50
|
+
```ruby
|
|
51
|
+
# For CI, testing, or one-off usage
|
|
52
|
+
ENV['CATARACT_PURE'] = '1'
|
|
53
|
+
require 'cataract'
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Check which implementation is loaded:**
|
|
57
|
+
```ruby
|
|
58
|
+
Cataract::IMPLEMENTATION # => :native or :ruby
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Note:** The pure Ruby implementation does not include color conversion functionality (`convert_colors!`), which is only available in the C extension.
|
|
62
|
+
|
|
37
63
|
## Usage
|
|
38
64
|
|
|
39
65
|
### Basic Parsing
|
|
@@ -142,9 +168,11 @@ See [BENCHMARKS.md](BENCHMARKS.md) for detailed performance comparisons.
|
|
|
142
168
|
|
|
143
169
|
## CSS Support
|
|
144
170
|
|
|
145
|
-
Cataract
|
|
171
|
+
Cataract parses and preserves all standard CSS including:
|
|
146
172
|
- **Selectors**: All CSS2/CSS3 selectors (type, class, ID, attribute, pseudo-classes, pseudo-elements, combinators)
|
|
147
|
-
- **At-rules**:
|
|
173
|
+
- **At-rules**:
|
|
174
|
+
- **`@media`**: Special handling with indexing and filtering API (`with_media(:print)`, `with_media(:all)`)
|
|
175
|
+
- **Others** (`@font-face`, `@keyframes`, `@supports`, `@page`, `@layer`, `@container`, `@property`, `@scope`, `@counter-style`): Parsed and preserved as-is (pass-through)
|
|
148
176
|
- **Media Queries**: Full support including nested queries and media features
|
|
149
177
|
- **Special syntax**: Data URIs, `calc()`, `url()`, CSS functions with parentheses
|
|
150
178
|
- **!important**: Full support with correct cascade behavior
|