cataract 0.1.3 → 0.2.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 +4 -4
- data/.github/workflows/ci-manual-rubies.yml +44 -0
- data/.overcommit.yml +1 -1
- data/.rubocop.yml +96 -4
- data/.rubocop_todo.yml +186 -0
- data/BENCHMARKS.md +62 -141
- data/CHANGELOG.md +20 -0
- data/RAGEL_MIGRATION.md +2 -2
- data/README.md +37 -4
- data/Rakefile +72 -32
- data/cataract.gemspec +4 -1
- data/ext/cataract/cataract.c +59 -50
- data/ext/cataract/cataract.h +5 -3
- data/ext/cataract/css_parser.c +173 -65
- data/ext/cataract/extconf.rb +2 -2
- data/ext/cataract/{merge.c → flatten.c} +526 -468
- data/ext/cataract/shorthand_expander.c +164 -115
- data/lib/cataract/at_rule.rb +8 -9
- data/lib/cataract/declaration.rb +18 -0
- data/lib/cataract/import_resolver.rb +63 -43
- data/lib/cataract/import_statement.rb +49 -0
- data/lib/cataract/pure/byte_constants.rb +69 -0
- data/lib/cataract/pure/flatten.rb +1145 -0
- data/lib/cataract/pure/helpers.rb +35 -0
- data/lib/cataract/pure/imports.rb +268 -0
- data/lib/cataract/pure/parser.rb +1340 -0
- data/lib/cataract/pure/serializer.rb +590 -0
- data/lib/cataract/pure/specificity.rb +206 -0
- data/lib/cataract/pure.rb +153 -0
- data/lib/cataract/rule.rb +69 -15
- data/lib/cataract/stylesheet.rb +356 -49
- data/lib/cataract/version.rb +1 -1
- data/lib/cataract.rb +43 -26
- metadata +14 -26
- 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: bcb5043777c52ed2a3f4bcd96966dcbe00e0f7bcf5ac410398afc855d59407f5
|
|
4
|
+
data.tar.gz: 65fabbfff1bbc559a0998f5a412800987ed2921252cdd2d4a757c6b59041c66a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cee4076685ff5a5e6ae8dbe441a9e7cefc8f1ad109fa7179373cb0bb39e4118b570ddfef68aeef573b002a683b3ca66148293f460b52a562472596d4d1e11842
|
|
7
|
+
data.tar.gz: de9ae8a5f1d684c797706b48e10e7372cda9ae280698e67ea3dac25be81331a901c9a181dd8ca04d2cb9ce46b2447fc0709501b0fd53c848bcc6981a4d286597
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: CI - Manual Rubies
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
test-jruby:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
steps:
|
|
10
|
+
- uses: actions/checkout@v4
|
|
11
|
+
|
|
12
|
+
- name: Set up Ruby
|
|
13
|
+
uses: ruby/setup-ruby@v1
|
|
14
|
+
with:
|
|
15
|
+
ruby-version: jruby
|
|
16
|
+
bundler-cache: true
|
|
17
|
+
|
|
18
|
+
- name: Display Ruby version
|
|
19
|
+
run: ruby --version
|
|
20
|
+
|
|
21
|
+
- name: Run tests
|
|
22
|
+
run: bundle exec rake test:pure
|
|
23
|
+
|
|
24
|
+
test-alt-rubies:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
strategy:
|
|
27
|
+
fail-fast: false
|
|
28
|
+
matrix:
|
|
29
|
+
ruby: [truffleruby, ruby-head]
|
|
30
|
+
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
|
|
34
|
+
- name: Set up Ruby
|
|
35
|
+
uses: ruby/setup-ruby@v1
|
|
36
|
+
with:
|
|
37
|
+
ruby-version: ${{ matrix.ruby }}
|
|
38
|
+
bundler-cache: true
|
|
39
|
+
|
|
40
|
+
- name: Display Ruby version
|
|
41
|
+
run: ruby --version
|
|
42
|
+
|
|
43
|
+
- name: Run tests
|
|
44
|
+
run: bundle exec rake compile test
|
data/.overcommit.yml
CHANGED
data/.rubocop.yml
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
|
2
|
+
|
|
3
|
+
require:
|
|
4
|
+
- ./.rubocop/cataract/ban_assert_includes.rb
|
|
5
|
+
|
|
1
6
|
plugins:
|
|
2
7
|
- rubocop-performance
|
|
3
8
|
- rubocop-minitest
|
|
@@ -44,6 +49,12 @@ Metrics/BlockLength:
|
|
|
44
49
|
Metrics/ParameterLists:
|
|
45
50
|
Enabled: false
|
|
46
51
|
|
|
52
|
+
# Block nesting in byte-level parsers reflects CSS structure, not complexity
|
|
53
|
+
# Extracting helper methods would add method dispatch overhead in hot path
|
|
54
|
+
Metrics/BlockNesting:
|
|
55
|
+
Exclude:
|
|
56
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
57
|
+
|
|
47
58
|
# Keep line length for tests disabled - long test strings are fine
|
|
48
59
|
Layout/LineLength:
|
|
49
60
|
Exclude:
|
|
@@ -58,11 +69,17 @@ Layout/LineLength:
|
|
|
58
69
|
Minitest/MultipleAssertions:
|
|
59
70
|
Max: 10
|
|
60
71
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
72
|
+
# Disable ambiguous block association warnings in tests
|
|
73
|
+
# Tests frequently use assert/refute with blocks that call methods with their own blocks,
|
|
74
|
+
# e.g., `assert sheet.rules.any? { |r| r == '.box { color: red; }' }`
|
|
75
|
+
# This compares Rule objects against CSS strings using our custom Rule#== implementation.
|
|
76
|
+
# While this triggers the ambiguous block warning, it's intentional and clear in context.
|
|
77
|
+
Lint/AmbiguousBlockAssociation:
|
|
78
|
+
Exclude:
|
|
64
79
|
- 'test/**/*'
|
|
65
|
-
|
|
80
|
+
|
|
81
|
+
Style/Documentation:
|
|
82
|
+
Enabled: false
|
|
66
83
|
|
|
67
84
|
# Disable modifier if/unless enforcement - use it when it's clearer, not because a cop says so
|
|
68
85
|
Style/IfUnlessModifier:
|
|
@@ -81,3 +98,78 @@ Naming/VariableNumber:
|
|
|
81
98
|
Naming/PredicatePrefix:
|
|
82
99
|
Exclude:
|
|
83
100
|
- 'lib/**/*.rb'
|
|
101
|
+
|
|
102
|
+
# Performance-critical hot path exclusions for lib/cataract/pure
|
|
103
|
+
# These cosmetic style rules add overhead via method dispatch or allocation
|
|
104
|
+
|
|
105
|
+
# Disable numeric predicates in hot path - method dispatch overhead
|
|
106
|
+
# Benchmark shows `> 0` is faster than `.positive?` due to avoiding method call
|
|
107
|
+
Style/NumericPredicate:
|
|
108
|
+
Exclude:
|
|
109
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
110
|
+
|
|
111
|
+
# Disable multiple comparison style - avoid array allocation
|
|
112
|
+
# `||` chain is faster than `Array#include?` which allocates an array
|
|
113
|
+
Style/MultipleComparison:
|
|
114
|
+
Exclude:
|
|
115
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
116
|
+
|
|
117
|
+
# Disable zero-length predicate in hot path - method dispatch overhead
|
|
118
|
+
# Direct comparison `.length > 0` is faster than `.empty?` method call
|
|
119
|
+
Style/ZeroLengthPredicate:
|
|
120
|
+
Exclude:
|
|
121
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
122
|
+
|
|
123
|
+
# Benchmarked range vs offsets. Range has to allocated
|
|
124
|
+
# so not ideal in the hot path
|
|
125
|
+
Style/SlicingWithRange:
|
|
126
|
+
Exclude:
|
|
127
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
128
|
+
|
|
129
|
+
# Disable modifier while/until style in parsing code - readability
|
|
130
|
+
# Byte-level parsing loops are clearer with traditional while...end form
|
|
131
|
+
# Example: `while i < len && ident_char?(selector.getbyte(i)); i += 1; end`
|
|
132
|
+
# is more readable than `i += 1 while i < len && ident_char?(selector.getbyte(i))`
|
|
133
|
+
Style/WhileUntilModifier:
|
|
134
|
+
Exclude:
|
|
135
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
136
|
+
|
|
137
|
+
# Disable between? suggestion in hot path - method dispatch overhead
|
|
138
|
+
# Benchmark shows `byte >= X && byte <= Y` is 2.60-4.82x faster than `.between?(X, Y)`
|
|
139
|
+
Style/ComparableBetween:
|
|
140
|
+
Exclude:
|
|
141
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
142
|
+
|
|
143
|
+
# Disable map suggestion for array building - performance
|
|
144
|
+
# Benchmark shows `each { arr << x }` is 1.05-1.11x faster than `arr = map { x }`
|
|
145
|
+
# The << pattern is faster than map's implicit array return (even without YJIT)
|
|
146
|
+
Style/MapIntoArray:
|
|
147
|
+
Exclude:
|
|
148
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
149
|
+
|
|
150
|
+
Style/OptionalBooleanParameter:
|
|
151
|
+
Exclude:
|
|
152
|
+
- 'lib/cataract/pure/**/*.rb'
|
|
153
|
+
|
|
154
|
+
Style/StderrPuts:
|
|
155
|
+
Exclude:
|
|
156
|
+
- 'scripts/**/*.rb'
|
|
157
|
+
|
|
158
|
+
# Disable all performance cops in tests - test clarity is more important than micro-optimizations
|
|
159
|
+
# Performance suggestions often hurt readability without meaningful benefit in test code
|
|
160
|
+
Performance:
|
|
161
|
+
Exclude:
|
|
162
|
+
- 'test/**/*'
|
|
163
|
+
|
|
164
|
+
Style/HashLikeCase:
|
|
165
|
+
Exclude:
|
|
166
|
+
- 'benchmarks/**/*'
|
|
167
|
+
|
|
168
|
+
# Custom Cataract cops
|
|
169
|
+
Cataract/BanAssertIncludes:
|
|
170
|
+
Enabled: true
|
|
171
|
+
Include:
|
|
172
|
+
- 'test/**/*'
|
|
173
|
+
Exclude:
|
|
174
|
+
- 'test/test_benchmark_doc_generator.rb'
|
|
175
|
+
- 'test/support/**/*' # Support files define assert_contains which uses assert_includes internally
|
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/flatten.rb'
|
|
38
|
+
|
|
39
|
+
# Offense count: 2
|
|
40
|
+
# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
|
|
41
|
+
Lint/DuplicateBranch:
|
|
42
|
+
Exclude:
|
|
43
|
+
- 'lib/cataract/pure/flatten.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/flatten.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/flatten.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/flatten.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/flatten.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/flatten.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) | 62.47K i/s | 3.59K i/s | 15.67K i/s | 4.73K i/s | 6.17K i/s |
|
|
24
|
+
| Medium CSS with @media (139 lines, 1.6KB) | 39.7K i/s | 2.05K i/s | 8.64K i/s | 2.76K i/s | 3.52K 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.4x faster (avg) |
|
|
31
|
+
| Native vs Pure (YJIT) | 4.2x 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.77K i/s | 444.3 i/s | 676.3 i/s | 37.1 i/s | 35.9 i/s |
|
|
43
|
+
| Media Type Filtering (print only) | 282.58K i/s | 114.27K i/s | 155.82K i/s | 2.67K i/s | 4.03K 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.23x faster (avg) |
|
|
50
|
+
| Native vs Pure (YJIT) | 1.82x faster (avg) |
|
|
51
|
+
| YJIT impact on Pure Ruby | 1.36x 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 | 7.35M i/s | 506.04K i/s | 2.59M i/s | 357.72K i/s | 353.42K i/s |
|
|
62
|
+
| Compound Selectors | 6.36M i/s | 184.96K i/s | 406.99K i/s | 216.59K i/s | 214.86K i/s |
|
|
63
|
+
| Combinators | 5.04M i/s | 146.91K i/s | 252.94K i/s | 185.44K i/s | 183.71K i/s |
|
|
64
|
+
| Pseudo-classes & Pseudo-elements | 5.1M i/s | 116.46K i/s | 197.06K i/s | 113.05K i/s | 113.99K i/s |
|
|
65
|
+
| :not() Pseudo-class (CSS3) | 3.23M i/s | 102.63K i/s | 161.55K i/s | 144.04K i/s | 144.53K i/s |
|
|
66
|
+
| Complex Real-world Selectors | 4.01M i/s | 52.03K i/s | 77.65K i/s | 83.91K i/s | 82.03K i/s |
|
|
92
67
|
|
|
93
|
-
|
|
68
|
+
### Speedups
|
|
94
69
|
|
|
95
|
-
|
|
70
|
+
| Comparison | Speedup |
|
|
71
|
+
|------------|---------|
|
|
72
|
+
| Native vs Pure (no YJIT) | 39.27x faster (avg) |
|
|
73
|
+
| Native vs Pure (YJIT) | 8.43x faster (avg) |
|
|
74
|
+
| YJIT impact on Pure Ruby | 3.32x 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.
|
|
103
|
-
|
|
104
|
-
Time to merge multiple CSS rule sets with same selector
|
|
105
|
-
|
|
106
|
-
| Test Case | Speedup |
|
|
107
|
-
|-----------|---------|
|
|
108
|
-
| No shorthand properties (large) | **4.14x faster** |
|
|
109
|
-
| Simple properties | **3.86x faster** |
|
|
110
|
-
| Cascade with specificity | **5.75x faster** |
|
|
111
|
-
| Important declarations | **6.1x faster** |
|
|
112
|
-
| Shorthand expansion | **4.16x faster** |
|
|
113
|
-
| Complex merging | **3.07x faster** |
|
|
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 |
|
|
78
|
+
## CSS Flattening (Cascade)
|
|
163
79
|
|
|
164
|
-
|
|
80
|
+
Time to flatten multiple CSS rule sets with same selector
|
|
165
81
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
3.
|
|
169
|
-
|
|
82
|
+
| Test Case | Native | Pure (no YJIT) | Pure (YJIT) | css_parser (no YJIT) | css_parser (YJIT) |
|
|
83
|
+
|-----------|--------|----------------|-------------|----------------------|-------------------|
|
|
84
|
+
| No shorthand properties (large) | 21.34K i/s | 3.11K i/s | 5.23K i/s | 1.58K i/s | 2.3K i/s |
|
|
85
|
+
| Simple properties | 158.81K i/s | 75.72K i/s | 102.44K i/s | 28.29K i/s | 40.72K i/s |
|
|
86
|
+
| Cascade with specificity | 204.11K i/s | 77.93K i/s | 108.77K i/s | 31.92K i/s | 46.39K i/s |
|
|
87
|
+
| Important declarations | 203.43K i/s | 77.88K i/s | 109.38K i/s | 31.25K i/s | 45.25K i/s |
|
|
88
|
+
| Shorthand expansion | 21.34K i/s | 3.11K i/s | 5.23K i/s | 1.58K i/s | 2.3K i/s |
|
|
89
|
+
| Complex merging | 30.94K i/s | 16.09K i/s | 21.57K i/s | 11.6K i/s | 16.63K i/s |
|
|
170
90
|
|
|
171
|
-
###
|
|
91
|
+
### Speedups
|
|
172
92
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
93
|
+
| Comparison | Speedup |
|
|
94
|
+
|------------|---------|
|
|
95
|
+
| Native vs Pure (no YJIT) | 3.03x faster (avg) |
|
|
96
|
+
| Native vs Pure (YJIT) | 1.72x faster (avg) |
|
|
97
|
+
| YJIT impact on Pure Ruby | 1.38x 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
|
-
rake benchmark:
|
|
191
|
-
rake benchmark:yjit
|
|
111
|
+
rake benchmark:flattening
|
|
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,23 @@
|
|
|
1
|
+
## [0.2.0] - 2025-11-14
|
|
2
|
+
|
|
3
|
+
- Major: CSS `@import` resolution refactored from string-concatenation to parsed-object architecture with proper charset handling, media query combining,
|
|
4
|
+
and circular import detection
|
|
5
|
+
- Major: Terminology change: all `merge` methods renamed to `flatten` to better represent CSS cascade behavior (old names deprecated with warnings)
|
|
6
|
+
- Major: Rule equality now considers shorthand/longhand property equivalence (e.g., `margin: 10px` equals `margin-top: 10px; margin-right: 10px; ...`)
|
|
7
|
+
- Performance: Flatten operation optimized with array-based property storage, pre-allocated frozen strings, and lazy specificity calculation
|
|
8
|
+
- Feature: New Stylesheet collection methods (`+`, `-`, `|`, `concat`, `take`, `take_while`) with cascade rules applied
|
|
9
|
+
- Feature: Added source order tracking for proper CSS cascade resolution
|
|
10
|
+
|
|
11
|
+
## [0.1.4] - 2025-11-12
|
|
12
|
+
- Major: Pure Ruby implementation added (#12)
|
|
13
|
+
- Fix: Media query serialization bugs - parentheses now preserved per CSS spec (min-width: 768px), fixed media query ordering
|
|
14
|
+
- Fix: CSS merge declaration ordering made consistent between C and pure Ruby implementations
|
|
15
|
+
- Fix: Shorthand property recreation (margin, padding, border, font, background, list-style) ordering
|
|
16
|
+
- Fix: Rule equality comparisons (Rule#==, AtRule#==)
|
|
17
|
+
|
|
18
|
+
## [0.1.3] - 2025-11-11
|
|
19
|
+
- Fix: Proper handling of at-rules (@keyframes, @font-face, etc.) during CSS merge operations
|
|
20
|
+
|
|
1
21
|
## [0.1.2] - 2025-11-11
|
|
2
22
|
|
|
3
23
|
- Fix segfault in merge
|
data/RAGEL_MIGRATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Why We Switched
|
|
4
4
|
|
|
5
|
-
Started with Ragel as an experiment for the CSS parser, but quickly moved to hand-written pure C in October
|
|
5
|
+
Started with Ragel as an experiment for the CSS parser, but quickly moved to hand-written pure C in October 2025.
|
|
6
6
|
|
|
7
7
|
### Performance
|
|
8
8
|
|
|
@@ -55,6 +55,6 @@ Pure C is more verbose than Ragel's DSL, but the performance gains and simpler b
|
|
|
55
55
|
|
|
56
56
|
## Details
|
|
57
57
|
|
|
58
|
-
- Swapped in October
|
|
58
|
+
- Swapped in October 2025
|
|
59
59
|
- Files: `css_parser.c`, `specificity.c`, `value_splitter.c`
|
|
60
60
|
- API unchanged, all tests pass
|