expressir 2.2.0 → 2.3.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/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +254 -77
- data/Gemfile +4 -1
- data/README.adoc +63 -26
- data/benchmark/srl_benchmark.rb +386 -0
- data/benchmark/srl_native_benchmark.rb +142 -0
- data/benchmark/srl_ruby_benchmark.rb +130 -0
- data/expressir.gemspec +4 -2
- data/lib/expressir/benchmark.rb +1 -1
- data/lib/expressir/changes/item_change.rb +0 -1
- data/lib/expressir/changes/mapping_change.rb +0 -1
- data/lib/expressir/changes/schema_change.rb +0 -2
- data/lib/expressir/changes/version_change.rb +0 -3
- data/lib/expressir/changes.rb +5 -6
- data/lib/expressir/cli.rb +10 -24
- data/lib/expressir/commands/base.rb +2 -9
- data/lib/expressir/commands/changes.rb +0 -2
- data/lib/expressir/commands/changes_import_eengine.rb +0 -3
- data/lib/expressir/commands/changes_validate.rb +0 -2
- data/lib/expressir/commands/format.rb +5 -3
- data/lib/expressir/commands/manifest.rb +0 -7
- data/lib/expressir/commands/package.rb +93 -101
- data/lib/expressir/commands/validate.rb +0 -2
- data/lib/expressir/commands/validate_ascii.rb +2 -4
- data/lib/expressir/commands/validate_load.rb +8 -5
- data/lib/expressir/commands.rb +20 -0
- data/lib/expressir/config.rb +0 -2
- data/lib/expressir/coverage.rb +11 -4
- data/lib/expressir/eengine/arm_compare_report.rb +1 -4
- data/lib/expressir/eengine/changes_section.rb +1 -3
- data/lib/expressir/eengine/compare_report.rb +1 -13
- data/lib/expressir/eengine/mim_compare_report.rb +1 -4
- data/lib/expressir/eengine/modified_object.rb +1 -2
- data/lib/expressir/eengine.rb +9 -0
- data/lib/expressir/errors.rb +113 -0
- data/lib/expressir/express/builder.rb +22 -7
- data/lib/expressir/express/builder_registry.rb +411 -0
- data/lib/expressir/express/builders/attribute_decl_builder.rb +0 -6
- data/lib/expressir/express/builders/built_in_builder.rb +1 -16
- data/lib/expressir/express/builders/constant_builder.rb +4 -19
- data/lib/expressir/express/builders/declaration_builder.rb +0 -4
- data/lib/expressir/express/builders/derive_clause_builder.rb +0 -2
- data/lib/expressir/express/builders/derived_attr_builder.rb +0 -2
- data/lib/expressir/express/builders/domain_rule_builder.rb +0 -2
- data/lib/expressir/express/builders/entity_decl_builder.rb +7 -13
- data/lib/expressir/express/builders/explicit_attr_builder.rb +5 -8
- data/lib/expressir/express/builders/expression_builder.rb +31 -67
- data/lib/expressir/express/builders/function_decl_builder.rb +20 -18
- data/lib/expressir/express/builders/interface_builder.rb +0 -20
- data/lib/expressir/express/builders/inverse_attr_builder.rb +0 -2
- data/lib/expressir/express/builders/inverse_attr_type_builder.rb +0 -6
- data/lib/expressir/express/builders/inverse_clause_builder.rb +0 -2
- data/lib/expressir/express/builders/literal_builder.rb +1 -15
- data/lib/expressir/express/builders/procedure_decl_builder.rb +20 -19
- data/lib/expressir/express/builders/qualifier_builder.rb +0 -27
- data/lib/expressir/express/builders/reference_builder.rb +1 -10
- data/lib/expressir/express/builders/rule_decl_builder.rb +21 -19
- data/lib/expressir/express/builders/schema_body_decl_builder.rb +0 -4
- data/lib/expressir/express/builders/schema_decl_builder.rb +7 -13
- data/lib/expressir/express/builders/schema_version_builder.rb +0 -6
- data/lib/expressir/express/builders/simple_id_builder.rb +1 -10
- data/lib/expressir/express/builders/statement_builder.rb +4 -32
- data/lib/expressir/express/builders/subtype_constraint_builder.rb +6 -30
- data/lib/expressir/express/builders/syntax_builder.rb +60 -7
- data/lib/expressir/express/builders/type_builder.rb +3 -45
- data/lib/expressir/express/builders/type_decl_builder.rb +1 -7
- data/lib/expressir/express/builders/unique_clause_builder.rb +1 -3
- data/lib/expressir/express/builders/unique_rule_builder.rb +0 -2
- data/lib/expressir/express/builders/where_clause_builder.rb +1 -3
- data/lib/expressir/express/builders.rb +47 -35
- data/lib/expressir/express/error.rb +0 -3
- data/lib/expressir/express/formatter.rb +17 -19
- data/lib/expressir/express/formatters/data_types_formatter.rb +295 -293
- data/lib/expressir/express/formatters/declarations_formatter.rb +617 -615
- data/lib/expressir/express/formatters/expressions_formatter.rb +146 -144
- data/lib/expressir/express/formatters/literals_formatter.rb +35 -33
- data/lib/expressir/express/formatters/references_formatter.rb +34 -32
- data/lib/expressir/express/formatters/remark_formatter.rb +176 -209
- data/lib/expressir/express/formatters/remark_item_formatter.rb +18 -16
- data/lib/expressir/express/formatters/statements_formatter.rb +190 -188
- data/lib/expressir/express/formatters/supertype_expressions_formatter.rb +41 -39
- data/lib/expressir/express/formatters.rb +22 -0
- data/lib/expressir/express/parser.rb +40 -41
- data/lib/expressir/express/pretty_formatter.rb +68 -47
- data/lib/expressir/express/remark_attacher.rb +210 -147
- data/lib/expressir/express/streaming_builder.rb +0 -3
- data/lib/expressir/express/transformer/remark_handling.rb +1 -2
- data/lib/expressir/express.rb +29 -0
- data/lib/expressir/manifest/resolver.rb +0 -3
- data/lib/expressir/manifest/validator.rb +0 -3
- data/lib/expressir/manifest.rb +6 -0
- data/lib/expressir/model/cache.rb +1 -1
- data/lib/expressir/model/concerns.rb +19 -0
- data/lib/expressir/model/data_types/aggregate.rb +1 -1
- data/lib/expressir/model/data_types/array.rb +1 -1
- data/lib/expressir/model/data_types/bag.rb +1 -1
- data/lib/expressir/model/data_types/binary.rb +1 -1
- data/lib/expressir/model/data_types/boolean.rb +1 -1
- data/lib/expressir/model/data_types/enumeration.rb +1 -1
- data/lib/expressir/model/data_types/enumeration_item.rb +1 -1
- data/lib/expressir/model/data_types/generic.rb +1 -1
- data/lib/expressir/model/data_types/generic_entity.rb +1 -1
- data/lib/expressir/model/data_types/integer.rb +1 -1
- data/lib/expressir/model/data_types/list.rb +1 -1
- data/lib/expressir/model/data_types/logical.rb +1 -1
- data/lib/expressir/model/data_types/number.rb +1 -1
- data/lib/expressir/model/data_types/real.rb +1 -1
- data/lib/expressir/model/data_types/select.rb +1 -1
- data/lib/expressir/model/data_types/set.rb +1 -1
- data/lib/expressir/model/data_types/string.rb +1 -1
- data/lib/expressir/model/data_types.rb +25 -0
- data/lib/expressir/model/declarations/attribute.rb +1 -1
- data/lib/expressir/model/declarations/constant.rb +1 -1
- data/lib/expressir/model/declarations/derived_attribute.rb +1 -1
- data/lib/expressir/model/declarations/entity.rb +4 -1
- data/lib/expressir/model/declarations/function.rb +3 -1
- data/lib/expressir/model/declarations/informal_proposition_rule.rb +2 -1
- data/lib/expressir/model/declarations/interface.rb +1 -1
- data/lib/expressir/model/declarations/interface_item.rb +1 -1
- data/lib/expressir/model/declarations/interfaced_item.rb +1 -1
- data/lib/expressir/model/declarations/inverse_attribute.rb +1 -1
- data/lib/expressir/model/declarations/parameter.rb +1 -1
- data/lib/expressir/model/declarations/procedure.rb +3 -1
- data/lib/expressir/model/declarations/remark_item.rb +1 -1
- data/lib/expressir/model/declarations/rule.rb +4 -1
- data/lib/expressir/model/declarations/schema.rb +2 -1
- data/lib/expressir/model/declarations/schema_version.rb +1 -1
- data/lib/expressir/model/declarations/schema_version_item.rb +1 -1
- data/lib/expressir/model/declarations/subtype_constraint.rb +1 -1
- data/lib/expressir/model/declarations/type.rb +4 -1
- data/lib/expressir/model/declarations/unique_rule.rb +1 -1
- data/lib/expressir/model/declarations/variable.rb +1 -1
- data/lib/expressir/model/declarations/where_rule.rb +1 -1
- data/lib/expressir/model/declarations.rb +31 -0
- data/lib/expressir/model/dependency_resolver.rb +0 -2
- data/lib/expressir/model/exp_file.rb +38 -0
- data/lib/expressir/model/expressions/aggregate_initializer.rb +1 -1
- data/lib/expressir/model/expressions/aggregate_initializer_item.rb +1 -1
- data/lib/expressir/model/expressions/binary_expression.rb +1 -1
- data/lib/expressir/model/expressions/entity_constructor.rb +1 -1
- data/lib/expressir/model/expressions/function_call.rb +1 -1
- data/lib/expressir/model/expressions/interval.rb +1 -1
- data/lib/expressir/model/expressions/query_expression.rb +1 -1
- data/lib/expressir/model/expressions/unary_expression.rb +1 -1
- data/lib/expressir/model/expressions.rb +18 -0
- data/lib/expressir/model/identifier.rb +5 -1
- data/lib/expressir/model/indexes.rb +11 -0
- data/lib/expressir/model/literals/binary.rb +1 -1
- data/lib/expressir/model/literals/integer.rb +1 -1
- data/lib/expressir/model/literals/logical.rb +1 -1
- data/lib/expressir/model/literals/real.rb +1 -1
- data/lib/expressir/model/literals/string.rb +1 -1
- data/lib/expressir/model/literals.rb +13 -0
- data/lib/expressir/model/model_element.rb +7 -15
- data/lib/expressir/model/references/attribute_reference.rb +1 -1
- data/lib/expressir/model/references/group_reference.rb +1 -1
- data/lib/expressir/model/references/index_reference.rb +1 -1
- data/lib/expressir/model/references/simple_reference.rb +1 -1
- data/lib/expressir/model/references.rb +12 -0
- data/lib/expressir/model/remark_info.rb +1 -7
- data/lib/expressir/model/repository.rb +72 -36
- data/lib/expressir/model/repository_validator.rb +0 -2
- data/lib/expressir/model/search_engine.rb +6 -30
- data/lib/expressir/model/statements/alias.rb +1 -1
- data/lib/expressir/model/statements/assignment.rb +1 -1
- data/lib/expressir/model/statements/case.rb +1 -1
- data/lib/expressir/model/statements/case_action.rb +1 -1
- data/lib/expressir/model/statements/compound.rb +1 -1
- data/lib/expressir/model/statements/escape.rb +1 -1
- data/lib/expressir/model/statements/if.rb +1 -1
- data/lib/expressir/model/statements/null.rb +1 -1
- data/lib/expressir/model/statements/procedure_call.rb +1 -1
- data/lib/expressir/model/statements/repeat.rb +1 -1
- data/lib/expressir/model/statements/return.rb +1 -1
- data/lib/expressir/model/statements/skip.rb +1 -1
- data/lib/expressir/model/statements.rb +20 -0
- data/lib/expressir/model/supertype_expressions/binary_supertype_expression.rb +1 -1
- data/lib/expressir/model/supertype_expressions/oneof_supertype_expression.rb +1 -1
- data/lib/expressir/model/supertype_expressions.rb +12 -0
- data/lib/expressir/model.rb +28 -4
- data/lib/expressir/package/builder.rb +33 -4
- data/lib/expressir/package/metadata.rb +0 -1
- data/lib/expressir/package/reader.rb +0 -1
- data/lib/expressir/package.rb +8 -0
- data/lib/expressir/schema_manifest.rb +5 -6
- data/lib/expressir/schema_manifest_entry.rb +3 -4
- data/lib/expressir/transformer.rb +7 -0
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +23 -173
- metadata +64 -9
- data/lib/expressir/express/builders/token_builder.rb +0 -15
data/README.adoc
CHANGED
|
@@ -117,35 +117,42 @@ Expressir uses the link:https://github.com/parsanol/parsanol-ruby[Parsanol] gem
|
|
|
117
117
|
|
|
118
118
|
[cols="3,2,2,3"]
|
|
119
119
|
|===
|
|
120
|
-
| Mode | Time | Speedup | Notes
|
|
120
|
+
| Mode | Time (179K lines) | Speedup | Notes
|
|
121
121
|
|
|
122
|
-
| Ruby (
|
|
123
|
-
| Native
|
|
124
|
-
| Native ZeroCopy (Parsanol) | 106 ms | 28.7x faster | Zero-copy with source positions
|
|
122
|
+
| Ruby (Parslet) | 507 s | 1x (baseline) | Pure Ruby parsing
|
|
123
|
+
| Native (Parsanol) | 29 s | 17x faster | Rust parser with AST transformation
|
|
125
124
|
|===
|
|
126
125
|
|
|
127
126
|
=== Features
|
|
128
127
|
|
|
129
128
|
When Parsanol is installed:
|
|
130
129
|
|
|
131
|
-
* **
|
|
132
|
-
* **
|
|
133
|
-
* **
|
|
134
|
-
* **Streaming Builder API** - Zero-allocation custom parsing
|
|
130
|
+
* **17x faster parsing** - Rust native backend
|
|
131
|
+
* **Lazy line/column** - Zero overhead for position info
|
|
132
|
+
* **Batch FFI** - Efficient u64 array transfer across language boundary
|
|
135
133
|
|
|
136
134
|
=== Usage
|
|
137
135
|
|
|
138
|
-
Expressir automatically uses Parsanol when available:
|
|
136
|
+
Expressir automatically uses Parsanol (Rust parser) when available for better performance:
|
|
139
137
|
|
|
140
138
|
[source,ruby]
|
|
141
139
|
----
|
|
142
|
-
#
|
|
143
|
-
|
|
140
|
+
# Parse a single file - returns ExpFile
|
|
141
|
+
exp_file = Expressir::Express::Parser.from_file("geometry.exp")
|
|
142
|
+
schema = exp_file.schemas.first
|
|
143
|
+
puts "Schema: #{schema.id}"
|
|
144
144
|
|
|
145
145
|
# Check if native parser is being used
|
|
146
146
|
if Parsanol::Native.available?
|
|
147
147
|
puts "Using Parsanol (Rust parser)"
|
|
148
148
|
end
|
|
149
|
+
|
|
150
|
+
# Parse multiple files - returns Repository
|
|
151
|
+
files = ["schema1.exp", "schema2.exp"]
|
|
152
|
+
repo = Expressir::Express::Parser.from_files(files)
|
|
153
|
+
repo.schemas.each do |s|
|
|
154
|
+
puts "Schema: #{s.id}"
|
|
155
|
+
end
|
|
149
156
|
----
|
|
150
157
|
|
|
151
158
|
For maximum performance, ensure the Parsanol gem is installed:
|
|
@@ -240,9 +247,15 @@ https://www.express-language-foundation.org/pretty-print-spec/[ELF Pretty Print
|
|
|
240
247
|
[source,ruby]
|
|
241
248
|
----
|
|
242
249
|
# Basic usage - formats with default settings
|
|
243
|
-
|
|
250
|
+
# Parser.from_file returns an ExpFile
|
|
251
|
+
exp_file = Expressir::Express::Parser.from_file("schema.exp")
|
|
244
252
|
formatter = Expressir::Express::PrettyFormatter.new
|
|
245
|
-
formatted = formatter.format(
|
|
253
|
+
formatted = formatter.format(exp_file)
|
|
254
|
+
puts formatted
|
|
255
|
+
|
|
256
|
+
# The formatter also accepts Repository objects
|
|
257
|
+
repo = Expressir::Express::Parser.from_files(["schema1.exp", "schema2.exp"])
|
|
258
|
+
formatted = formatter.format(repo)
|
|
246
259
|
puts formatted
|
|
247
260
|
----
|
|
248
261
|
|
|
@@ -1319,15 +1332,19 @@ The library provides two main methods for parsing EXPRESS files.
|
|
|
1319
1332
|
|
|
1320
1333
|
==== Parsing a single file
|
|
1321
1334
|
|
|
1322
|
-
Use the `from_file` method to parse a single EXPRESS schema file
|
|
1335
|
+
Use the `from_file` method to parse a single EXPRESS schema file.
|
|
1336
|
+
This returns an `ExpFile` object containing the parsed schemas:
|
|
1323
1337
|
|
|
1324
1338
|
[source,ruby]
|
|
1325
1339
|
----
|
|
1326
|
-
# Parse a single file
|
|
1327
|
-
|
|
1340
|
+
# Parse a single file - returns ExpFile
|
|
1341
|
+
exp_file = Expressir::Express::Parser.from_file("path/to/schema.exp")
|
|
1342
|
+
|
|
1343
|
+
# Access schemas from the file
|
|
1344
|
+
schema = exp_file.schemas.first
|
|
1328
1345
|
|
|
1329
1346
|
# With options
|
|
1330
|
-
|
|
1347
|
+
exp_file = Expressir::Express::Parser.from_file(
|
|
1331
1348
|
"path/to/schema.exp",
|
|
1332
1349
|
skip_references: false, # Set to true to skip resolving references
|
|
1333
1350
|
include_source: true, # Set to true to include original source in the model
|
|
@@ -1342,7 +1359,7 @@ error:
|
|
|
1342
1359
|
[source,ruby]
|
|
1343
1360
|
----
|
|
1344
1361
|
begin
|
|
1345
|
-
|
|
1362
|
+
exp_file = Expressir::Express::Parser.from_file("path/to/schema.exp")
|
|
1346
1363
|
rescue Expressir::Express::Error::SchemaParseFailure => e
|
|
1347
1364
|
puts "Failed to parse schema: #{e.message}"
|
|
1348
1365
|
puts "Filename: #{e.filename}"
|
|
@@ -1352,13 +1369,19 @@ end
|
|
|
1352
1369
|
|
|
1353
1370
|
==== Parsing multiple files
|
|
1354
1371
|
|
|
1355
|
-
Use the `from_files` method to parse multiple EXPRESS schema files
|
|
1372
|
+
Use the `from_files` method to parse multiple EXPRESS schema files.
|
|
1373
|
+
This returns a `Repository` object containing all parsed files:
|
|
1356
1374
|
|
|
1357
1375
|
[source,ruby]
|
|
1358
1376
|
----
|
|
1359
|
-
# Parse multiple files
|
|
1377
|
+
# Parse multiple files - returns Repository
|
|
1360
1378
|
files = ["schema1.exp", "schema2.exp", "schema3.exp"]
|
|
1361
1379
|
repository = Expressir::Express::Parser.from_files(files)
|
|
1380
|
+
|
|
1381
|
+
# Access all schemas across all files
|
|
1382
|
+
repository.schemas.each do |schema|
|
|
1383
|
+
puts "Schema: #{schema.id}"
|
|
1384
|
+
end
|
|
1362
1385
|
----
|
|
1363
1386
|
|
|
1364
1387
|
You can provide a block to track loading progress and handle errors:
|
|
@@ -1407,7 +1430,12 @@ Example:
|
|
|
1407
1430
|
|
|
1408
1431
|
[source,ruby]
|
|
1409
1432
|
----
|
|
1410
|
-
|
|
1433
|
+
# Single file (ExpFile)
|
|
1434
|
+
exp_file = Expressir::Express::Parser.from_file("path/to/file.exp")
|
|
1435
|
+
file_drop = exp_file.to_liquid
|
|
1436
|
+
|
|
1437
|
+
# Multiple files (Repository)
|
|
1438
|
+
repo = Expressir::Express::Parser.from_files(["file1.exp", "file2.exp"])
|
|
1411
1439
|
repo_drop = repo.to_liquid
|
|
1412
1440
|
----
|
|
1413
1441
|
|
|
@@ -1438,10 +1466,15 @@ and `Expressir::Liquid::Declarations::SchemaDrop` has same attribute `file`.
|
|
|
1438
1466
|
|
|
1439
1467
|
[source,ruby]
|
|
1440
1468
|
----
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1469
|
+
# Parse file (returns ExpFile)
|
|
1470
|
+
exp_file = Expressir::Express::Parser.from_file("path/to/file.exp")
|
|
1471
|
+
file_drop = exp_file.to_liquid
|
|
1472
|
+
schema = file_drop.schemas.first
|
|
1444
1473
|
schema.file = "path/to/file.exp"
|
|
1474
|
+
|
|
1475
|
+
# Or parse multiple files (returns Repository)
|
|
1476
|
+
repo = Expressir::Express::Parser.from_files(["file1.exp", "file2.exp"])
|
|
1477
|
+
repo_drop = repo.to_liquid
|
|
1445
1478
|
----
|
|
1446
1479
|
|
|
1447
1480
|
=== Documentation coverage analysis
|
|
@@ -1454,9 +1487,13 @@ analyze and report on documentation coverage of EXPRESS schemas.
|
|
|
1454
1487
|
# Create a coverage report from a file
|
|
1455
1488
|
report = Expressir::Coverage::Report.from_file("path/to/schema.exp")
|
|
1456
1489
|
|
|
1490
|
+
# Or create a report from an ExpFile
|
|
1491
|
+
exp_file = Expressir::Express::Parser.from_file("path/to/schema.exp")
|
|
1492
|
+
report = Expressir::Coverage::Report.from_exp_file(exp_file)
|
|
1493
|
+
|
|
1457
1494
|
# Or create a report from a repository
|
|
1458
|
-
|
|
1459
|
-
report = Expressir::Coverage::Report.from_repository(
|
|
1495
|
+
repo = Expressir::Express::Parser.from_files(["schema1.exp", "schema2.exp"])
|
|
1496
|
+
report = Expressir::Coverage::Report.from_repository(repo)
|
|
1460
1497
|
|
|
1461
1498
|
# Access overall statistics
|
|
1462
1499
|
puts "Overall coverage: #{report.coverage_percentage}%"
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# SRL Benchmark Script for Expressir
|
|
5
|
+
# Compares Parsanol Ruby vs Native performance on full STEPmod Resource Library
|
|
6
|
+
# Features: Live progress, emojis, colors, per-schema stats
|
|
7
|
+
|
|
8
|
+
require 'bundler/setup'
|
|
9
|
+
require 'benchmark'
|
|
10
|
+
require 'fileutils'
|
|
11
|
+
|
|
12
|
+
# Force loading of native extension
|
|
13
|
+
require 'parsanol'
|
|
14
|
+
require 'parsanol/native'
|
|
15
|
+
|
|
16
|
+
# Now require expressir
|
|
17
|
+
require 'expressir'
|
|
18
|
+
|
|
19
|
+
# Configuration
|
|
20
|
+
SRL_PATH = '/Users/mulgogi/src/mn/iso-10303/schemas/resources'
|
|
21
|
+
ITERATIONS = (ENV['ITERATIONS'] || 1).to_i
|
|
22
|
+
TIMEOUT_SECONDS = (ENV['TIMEOUT'] || 30).to_i # Timeout per file
|
|
23
|
+
|
|
24
|
+
# Check if we're running in an interactive terminal
|
|
25
|
+
INTERACTIVE = $stdout.tty?
|
|
26
|
+
|
|
27
|
+
# ANSI Color codes
|
|
28
|
+
module Colors
|
|
29
|
+
RESET = "\e[0m"
|
|
30
|
+
BOLD = "\e[1m"
|
|
31
|
+
DIM = "\e[2m"
|
|
32
|
+
|
|
33
|
+
# Foreground colors
|
|
34
|
+
BLACK = "\e[30m"
|
|
35
|
+
RED = "\e[31m"
|
|
36
|
+
GREEN = "\e[32m"
|
|
37
|
+
YELLOW = "\e[33m"
|
|
38
|
+
BLUE = "\e[34m"
|
|
39
|
+
MAGENTA = "\e[35m"
|
|
40
|
+
CYAN = "\e[36m"
|
|
41
|
+
WHITE = "\e[37m"
|
|
42
|
+
|
|
43
|
+
# Bright foreground colors
|
|
44
|
+
BRIGHT_RED = "\e[91m"
|
|
45
|
+
BRIGHT_GREEN = "\e[92m"
|
|
46
|
+
BRIGHT_YELLOW = "\e[93m"
|
|
47
|
+
BRIGHT_BLUE = "\e[94m"
|
|
48
|
+
BRIGHT_MAGENTA = "\e[95m"
|
|
49
|
+
BRIGHT_CYAN = "\e[96m"
|
|
50
|
+
BRIGHT_WHITE = "\e[97m"
|
|
51
|
+
|
|
52
|
+
# Background colors
|
|
53
|
+
BG_RED = "\e[41m"
|
|
54
|
+
BG_GREEN = "\e[42m"
|
|
55
|
+
BG_YELLOW = "\e[43m"
|
|
56
|
+
BG_BLUE = "\e[44m"
|
|
57
|
+
BG_MAGENTA = "\e[45m"
|
|
58
|
+
BG_CYAN = "\e[46m"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
include Colors
|
|
62
|
+
|
|
63
|
+
# Terminal utilities
|
|
64
|
+
module Terminal
|
|
65
|
+
class << self
|
|
66
|
+
def width
|
|
67
|
+
IO.console.winsize[1] rescue 80
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def clear_line
|
|
71
|
+
INTERACTIVE ? "\r\e[K" : ""
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def move_to_start
|
|
75
|
+
INTERACTIVE ? "\r" : ""
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Progress bar
|
|
81
|
+
class ProgressBar
|
|
82
|
+
def initialize(total, width = 30)
|
|
83
|
+
@total = total
|
|
84
|
+
@width = width
|
|
85
|
+
@current = 0
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def render(current, label = '')
|
|
89
|
+
@current = current
|
|
90
|
+
percentage = (@current.to_f / @total * 100).round(1)
|
|
91
|
+
filled = (@width * @current / @total.to_f).round
|
|
92
|
+
empty = @width - filled
|
|
93
|
+
|
|
94
|
+
bar = "#{BRIGHT_CYAN}#{'█' * filled}#{DIM}#{'░' * empty}#{RESET}"
|
|
95
|
+
"#{bar} #{BRIGHT_WHITE}#{percentage.to_s.rjust(5)}%#{RESET} #{DIM}#{label}#{RESET}"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def find_exp_files
|
|
100
|
+
Dir.glob("#{SRL_PATH}/*/*.exp").sort.reject { |f| f.include?('quantities_and_units') }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def count_lines(files)
|
|
104
|
+
files.sum { |f| File.read(f).lines.count }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def format_time(seconds)
|
|
108
|
+
if seconds < 60
|
|
109
|
+
"#{seconds.round(2)}s"
|
|
110
|
+
else
|
|
111
|
+
minutes = (seconds / 60).floor
|
|
112
|
+
secs = (seconds % 60).round(1)
|
|
113
|
+
"#{minutes}m #{secs}s"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def format_number(num)
|
|
118
|
+
num.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def print_header
|
|
122
|
+
puts
|
|
123
|
+
puts "#{BRIGHT_MAGENTA}#{'═' * 60}#{RESET}"
|
|
124
|
+
puts "#{BRIGHT_MAGENTA}║#{RESET}#{BOLD}#{BRIGHT_WHITE} 🚀 EXPRESSIR SRL BENCHMARK 🚀 #{RESET}#{BRIGHT_MAGENTA}║#{RESET}"
|
|
125
|
+
puts "#{BRIGHT_MAGENTA}║#{RESET}#{DIM} Ruby vs Native Parser Showdown #{RESET}#{BRIGHT_MAGENTA}║#{RESET}"
|
|
126
|
+
puts "#{BRIGHT_MAGENTA}#{'═' * 60}#{RESET}"
|
|
127
|
+
puts
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def print_config(files, total_lines)
|
|
131
|
+
puts "#{BRIGHT_CYAN}⚙️ Configuration#{RESET}"
|
|
132
|
+
puts "#{DIM}┌─────────────────────────────────────────────┐#{RESET}"
|
|
133
|
+
puts "#{DIM}│#{RESET} 📁 Files: #{BRIGHT_WHITE}#{format_number(files.size).rjust(10)}#{RESET} #{DIM}│#{RESET}"
|
|
134
|
+
puts "#{DIM}│#{RESET} 📊 Lines: #{BRIGHT_WHITE}#{format_number(total_lines).rjust(10)}#{RESET} #{DIM}│#{RESET}"
|
|
135
|
+
puts "#{DIM}│#{RESET} 🔄 Iterations: #{BRIGHT_WHITE}#{ITERATIONS.to_s.rjust(10)}#{RESET} #{DIM}│#{RESET}"
|
|
136
|
+
puts "#{DIM}│#{RESET} 🦀 Native: #{BRIGHT_WHITE}#{Parsanol::Native.available?.to_s.upcase.rjust(10)}#{RESET} #{DIM}│#{RESET}"
|
|
137
|
+
puts "#{DIM}└─────────────────────────────────────────────┘#{RESET}"
|
|
138
|
+
puts
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def print_warmup_start
|
|
142
|
+
print "#{BRIGHT_YELLOW}🔥 Warming up...#{RESET} "
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def print_warmup_done
|
|
146
|
+
puts "#{BRIGHT_GREEN}✅ Done!#{RESET}"
|
|
147
|
+
puts
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
class ParserBenchmark
|
|
151
|
+
attr_reader :name, :emoji, :color, :results
|
|
152
|
+
|
|
153
|
+
def initialize(name:, emoji:, color:, use_native:)
|
|
154
|
+
@name = name
|
|
155
|
+
@emoji = emoji
|
|
156
|
+
@color = color
|
|
157
|
+
@use_native = use_native
|
|
158
|
+
@results = []
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def run(files, total_lines)
|
|
162
|
+
puts "#{@color}#{@emoji} #{@name}#{@RESET}"
|
|
163
|
+
puts "#{DIM}┌──────────────────────────────────────────────────────────┐#{RESET}"
|
|
164
|
+
|
|
165
|
+
progress_bar = ProgressBar.new(files.size, 25)
|
|
166
|
+
iteration_results = { success: 0, failed: 0, errors: [], time: 0, schema_times: [] }
|
|
167
|
+
start_time = Time.now
|
|
168
|
+
|
|
169
|
+
files.each_with_index do |file, idx|
|
|
170
|
+
schema_name = File.basename(file, '.exp')
|
|
171
|
+
file_start = Time.now
|
|
172
|
+
schema_lines = File.read(file).lines.count
|
|
173
|
+
|
|
174
|
+
begin
|
|
175
|
+
require 'timeout'
|
|
176
|
+
Timeout.timeout(TIMEOUT_SECONDS) do
|
|
177
|
+
if @use_native
|
|
178
|
+
content = File.read(file)
|
|
179
|
+
Expressir::Express::Parser.from_exp(content, skip_references: true, use_native: true)
|
|
180
|
+
else
|
|
181
|
+
Expressir::Express::Parser.from_file(file, skip_references: true)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
iteration_results[:success] += 1
|
|
185
|
+
status = "#{BRIGHT_GREEN}✓#{RESET}"
|
|
186
|
+
rescue Timeout::Error => e
|
|
187
|
+
iteration_results[:failed] += 1
|
|
188
|
+
iteration_results[:errors] << { file: File.basename(file), error: "Timeout after #{TIMEOUT_SECONDS}s" }
|
|
189
|
+
status = "#{BRIGHT_YELLOW}⏱#{RESET}"
|
|
190
|
+
rescue => e
|
|
191
|
+
iteration_results[:failed] += 1
|
|
192
|
+
iteration_results[:errors] << { file: File.basename(file), error: e.message[0..60] }
|
|
193
|
+
status = "#{BRIGHT_RED}✗#{RESET}"
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
elapsed = Time.now - file_start
|
|
197
|
+
iteration_results[:schema_times] << { name: schema_name, time: elapsed, lines: schema_lines }
|
|
198
|
+
|
|
199
|
+
# Live progress update
|
|
200
|
+
if INTERACTIVE
|
|
201
|
+
progress_label = "#{status} #{schema_name[0..20].ljust(21)}"
|
|
202
|
+
print "#{Terminal.clear_line} #{progress_bar.render(idx + 1, progress_label)}"
|
|
203
|
+
$stdout.flush
|
|
204
|
+
elsif (idx + 1) % 10 == 0 || idx == 0
|
|
205
|
+
# Print progress every 10 files when not interactive
|
|
206
|
+
pct = ((idx + 1).to_f / files.size * 100).round(1)
|
|
207
|
+
puts " #{progress_bar.render(idx + 1, "#{pct}% complete")}"
|
|
208
|
+
$stdout.flush
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
if INTERACTIVE
|
|
213
|
+
puts
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
iteration_results[:time] = Time.now - start_time
|
|
217
|
+
@results << iteration_results
|
|
218
|
+
iteration_results
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def print_summary(files, total_lines, result)
|
|
222
|
+
avg_time = result[:time]
|
|
223
|
+
lines_per_sec = (total_lines / avg_time).round(1)
|
|
224
|
+
files_per_sec = (files.size / avg_time).round(2)
|
|
225
|
+
|
|
226
|
+
puts "#{DIM}├──────────────────────────────────────────────────────────┤#{RESET}"
|
|
227
|
+
puts "#{DIM}│#{RESET} #{BOLD}Results:#{RESET}"
|
|
228
|
+
puts "#{DIM}│#{RESET} #{BRIGHT_GREEN}✅ Success:#{RESET} #{BRIGHT_WHITE}#{result[:success].to_s.rjust(5)}#{RESET} / #{files.size} files"
|
|
229
|
+
puts "#{DIM}│#{RESET} #{BRIGHT_RED}❌ Failed:#{RESET} #{BRIGHT_WHITE}#{result[:failed].to_s.rjust(5)}#{RESET} files"
|
|
230
|
+
puts "#{DIM}│#{RESET} #{BRIGHT_CYAN}⏱️ Time:#{RESET} #{BRIGHT_WHITE}#{format_time(avg_time).rjust(5)}#{RESET}"
|
|
231
|
+
puts "#{DIM}│#{RESET} #{BRIGHT_YELLOW}⚡ Speed:#{RESET} #{BRIGHT_WHITE}#{format_number(lines_per_sec).rjust(5)}#{RESET} lines/sec"
|
|
232
|
+
|
|
233
|
+
if result[:failed] > 0 && result[:failed] <= 5
|
|
234
|
+
puts "#{DIM}├──────────────────────────────────────────────────────────┤#{RESET}"
|
|
235
|
+
puts "#{DIM}│#{RESET} #{BRIGHT_RED}⚠️ Errors:#{RESET}"
|
|
236
|
+
result[:errors].first(3).each do |err|
|
|
237
|
+
puts "#{DIM}│#{RESET} #{DIM}• #{err[:file]}: #{err[:error][0..40]}#{RESET}"
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
puts "#{DIM}└──────────────────────────────────────────────────────────┘#{RESET}"
|
|
242
|
+
puts
|
|
243
|
+
|
|
244
|
+
{ avg_time: avg_time, lines_per_sec: lines_per_sec, files_per_sec: files_per_sec }
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def print_slowest_schemas(result, count = 5)
|
|
248
|
+
return if result[:schema_times].empty?
|
|
249
|
+
|
|
250
|
+
slowest = result[:schema_times].sort_by { |s| -s[:time] }.first(count)
|
|
251
|
+
|
|
252
|
+
puts "#{@color}#{@emoji} Slowest Schemas#{@RESET}"
|
|
253
|
+
puts "#{DIM}┌─────────────────────────────────────────────────────┐#{RESET}"
|
|
254
|
+
slowest.each_with_index do |s, i|
|
|
255
|
+
rank = "#{BRIGHT_WHITE}#{(i + 1).to_s.rjust(2)}.#{RESET}"
|
|
256
|
+
name = s[:name][0..25].ljust(26)
|
|
257
|
+
time = "#{BRIGHT_CYAN}#{s[:time].round(1)}s#{RESET}".rjust(8)
|
|
258
|
+
lines = "#{DIM}#{s[:lines]} lines#{RESET}"
|
|
259
|
+
puts "#{DIM}│#{RESET} #{rank} #{name} #{time} #{lines}"
|
|
260
|
+
end
|
|
261
|
+
puts "#{DIM}└─────────────────────────────────────────────────────┘#{RESET}"
|
|
262
|
+
puts
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def print_comparison(ruby_stats, native_stats, files, total_lines)
|
|
267
|
+
speedup = (ruby_stats[:avg_time] / native_stats[:avg_time]).round(1)
|
|
268
|
+
time_saved = (ruby_stats[:avg_time] - native_stats[:avg_time]).round(2)
|
|
269
|
+
|
|
270
|
+
puts "#{BRIGHT_MAGENTA}#{'═' * 60}#{RESET}"
|
|
271
|
+
puts "#{BOLD}#{BRIGHT_WHITE} 📊 PERFORMANCE COMPARISON 📊 #{RESET}"
|
|
272
|
+
puts "#{BRIGHT_MAGENTA}#{'═' * 60}#{RESET}"
|
|
273
|
+
puts
|
|
274
|
+
|
|
275
|
+
puts "#{DIM}┌─────────────────────────────────────────────────────┐#{RESET}"
|
|
276
|
+
puts "#{DIM}│#{RESET}#{BOLD} HEAD-TO-HEAD #{RESET}#{DIM}│#{RESET}"
|
|
277
|
+
puts "#{DIM}├─────────────────────────────────────────────────────┤#{RESET}"
|
|
278
|
+
puts "#{DIM}│#{RESET} #{DIM}│#{RESET}"
|
|
279
|
+
puts "#{DIM}│#{RESET} #{BRIGHT_BLUE}💎 Ruby Parser#{RESET} #{format_time(ruby_stats[:avg_time]).rjust(10)} #{DIM}│#{RESET}"
|
|
280
|
+
puts "#{DIM}│#{RESET} #{DIM}#{format_number(ruby_stats[:lines_per_sec])} lines/sec#{RESET} #{DIM}│#{RESET}"
|
|
281
|
+
puts "#{DIM}│#{RESET} #{DIM}│#{RESET}"
|
|
282
|
+
puts "#{DIM}│#{RESET} #{BRIGHT_CYAN}🦀 Native Parser#{RESET} #{format_time(native_stats[:avg_time]).rjust(10)} #{DIM}│#{RESET}"
|
|
283
|
+
puts "#{DIM}│#{RESET} #{DIM}#{format_number(native_stats[:lines_per_sec])} lines/sec#{RESET} #{DIM}│#{RESET}"
|
|
284
|
+
puts "#{DIM}│#{RESET} #{DIM}│#{RESET}"
|
|
285
|
+
puts "#{DIM}├─────────────────────────────────────────────────────┤#{RESET}"
|
|
286
|
+
|
|
287
|
+
if speedup > 1
|
|
288
|
+
puts "#{DIM}│#{RESET} #{BOLD}#{BRIGHT_GREEN}🏆 WINNER: Native Parser#{RESET} #{DIM}│#{RESET}"
|
|
289
|
+
puts "#{DIM}│#{RESET} #{BRIGHT_YELLOW}⚡ Speedup:#{RESET} #{BOLD}#{BRIGHT_GREEN}#{speedup}x FASTER#{RESET} #{DIM}│#{RESET}"
|
|
290
|
+
puts "#{DIM}│#{RESET} #{BRIGHT_YELLOW}⏱️ Time Saved:#{RESET} #{BRIGHT_GREEN}#{format_time(time_saved)} per run#{RESET} #{DIM}│#{RESET}"
|
|
291
|
+
else
|
|
292
|
+
puts "#{DIM}│#{RESET} #{BOLD}#{BRIGHT_BLUE}🏆 WINNER: Ruby Parser#{RESET} #{DIM}│#{RESET}"
|
|
293
|
+
puts "#{DIM}│#{RESET} #{BRIGHT_YELLOW}⚡ Speedup:#{RESET} #{BOLD}#{BRIGHT_BLUE}#{(1/speedup).round(1)}x FASTER#{RESET} #{DIM}│#{RESET}"
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
puts "#{DIM}│#{RESET} #{DIM}│#{RESET}"
|
|
297
|
+
puts "#{DIM}└─────────────────────────────────────────────────────┘#{RESET}"
|
|
298
|
+
puts
|
|
299
|
+
|
|
300
|
+
# Summary
|
|
301
|
+
puts "#{DIM}┌─────────────────────────────────────────────────────┐#{RESET}"
|
|
302
|
+
puts "#{DIM}│#{RESET}#{BOLD} SUMMARY #{RESET}#{DIM}│#{RESET}"
|
|
303
|
+
puts "#{DIM}├─────────────────────────────────────────────────────┤#{RESET}"
|
|
304
|
+
puts "#{DIM}│#{RESET} 📦 #{format_number(files.size)} schemas (#{format_number(total_lines)} lines) #{DIM}│#{RESET}"
|
|
305
|
+
puts "#{DIM}│#{RESET} #{BRIGHT_BLUE}Ruby:#{RESET} #{format_time(ruby_stats[:avg_time]).rjust(8)} #{DIM}│#{RESET}"
|
|
306
|
+
puts "#{DIM}│#{RESET} #{BRIGHT_CYAN}Native:#{RESET} #{format_time(native_stats[:avg_time]).rjust(8)} #{DIM}│#{RESET}"
|
|
307
|
+
puts "#{DIM}└─────────────────────────────────────────────────────┘#{RESET}"
|
|
308
|
+
puts
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def print_footer
|
|
312
|
+
puts "#{BRIGHT_MAGENTA}#{'═' * 60}#{RESET}"
|
|
313
|
+
puts "#{BOLD}#{BRIGHT_WHITE} ✨ BENCHMARK COMPLETE ✨ #{RESET}"
|
|
314
|
+
puts "#{BRIGHT_MAGENTA}#{'═' * 60}#{RESET}"
|
|
315
|
+
puts
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# ==================== MAIN ====================
|
|
319
|
+
|
|
320
|
+
print_header
|
|
321
|
+
|
|
322
|
+
# Find all EXPRESS files
|
|
323
|
+
files = find_exp_files
|
|
324
|
+
total_lines = count_lines(files)
|
|
325
|
+
|
|
326
|
+
if files.empty?
|
|
327
|
+
puts "#{BRIGHT_RED}❌ ERROR: No EXPRESS files found at #{SRL_PATH}#{RESET}"
|
|
328
|
+
exit 1
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
print_config(files, total_lines)
|
|
332
|
+
|
|
333
|
+
# Warmup
|
|
334
|
+
print_warmup_start
|
|
335
|
+
warmup_file = files.first
|
|
336
|
+
|
|
337
|
+
begin
|
|
338
|
+
Expressir::Express::Parser.from_file(warmup_file, skip_references: true)
|
|
339
|
+
rescue => e
|
|
340
|
+
puts "#{BRIGHT_YELLOW}⚠️ Ruby warmup warning: #{e.message[0..40]}#{RESET}"
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
if Parsanol::Native.available?
|
|
344
|
+
begin
|
|
345
|
+
content = File.read(warmup_file)
|
|
346
|
+
Expressir::Express::Parser.from_exp(content, skip_references: true, use_native: true)
|
|
347
|
+
rescue => e
|
|
348
|
+
puts "#{BRIGHT_YELLOW}⚠️ Native warmup warning: #{e.message[0..40]}#{RESET}"
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
print_warmup_done
|
|
353
|
+
|
|
354
|
+
# Ruby Benchmark
|
|
355
|
+
ruby_benchmark = ParserBenchmark.new(
|
|
356
|
+
name: "Ruby Parser",
|
|
357
|
+
emoji: "💎",
|
|
358
|
+
color: BRIGHT_BLUE,
|
|
359
|
+
use_native: false
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
ruby_result = ruby_benchmark.run(files, total_lines)
|
|
363
|
+
ruby_stats = ruby_benchmark.print_summary(files, total_lines, ruby_result)
|
|
364
|
+
ruby_benchmark.print_slowest_schemas(ruby_result)
|
|
365
|
+
|
|
366
|
+
# Native Benchmark (if available)
|
|
367
|
+
if Parsanol::Native.available?
|
|
368
|
+
native_benchmark = ParserBenchmark.new(
|
|
369
|
+
name: "Native Parser (Rust)",
|
|
370
|
+
emoji: "🦀",
|
|
371
|
+
color: BRIGHT_CYAN,
|
|
372
|
+
use_native: true
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
native_result = native_benchmark.run(files, total_lines)
|
|
376
|
+
native_stats = native_benchmark.print_summary(files, total_lines, native_result)
|
|
377
|
+
native_benchmark.print_slowest_schemas(native_result)
|
|
378
|
+
|
|
379
|
+
# Print comparison
|
|
380
|
+
print_comparison(ruby_stats, native_stats, files, total_lines)
|
|
381
|
+
else
|
|
382
|
+
puts "#{BRIGHT_YELLOW}⚠️ Native parser not available. Run \`rake compile\` in parsanol-ruby to build.#{RESET}"
|
|
383
|
+
puts
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
print_footer
|