expressir 2.4.0 → 2.4.2
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/TODO.bugs/01-stale-transformer-autoload.md +39 -0
- data/TODO.bugs/02-parser-class-instance-vars.md +36 -0
- data/TODO.bugs/03-builder-mutable-state.md +43 -0
- data/TODO.bugs/04-formatter-public-send-dispatch.md +53 -0
- data/TODO.bugs/05-anonymous-formatter-subclass.md +45 -0
- data/TODO.bugs/06-collection-registry-single-source.md +53 -0
- data/TODO.bugs/07-require-relative-cleanup.md +42 -0
- data/TODO.bugs/08-require-expressir-in-commands.md +34 -0
- data/TODO.bugs/09-parser-split.md +53 -0
- data/TODO.bugs/10-to-s-override.md +42 -0
- data/TODO.bugs/11-parser-class-variables.md +39 -0
- data/TODO.bugs/12-marker-modules-vs-registry.md +64 -0
- data/TODO.bugs/13-string-literal-scanner-limitation.md +52 -0
- data/TODO.bugs/14-model-formatting-leak.md +30 -0
- data/TODO.bugs/15-expression-children-macro.md +27 -0
- data/TODO.bugs/16-pretty-formatter-duplication.md +28 -0
- data/TODO.bugs/17-snake-case-cache-mutable-constant.md +28 -0
- data/TODO.bugs/18-const-get-private-constants.md +30 -0
- data/TODO.bugs/19-format-methods-public.md +22 -0
- data/TODO.bugs/20-coverage-nested-entities-dedup.md +20 -0
- data/TODO.bugs/21-operator-tokens-secondary-dispatch.md +21 -0
- data/TODO.bugs/22-builder-fast-path-wrappers.md +32 -0
- data/TODO.bugs/23-coverage-inverse-maps.md +21 -0
- data/TODO.bugs/24-streaming-builder-complexity.md +19 -0
- data/TODO.bugs/25-debug-puts-in-production.md +21 -0
- data/TODO.bugs/26-generic-entity-children-misplaced.md +21 -0
- data/TODO.bugs/27-package-build-god-method.md +19 -0
- data/TODO.bugs/28-package-god-class.md +30 -0
- data/TODO.bugs/29-validate-ascii-god-class.md +24 -0
- data/TODO.bugs/30-unicode-map-extraction.md +19 -0
- data/TODO.bugs/README.md +43 -0
- data/TODO.max-perf/01-restore-ci-green.md +29 -0
- data/TODO.max-perf/02-streaming-parse-path.md +31 -0
- data/TODO.max-perf/03-cli-parallel-opt-in.md +27 -0
- data/TODO.max-perf/04-benchmark-harness.md +28 -0
- data/TODO.max-perf/05-parallel-fidelity-specs.md +22 -0
- data/TODO.max-perf/06-builder-cpu-audit.md +41 -0
- data/TODO.max-perf/07-upstream-parsanol-roadmap.md +27 -0
- data/TODO.max-perf/08-builder-build-perf.md +45 -0
- data/TODO.max-perf/09-grammar-cold-start.md +25 -0
- data/TODO.max-perf/10-parser-facade-hygiene.md +23 -0
- data/TODO.max-perf/11-ci-green-closeout.md +25 -0
- data/TODO.max-perf/12-require-boot-profile.md +25 -0
- data/TODO.max-perf/13-key-conversion-specs.md +26 -0
- data/TODO.max-perf/14-builder-call-handler-audit.md +28 -0
- data/benchmark/srl_benchmark.rb +76 -17
- data/docs/_tutorials/formatting-schemas.adoc +89 -0
- data/docs/_tutorials/index.adoc +10 -0
- data/docs/lychee.toml +3 -0
- data/expressir.gemspec +4 -2
- data/lib/expressir/cli.rb +3 -0
- data/lib/expressir/commands/changes_import_eengine.rb +0 -6
- data/lib/expressir/commands/changes_validate.rb +0 -2
- data/lib/expressir/commands/clean.rb +1 -1
- data/lib/expressir/commands/coverage.rb +6 -2
- data/lib/expressir/commands/file_violations.rb +70 -0
- data/lib/expressir/commands/format.rb +1 -1
- data/lib/expressir/commands/non_ascii_character.rb +49 -0
- data/lib/expressir/commands/non_ascii_violation_collection.rb +301 -0
- data/lib/expressir/commands/package.rb +143 -187
- data/lib/expressir/commands/validate_ascii.rb +0 -510
- data/lib/expressir/commands/version.rb +1 -1
- data/lib/expressir/commands.rb +3 -0
- data/lib/expressir/coverage.rb +49 -117
- data/lib/expressir/express/ast_key_converter.rb +114 -0
- data/lib/expressir/express/builder.rb +71 -194
- data/lib/expressir/express/builder_context.rb +22 -0
- data/lib/expressir/express/builders/expression_builder.rb +16 -16
- data/lib/expressir/express/cache.rb +35 -8
- data/lib/expressir/express/error.rb +24 -0
- data/lib/expressir/express/formatter.rb +33 -18
- data/lib/expressir/express/formatters/declarations_formatter.rb +24 -2
- data/lib/expressir/express/formatters/remark_formatter.rb +75 -2
- data/lib/expressir/express/formatters/statements_formatter.rb +20 -3
- data/lib/expressir/express/grammar/parser.rb +705 -0
- data/lib/expressir/express/grammar.rb +11 -0
- data/lib/expressir/express/node_position_index.rb +215 -0
- data/lib/expressir/express/parallel_files.rb +229 -0
- data/lib/expressir/express/parser.rb +138 -950
- data/lib/expressir/express/pretty_formatter.rb +23 -5
- data/lib/expressir/express/remark_attacher.rb +497 -581
- data/lib/expressir/express/remark_scanner.rb +84 -19
- data/lib/expressir/express/schema_block_scanner.rb +137 -0
- data/lib/expressir/express/schema_source_formatter.rb +15 -0
- data/lib/expressir/express/scope_resolver.rb +223 -0
- data/lib/expressir/express/source_formatter.rb +15 -0
- data/lib/expressir/express/streaming_builder.rb +147 -176
- data/lib/expressir/express.rb +10 -6
- data/lib/expressir/model/concerns.rb +3 -0
- data/lib/expressir/model/data_types/generic_entity.rb +6 -6
- data/lib/expressir/model/declarations/entity.rb +5 -0
- data/lib/expressir/model/declarations/function.rb +5 -0
- data/lib/expressir/model/declarations/procedure.rb +5 -0
- data/lib/expressir/model/declarations/rule.rb +6 -0
- data/lib/expressir/model/declarations/schema.rb +21 -8
- data/lib/expressir/model/declarations/type.rb +3 -0
- data/lib/expressir/model/exp_file.rb +2 -0
- data/lib/expressir/model/expressions/aggregate_initializer.rb +1 -0
- data/lib/expressir/model/expressions/aggregate_initializer_item.rb +1 -0
- data/lib/expressir/model/expressions/binary_expression.rb +1 -0
- data/lib/expressir/model/expressions/entity_constructor.rb +1 -0
- data/lib/expressir/model/expressions/function_call.rb +1 -0
- data/lib/expressir/model/expressions/interval.rb +1 -0
- data/lib/expressir/model/expressions/query_expression.rb +2 -0
- data/lib/expressir/model/expressions/unary_expression.rb +1 -0
- data/lib/expressir/model/model_element.rb +133 -14
- data/lib/expressir/model/remark_format.rb +17 -0
- data/lib/expressir/model/remark_info.rb +31 -3
- data/lib/expressir/model/remark_placement.rb +29 -0
- data/lib/expressir/model/repository.rb +18 -5
- data/lib/expressir/model/statements/alias.rb +2 -0
- data/lib/expressir/model/statements/assignment.rb +4 -0
- data/lib/expressir/model/statements/case.rb +21 -0
- data/lib/expressir/model/statements/case_action.rb +1 -0
- data/lib/expressir/model/statements/compound.rb +4 -0
- data/lib/expressir/model/statements/escape.rb +3 -0
- data/lib/expressir/model/statements/if.rb +5 -0
- data/lib/expressir/model/statements/null.rb +3 -0
- data/lib/expressir/model/statements/procedure_call.rb +3 -0
- data/lib/expressir/model/statements/repeat.rb +3 -0
- data/lib/expressir/model/statements/return.rb +3 -0
- data/lib/expressir/model/statements/skip.rb +3 -0
- data/lib/expressir/model.rb +12 -3
- data/lib/expressir/package/builder.rb +2 -2
- data/lib/expressir/version.rb +6 -1
- data/lib/expressir.rb +56 -3
- metadata +81 -7
- data/lib/expressir/express/transformer/remark_handling.rb +0 -194
|
@@ -5,516 +5,6 @@ require "table_tennis"
|
|
|
5
5
|
|
|
6
6
|
module Expressir
|
|
7
7
|
module Commands
|
|
8
|
-
# Represents a non-ASCII character with its details and replacement
|
|
9
|
-
class NonAsciiCharacter
|
|
10
|
-
attr_reader :char, :hex, :utf8, :is_math, :replacement,
|
|
11
|
-
:replacement_type, :occurrences
|
|
12
|
-
|
|
13
|
-
def initialize(char, hex, utf8, is_math, replacement, replacement_type)
|
|
14
|
-
@char = char
|
|
15
|
-
@hex = hex
|
|
16
|
-
@utf8 = utf8
|
|
17
|
-
@is_math = is_math
|
|
18
|
-
@replacement = replacement
|
|
19
|
-
@replacement_type = replacement_type
|
|
20
|
-
@occurrences = []
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def add_occurrence(line_number, column, line)
|
|
24
|
-
@occurrences << {
|
|
25
|
-
line_number: line_number,
|
|
26
|
-
column: column,
|
|
27
|
-
line: line,
|
|
28
|
-
}
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def replacement_text
|
|
32
|
-
@is_math ? "AsciiMath: #{@replacement}" : "ISO 10303-11: #{@replacement}"
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def occurrence_count
|
|
36
|
-
@occurrences.size
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def to_h
|
|
40
|
-
{
|
|
41
|
-
character: @char,
|
|
42
|
-
hex: @hex,
|
|
43
|
-
utf8: @utf8,
|
|
44
|
-
is_math: @is_math,
|
|
45
|
-
replacement_type: @replacement_type,
|
|
46
|
-
replacement: @replacement,
|
|
47
|
-
occurrence_count: occurrence_count,
|
|
48
|
-
occurrences: @occurrences,
|
|
49
|
-
}
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Represents all non-ASCII characters in a file
|
|
54
|
-
class FileViolations
|
|
55
|
-
attr_reader :path, :filename, :directory, :violations, :unique_characters
|
|
56
|
-
|
|
57
|
-
def initialize(file_path)
|
|
58
|
-
@path = file_path
|
|
59
|
-
@filename = File.basename(file_path)
|
|
60
|
-
@directory = File.dirname(file_path)
|
|
61
|
-
@characters = {} # Map of characters to NonAsciiCharacter objects
|
|
62
|
-
@violations = [] # List of violations (line, column, etc.)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def add_violation(line_number, column, match, char_details, line)
|
|
66
|
-
violation = {
|
|
67
|
-
line_number: line_number,
|
|
68
|
-
column: column,
|
|
69
|
-
match: match,
|
|
70
|
-
char_details: char_details,
|
|
71
|
-
line: line,
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
@violations << violation
|
|
75
|
-
|
|
76
|
-
# Register each character
|
|
77
|
-
char_details.each do |detail|
|
|
78
|
-
char = detail[:char]
|
|
79
|
-
unless @characters[char]
|
|
80
|
-
@characters[char] = NonAsciiCharacter.new(
|
|
81
|
-
char,
|
|
82
|
-
detail[:hex],
|
|
83
|
-
detail[:utf8],
|
|
84
|
-
detail[:is_math],
|
|
85
|
-
detail[:replacement],
|
|
86
|
-
detail[:replacement_type],
|
|
87
|
-
)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
@characters[char].add_occurrence(line_number, column, line)
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def violation_count
|
|
95
|
-
@violations.size
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def unique_characters
|
|
99
|
-
@characters.values
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def display_path
|
|
103
|
-
"#{File.basename(@directory)}/#{@filename}"
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def full_path
|
|
107
|
-
File.expand_path(@path)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def to_h
|
|
111
|
-
{
|
|
112
|
-
file: display_path,
|
|
113
|
-
count: violation_count,
|
|
114
|
-
non_ascii_characters: unique_characters.map(&:to_h),
|
|
115
|
-
}
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Collection of all violations across multiple files
|
|
120
|
-
class NonAsciiViolationCollection
|
|
121
|
-
attr_reader :file_violations, :total_files
|
|
122
|
-
|
|
123
|
-
def initialize(check_remarks: false)
|
|
124
|
-
@file_violations = {} # Map of file paths to FileViolations objects
|
|
125
|
-
@total_files = 0
|
|
126
|
-
@unicode_to_asciimath = nil
|
|
127
|
-
@check_remarks = check_remarks
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def process_file(file)
|
|
131
|
-
@total_files += 1
|
|
132
|
-
|
|
133
|
-
# Initialize the mapping once
|
|
134
|
-
@unicode_to_asciimath ||= build_unicode_to_asciimath_map
|
|
135
|
-
|
|
136
|
-
file_violations = process_file_violations(file)
|
|
137
|
-
return if file_violations.violations.empty?
|
|
138
|
-
|
|
139
|
-
@file_violations[file] = file_violations
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def files_with_violations
|
|
143
|
-
@file_violations.size
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def total_violations
|
|
147
|
-
@file_violations.values.sum(&:violation_count)
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def unique_character_count
|
|
151
|
-
# Get total unique characters across all files
|
|
152
|
-
all_chars = Set.new
|
|
153
|
-
@file_violations.each_value do |file_violation|
|
|
154
|
-
file_violation.unique_characters.each do |char|
|
|
155
|
-
all_chars.add(char.char)
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
all_chars.size
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def total_occurrence_count
|
|
162
|
-
# Sum all occurrences of all characters across all files
|
|
163
|
-
@file_violations.values.sum do |file_violation|
|
|
164
|
-
file_violation.unique_characters.sum(&:occurrence_count)
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
def to_yaml_data
|
|
169
|
-
{
|
|
170
|
-
summary: {
|
|
171
|
-
total_files: @total_files,
|
|
172
|
-
files_with_violations: files_with_violations,
|
|
173
|
-
total_violations: total_violations,
|
|
174
|
-
total_unique_characters: unique_character_count,
|
|
175
|
-
total_occurrences: total_occurrence_count,
|
|
176
|
-
},
|
|
177
|
-
violations: @file_violations.transform_keys do |k|
|
|
178
|
-
File.expand_path(k)
|
|
179
|
-
end.transform_values(&:to_h),
|
|
180
|
-
}
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def print_text_output
|
|
184
|
-
# Print each file's violations if any
|
|
185
|
-
unless @file_violations.empty?
|
|
186
|
-
@file_violations.each_value do |file_violation|
|
|
187
|
-
puts "\n#{Paint[file_violation.display_path, :cyan, :bold]}:"
|
|
188
|
-
|
|
189
|
-
file_violation.violations.each do |v|
|
|
190
|
-
puts " #{Paint['Line',
|
|
191
|
-
:blue]} #{Paint[v[:line_number],
|
|
192
|
-
:yellow]}, #{Paint['Column',
|
|
193
|
-
:blue]} #{Paint[v[:column],
|
|
194
|
-
:yellow]}:"
|
|
195
|
-
puts " #{v[:line]}"
|
|
196
|
-
puts " #{' ' * v[:column]}#{Paint['^' * v[:match].length,
|
|
197
|
-
:red]} #{Paint['Non-ASCII sequence',
|
|
198
|
-
:red]}"
|
|
199
|
-
|
|
200
|
-
v[:char_details].each do |cd|
|
|
201
|
-
character = file_violation.unique_characters.find do |c|
|
|
202
|
-
c.char == cd[:char]
|
|
203
|
-
end
|
|
204
|
-
next unless character
|
|
205
|
-
|
|
206
|
-
puts " #{Paint["\"#{cd[:char]}\"",
|
|
207
|
-
:yellow]} - Hex: #{Paint[cd[:hex],
|
|
208
|
-
:magenta]}, UTF-8 bytes: #{Paint[cd[:utf8],
|
|
209
|
-
:magenta]}"
|
|
210
|
-
puts " #{Paint['Replacement:',
|
|
211
|
-
:green]} #{character.replacement_text}"
|
|
212
|
-
end
|
|
213
|
-
puts ""
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
puts " #{Paint['Found',
|
|
217
|
-
:green]} #{Paint[file_violation.violation_count,
|
|
218
|
-
:red]} #{Paint['non-ASCII sequence(s) in',
|
|
219
|
-
:green]} #{Paint[file_violation.filename,
|
|
220
|
-
:cyan]}\n"
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
# Always print summary
|
|
225
|
-
validation_scope = @check_remarks ? "code and remarks" : "code only (remarks excluded)"
|
|
226
|
-
puts "\n#{Paint['Summary:', :blue, :bold]}"
|
|
227
|
-
puts " #{Paint['Validation scope:',
|
|
228
|
-
:green]} #{Paint[validation_scope,
|
|
229
|
-
:cyan]}"
|
|
230
|
-
puts " #{Paint['Scanned',
|
|
231
|
-
:green]} #{Paint[@total_files,
|
|
232
|
-
:yellow]} #{Paint['EXPRESS file(s)',
|
|
233
|
-
:green]}"
|
|
234
|
-
puts " #{Paint['Found',
|
|
235
|
-
:green]} #{Paint[total_violations,
|
|
236
|
-
:red]} #{Paint['non-ASCII sequence(s) in',
|
|
237
|
-
:green]} #{Paint[files_with_violations,
|
|
238
|
-
:red]} #{Paint['file(s)',
|
|
239
|
-
:green]}"
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
def print_table_output
|
|
243
|
-
return if @file_violations.empty?
|
|
244
|
-
|
|
245
|
-
# Build rows array
|
|
246
|
-
rows = []
|
|
247
|
-
total_occurrences = 0
|
|
248
|
-
|
|
249
|
-
@file_violations.each_value do |file_violation|
|
|
250
|
-
file_violation.unique_characters.each do |character|
|
|
251
|
-
occurrence_count = character.occurrence_count
|
|
252
|
-
total_occurrences += occurrence_count
|
|
253
|
-
|
|
254
|
-
rows << {
|
|
255
|
-
file: file_violation.display_path,
|
|
256
|
-
symbol: "\"#{character.char}\" (#{character.hex})",
|
|
257
|
-
replacement: character.replacement_text,
|
|
258
|
-
occurrences: occurrence_count,
|
|
259
|
-
}
|
|
260
|
-
end
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
# Add total row
|
|
264
|
-
rows << {
|
|
265
|
-
file: "TOTAL",
|
|
266
|
-
symbol: "#{unique_character_count} unique",
|
|
267
|
-
replacement: "",
|
|
268
|
-
occurrences: total_occurrences,
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
# Use TableTennis to render
|
|
272
|
-
options = {
|
|
273
|
-
title: "Non-ASCII Characters Summary",
|
|
274
|
-
columns: %i[file symbol replacement occurrences],
|
|
275
|
-
headers: {
|
|
276
|
-
file: "File",
|
|
277
|
-
symbol: "Symbol",
|
|
278
|
-
replacement: "Replacement",
|
|
279
|
-
occurrences: "Occurrences",
|
|
280
|
-
},
|
|
281
|
-
mark: ->(row) { row[:file] == "TOTAL" },
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
puts "\n#{TableTennis.new(rows, options)}\n"
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
private
|
|
288
|
-
|
|
289
|
-
def process_file_violations(file)
|
|
290
|
-
file_violations = FileViolations.new(file)
|
|
291
|
-
|
|
292
|
-
if @check_remarks
|
|
293
|
-
# Check remarks too - validate original file content
|
|
294
|
-
File.readlines(file,
|
|
295
|
-
encoding: "UTF-8").each_with_index do |line, line_idx|
|
|
296
|
-
line_number = line_idx + 1
|
|
297
|
-
|
|
298
|
-
# Skip if line only contains ASCII
|
|
299
|
-
next unless /[^\x00-\x7F]/.match?(line)
|
|
300
|
-
|
|
301
|
-
# Find all non-ASCII sequences
|
|
302
|
-
line.chomp.scan(/([^\x00-\x7F]+)/) do |match|
|
|
303
|
-
match = match[0]
|
|
304
|
-
column = line.index(match)
|
|
305
|
-
|
|
306
|
-
# Process each character in the sequence
|
|
307
|
-
char_details = match.chars.filter_map do |c|
|
|
308
|
-
process_non_ascii_char(c)
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
# Skip if no non-ASCII characters found
|
|
312
|
-
next if char_details.empty?
|
|
313
|
-
|
|
314
|
-
file_violations.add_violation(line_number, column, match,
|
|
315
|
-
char_details, line.chomp)
|
|
316
|
-
end
|
|
317
|
-
end
|
|
318
|
-
else
|
|
319
|
-
# Default: exclude remarks - use model-based approach
|
|
320
|
-
# Parse the EXPRESS file to get the model
|
|
321
|
-
repository = Expressir::Express::Parser.from_file(file)
|
|
322
|
-
|
|
323
|
-
# Format each schema without remarks to get plain EXPRESS code
|
|
324
|
-
repository.schemas.each do |schema|
|
|
325
|
-
formatted_schema = schema.to_s(no_remarks: true)
|
|
326
|
-
|
|
327
|
-
# Check the formatted schema (without remarks) for non-ASCII
|
|
328
|
-
formatted_schema.lines.each_with_index do |line, line_idx|
|
|
329
|
-
line_number = line_idx + 1
|
|
330
|
-
|
|
331
|
-
# Skip if line only contains ASCII
|
|
332
|
-
next unless /[^\x00-\x7F]/.match?(line)
|
|
333
|
-
|
|
334
|
-
# Find all non-ASCII sequences
|
|
335
|
-
line.chomp.scan(/([^\x00-\x7F]+)/) do |match|
|
|
336
|
-
match = match[0]
|
|
337
|
-
column = line.index(match)
|
|
338
|
-
|
|
339
|
-
# Process each character in the sequence
|
|
340
|
-
char_details = match.chars.filter_map do |c|
|
|
341
|
-
process_non_ascii_char(c)
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
# Skip if no non-ASCII characters found
|
|
345
|
-
next if char_details.empty?
|
|
346
|
-
|
|
347
|
-
file_violations.add_violation(line_number, column, match,
|
|
348
|
-
char_details, line.chomp)
|
|
349
|
-
end
|
|
350
|
-
end
|
|
351
|
-
end
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
file_violations
|
|
355
|
-
rescue Expressir::Express::Error::SchemaParseFailure
|
|
356
|
-
# If file can't be parsed, fall back to checking original content
|
|
357
|
-
# This ensures we still catch non-ASCII even in invalid EXPRESS
|
|
358
|
-
File.readlines(file,
|
|
359
|
-
encoding: "UTF-8").each_with_index do |line, line_idx|
|
|
360
|
-
line_number = line_idx + 1
|
|
361
|
-
|
|
362
|
-
# Skip if line only contains ASCII
|
|
363
|
-
next unless /[^\x00-\x7F]/.match?(line)
|
|
364
|
-
|
|
365
|
-
# Find all non-ASCII sequences
|
|
366
|
-
line.chomp.scan(/([^\x00-\x7F]+)/) do |match|
|
|
367
|
-
match = match[0]
|
|
368
|
-
column = line.index(match)
|
|
369
|
-
|
|
370
|
-
# Process each character in the sequence
|
|
371
|
-
char_details = match.chars.filter_map do |c|
|
|
372
|
-
process_non_ascii_char(c)
|
|
373
|
-
end
|
|
374
|
-
|
|
375
|
-
# Skip if no non-ASCII characters found
|
|
376
|
-
next if char_details.empty?
|
|
377
|
-
|
|
378
|
-
file_violations.add_violation(line_number, column, match,
|
|
379
|
-
char_details, line.chomp)
|
|
380
|
-
end
|
|
381
|
-
end
|
|
382
|
-
|
|
383
|
-
file_violations
|
|
384
|
-
end
|
|
385
|
-
|
|
386
|
-
def process_non_ascii_char(char)
|
|
387
|
-
# Skip ASCII characters
|
|
388
|
-
return nil if char.ord <= 0x7F
|
|
389
|
-
|
|
390
|
-
code_point = char.ord
|
|
391
|
-
hex = "0x#{code_point.to_s(16)}"
|
|
392
|
-
utf8 = code_point.chr(Encoding::UTF_8).bytes.map do |b|
|
|
393
|
-
"0x#{b.to_s(16)}"
|
|
394
|
-
end.join(" ")
|
|
395
|
-
|
|
396
|
-
# Check if it's a math symbol
|
|
397
|
-
if asciimath = @unicode_to_asciimath[char]
|
|
398
|
-
return {
|
|
399
|
-
char: char,
|
|
400
|
-
hex: hex,
|
|
401
|
-
utf8: utf8,
|
|
402
|
-
is_math: true,
|
|
403
|
-
replacement: asciimath,
|
|
404
|
-
replacement_type: "asciimath",
|
|
405
|
-
}
|
|
406
|
-
end
|
|
407
|
-
|
|
408
|
-
# Not a math symbol, use ISO encoding
|
|
409
|
-
{
|
|
410
|
-
char: char,
|
|
411
|
-
hex: hex,
|
|
412
|
-
utf8: utf8,
|
|
413
|
-
is_math: false,
|
|
414
|
-
replacement: encode_iso_10303_11(char),
|
|
415
|
-
replacement_type: "iso-10303-11",
|
|
416
|
-
}
|
|
417
|
-
end
|
|
418
|
-
|
|
419
|
-
def encode_iso_10303_11(char)
|
|
420
|
-
code_point = char.ord
|
|
421
|
-
|
|
422
|
-
# Format the encoded value with double quotes
|
|
423
|
-
if code_point < 0x10000
|
|
424
|
-
"\"#{sprintf('%08X', code_point)}\"" # e.g., "00000041" for 'A'
|
|
425
|
-
else
|
|
426
|
-
# For higher code points, use all four octets
|
|
427
|
-
group = (code_point >> 24) & 0xFF
|
|
428
|
-
plane = (code_point >> 16) & 0xFF
|
|
429
|
-
row = (code_point >> 8) & 0xFF
|
|
430
|
-
cell = code_point & 0xFF
|
|
431
|
-
|
|
432
|
-
"\"#{sprintf('%02X%02X%02X%02X', group, plane, row, cell)}\""
|
|
433
|
-
end
|
|
434
|
-
end
|
|
435
|
-
|
|
436
|
-
def build_unicode_to_asciimath_map
|
|
437
|
-
# Pre-defined mapping of common math symbols
|
|
438
|
-
{
|
|
439
|
-
# Greek letters
|
|
440
|
-
"α" => "alpha",
|
|
441
|
-
"β" => "beta",
|
|
442
|
-
"γ" => "gamma",
|
|
443
|
-
"Γ" => "Gamma",
|
|
444
|
-
"δ" => "delta",
|
|
445
|
-
"Δ" => "Delta",
|
|
446
|
-
"ε" => "epsilon",
|
|
447
|
-
"ζ" => "zeta",
|
|
448
|
-
"η" => "eta",
|
|
449
|
-
"θ" => "theta",
|
|
450
|
-
"Θ" => "Theta",
|
|
451
|
-
"ι" => "iota",
|
|
452
|
-
"κ" => "kappa",
|
|
453
|
-
"λ" => "lambda",
|
|
454
|
-
"Λ" => "Lambda",
|
|
455
|
-
"μ" => "mu",
|
|
456
|
-
"ν" => "nu",
|
|
457
|
-
"ξ" => "xi",
|
|
458
|
-
"Ξ" => "Xi",
|
|
459
|
-
"π" => "pi",
|
|
460
|
-
"Π" => "Pi",
|
|
461
|
-
"ρ" => "rho",
|
|
462
|
-
"σ" => "sigma",
|
|
463
|
-
"Σ" => "Sigma",
|
|
464
|
-
"τ" => "tau",
|
|
465
|
-
"υ" => "upsilon",
|
|
466
|
-
"φ" => "phi",
|
|
467
|
-
"Φ" => "Phi",
|
|
468
|
-
"χ" => "chi",
|
|
469
|
-
"ψ" => "psi",
|
|
470
|
-
"Ψ" => "Psi",
|
|
471
|
-
"ω" => "omega",
|
|
472
|
-
"Ω" => "Omega",
|
|
473
|
-
|
|
474
|
-
# Math operators
|
|
475
|
-
"×" => "xx",
|
|
476
|
-
"÷" => "div",
|
|
477
|
-
"±" => "pm",
|
|
478
|
-
"∓" => "mp",
|
|
479
|
-
"∞" => "oo",
|
|
480
|
-
"≤" => "le",
|
|
481
|
-
"≥" => "ge",
|
|
482
|
-
"≠" => "ne",
|
|
483
|
-
"≈" => "~~",
|
|
484
|
-
"≅" => "cong",
|
|
485
|
-
"≡" => "equiv",
|
|
486
|
-
"∈" => "in",
|
|
487
|
-
"∉" => "notin",
|
|
488
|
-
"⊂" => "subset",
|
|
489
|
-
"⊃" => "supset",
|
|
490
|
-
"∩" => "cap",
|
|
491
|
-
"∪" => "cup",
|
|
492
|
-
"∧" => "and",
|
|
493
|
-
"∨" => "or",
|
|
494
|
-
"¬" => "neg",
|
|
495
|
-
"∀" => "forall",
|
|
496
|
-
"∃" => "exists",
|
|
497
|
-
"∄" => "nexists",
|
|
498
|
-
"∇" => "grad",
|
|
499
|
-
"∂" => "del",
|
|
500
|
-
"∑" => "sum",
|
|
501
|
-
"∏" => "prod",
|
|
502
|
-
"∫" => "int",
|
|
503
|
-
"∮" => "oint",
|
|
504
|
-
"√" => "sqrt",
|
|
505
|
-
"⊥" => "perp",
|
|
506
|
-
"‖" => "norm",
|
|
507
|
-
"→" => "rarr",
|
|
508
|
-
"←" => "larr",
|
|
509
|
-
"↔" => "harr",
|
|
510
|
-
"⇒" => "rArr",
|
|
511
|
-
"⇐" => "lArr",
|
|
512
|
-
"⇔" => "hArr",
|
|
513
|
-
}
|
|
514
|
-
end
|
|
515
|
-
end
|
|
516
|
-
|
|
517
|
-
# ValidateAscii command for checking EXPRESS files for non-ASCII characters
|
|
518
8
|
class ValidateAscii < Base
|
|
519
9
|
def run(express_file_path) # rubocop:disable Metrics/AbcSize
|
|
520
10
|
# Check if input is a manifest file
|
data/lib/expressir/commands.rb
CHANGED
|
@@ -10,6 +10,9 @@ module Expressir
|
|
|
10
10
|
autoload :Validate, "#{__dir__}/commands/validate"
|
|
11
11
|
autoload :ValidateLoad, "#{__dir__}/commands/validate_load"
|
|
12
12
|
autoload :ValidateAscii, "#{__dir__}/commands/validate_ascii"
|
|
13
|
+
autoload :NonAsciiCharacter, "#{__dir__}/commands/non_ascii_character"
|
|
14
|
+
autoload :FileViolations, "#{__dir__}/commands/file_violations"
|
|
15
|
+
autoload :NonAsciiViolationCollection, "#{__dir__}/commands/non_ascii_violation_collection"
|
|
13
16
|
autoload :Changes, "#{__dir__}/commands/changes"
|
|
14
17
|
autoload :ChangesValidate, "#{__dir__}/commands/changes_validate"
|
|
15
18
|
autoload :ChangesImportEengine, "#{__dir__}/commands/changes_import_eengine"
|