expressir 2.3.7 → 2.4.1
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/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/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/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 +138 -185
- 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/builder.rb +74 -86
- 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 +7 -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/line_map.rb +48 -0
- data/lib/expressir/express/node_position_index.rb +215 -0
- data/lib/expressir/express/parser.rb +114 -884
- data/lib/expressir/express/pretty_formatter.rb +23 -5
- data/lib/expressir/express/remark_attacher.rb +483 -669
- data/lib/expressir/express/remark_scanner.rb +245 -0
- data/lib/expressir/express/schema_block_scanner.rb +136 -0
- data/lib/expressir/express/schema_source_formatter.rb +15 -0
- data/lib/expressir/express/scope_resolver.rb +194 -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 +128 -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/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 +38 -3
- metadata +65 -5
- data/lib/expressir/express/transformer/remark_handling.rb +0 -194
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Expressir
|
|
4
|
+
module Commands
|
|
5
|
+
class FileViolations
|
|
6
|
+
attr_reader :path, :filename, :directory, :violations
|
|
7
|
+
|
|
8
|
+
def initialize(file_path)
|
|
9
|
+
@path = file_path
|
|
10
|
+
@filename = File.basename(file_path)
|
|
11
|
+
@directory = File.dirname(file_path)
|
|
12
|
+
@characters = {} # Map of characters to NonAsciiCharacter objects
|
|
13
|
+
@violations = [] # List of violations (line, column, etc.)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def add_violation(line_number, column, match, char_details, line)
|
|
17
|
+
violation = {
|
|
18
|
+
line_number: line_number,
|
|
19
|
+
column: column,
|
|
20
|
+
match: match,
|
|
21
|
+
char_details: char_details,
|
|
22
|
+
line: line,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@violations << violation
|
|
26
|
+
|
|
27
|
+
# Register each character
|
|
28
|
+
char_details.each do |detail|
|
|
29
|
+
char = detail[:char]
|
|
30
|
+
unless @characters[char]
|
|
31
|
+
@characters[char] = NonAsciiCharacter.new(
|
|
32
|
+
char,
|
|
33
|
+
detail[:hex],
|
|
34
|
+
detail[:utf8],
|
|
35
|
+
detail[:is_math],
|
|
36
|
+
detail[:replacement],
|
|
37
|
+
detail[:replacement_type],
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
@characters[char].add_occurrence(line_number, column, line)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def violation_count
|
|
46
|
+
@violations.size
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def unique_characters
|
|
50
|
+
@characters.values
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def display_path
|
|
54
|
+
"#{File.basename(@directory)}/#{@filename}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def full_path
|
|
58
|
+
File.expand_path(@path)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def to_h
|
|
62
|
+
{
|
|
63
|
+
file: display_path,
|
|
64
|
+
count: violation_count,
|
|
65
|
+
non_ascii_characters: unique_characters.map(&:to_h),
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Expressir
|
|
4
|
+
module Commands
|
|
5
|
+
class NonAsciiCharacter
|
|
6
|
+
attr_reader :char, :hex, :utf8, :is_math, :replacement,
|
|
7
|
+
:replacement_type, :occurrences
|
|
8
|
+
|
|
9
|
+
def initialize(char, hex, utf8, is_math, replacement, replacement_type)
|
|
10
|
+
@char = char
|
|
11
|
+
@hex = hex
|
|
12
|
+
@utf8 = utf8
|
|
13
|
+
@is_math = is_math
|
|
14
|
+
@replacement = replacement
|
|
15
|
+
@replacement_type = replacement_type
|
|
16
|
+
@occurrences = []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def add_occurrence(line_number, column, line)
|
|
20
|
+
@occurrences << {
|
|
21
|
+
line_number: line_number,
|
|
22
|
+
column: column,
|
|
23
|
+
line: line,
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def replacement_text
|
|
28
|
+
@is_math ? "AsciiMath: #{@replacement}" : "ISO 10303-11: #{@replacement}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def occurrence_count
|
|
32
|
+
@occurrences.size
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_h
|
|
36
|
+
{
|
|
37
|
+
character: @char,
|
|
38
|
+
hex: @hex,
|
|
39
|
+
utf8: @utf8,
|
|
40
|
+
is_math: @is_math,
|
|
41
|
+
replacement_type: @replacement_type,
|
|
42
|
+
replacement: @replacement,
|
|
43
|
+
occurrence_count: occurrence_count,
|
|
44
|
+
occurrences: @occurrences,
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "paint"
|
|
4
|
+
|
|
5
|
+
module Expressir
|
|
6
|
+
module Commands
|
|
7
|
+
class NonAsciiViolationCollection
|
|
8
|
+
# Pre-defined mapping of common Unicode math symbols to AsciiMath.
|
|
9
|
+
UNICODE_TO_ASCIIDOC_MAP = {
|
|
10
|
+
# Greek letters
|
|
11
|
+
"α" => "alpha", "β" => "beta", "γ" => "gamma", "Γ" => "Gamma",
|
|
12
|
+
"δ" => "delta", "Δ" => "Delta", "ε" => "epsilon", "ζ" => "zeta",
|
|
13
|
+
"η" => "eta", "θ" => "theta", "Θ" => "Theta", "ι" => "iota",
|
|
14
|
+
"κ" => "kappa", "λ" => "lambda", "Λ" => "Lambda", "μ" => "mu",
|
|
15
|
+
"ν" => "nu", "ξ" => "xi", "Ξ" => "Xi", "π" => "pi", "Π" => "Pi",
|
|
16
|
+
"ρ" => "rho", "σ" => "sigma", "Σ" => "Sigma", "τ" => "tau",
|
|
17
|
+
"υ" => "upsilon", "φ" => "phi", "Φ" => "Phi", "χ" => "chi",
|
|
18
|
+
"ψ" => "psi", "Ψ" => "Psi", "ω" => "omega", "Ω" => "Omega",
|
|
19
|
+
# Math operators
|
|
20
|
+
"×" => "xx", "÷" => "div", "±" => "pm", "∓" => "mp",
|
|
21
|
+
"∞" => "oo", "≤" => "le", "≥" => "ge", "≠" => "ne",
|
|
22
|
+
"≈" => "~~", "≅" => "cong", "≡" => "equiv", "∈" => "in",
|
|
23
|
+
"∉" => "notin", "⊂" => "subset", "⊃" => "supset", "∩" => "cap",
|
|
24
|
+
"∪" => "cup", "∧" => "and", "∨" => "or", "¬" => "neg",
|
|
25
|
+
"∀" => "forall", "∃" => "exists", "∄" => "nexists", "∇" => "grad",
|
|
26
|
+
"∂" => "del", "∑" => "sum", "∏" => "prod", "∫" => "int",
|
|
27
|
+
"∮" => "oint", "√" => "sqrt", "⊥" => "perp", "‖" => "norm",
|
|
28
|
+
"→" => "rarr", "←" => "larr", "↔" => "harr",
|
|
29
|
+
"⇒" => "rArr", "⇐" => "lArr", "⇔" => "hArr"
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
attr_reader :file_violations, :total_files
|
|
33
|
+
|
|
34
|
+
def initialize(check_remarks: false)
|
|
35
|
+
@file_violations = {} # Map of file paths to FileViolations objects
|
|
36
|
+
@total_files = 0
|
|
37
|
+
@unicode_to_asciimath = nil
|
|
38
|
+
@check_remarks = check_remarks
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def process_file(file)
|
|
42
|
+
@total_files += 1
|
|
43
|
+
|
|
44
|
+
# Initialize the mapping once
|
|
45
|
+
@unicode_to_asciimath ||= UNICODE_TO_ASCIIDOC_MAP
|
|
46
|
+
|
|
47
|
+
file_violations = process_file_violations(file)
|
|
48
|
+
return if file_violations.violations.empty?
|
|
49
|
+
|
|
50
|
+
@file_violations[file] = file_violations
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def files_with_violations
|
|
54
|
+
@file_violations.size
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def total_violations
|
|
58
|
+
@file_violations.values.sum(&:violation_count)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def unique_character_count
|
|
62
|
+
# Get total unique characters across all files
|
|
63
|
+
all_chars = Set.new
|
|
64
|
+
@file_violations.each_value do |file_violation|
|
|
65
|
+
file_violation.unique_characters.each do |char|
|
|
66
|
+
all_chars.add(char.char)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
all_chars.size
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def total_occurrence_count
|
|
73
|
+
# Sum all occurrences of all characters across all files
|
|
74
|
+
@file_violations.values.sum do |file_violation|
|
|
75
|
+
file_violation.unique_characters.sum(&:occurrence_count)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def to_yaml_data
|
|
80
|
+
{
|
|
81
|
+
summary: {
|
|
82
|
+
total_files: @total_files,
|
|
83
|
+
files_with_violations: files_with_violations,
|
|
84
|
+
total_violations: total_violations,
|
|
85
|
+
total_unique_characters: unique_character_count,
|
|
86
|
+
total_occurrences: total_occurrence_count,
|
|
87
|
+
},
|
|
88
|
+
violations: @file_violations.transform_keys do |k|
|
|
89
|
+
File.expand_path(k)
|
|
90
|
+
end.transform_values(&:to_h),
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def print_text_output
|
|
95
|
+
# Print each file's violations if any
|
|
96
|
+
unless @file_violations.empty?
|
|
97
|
+
@file_violations.each_value do |file_violation|
|
|
98
|
+
puts "\n#{Paint[file_violation.display_path, :cyan, :bold]}:"
|
|
99
|
+
|
|
100
|
+
file_violation.violations.each do |v|
|
|
101
|
+
puts " #{Paint['Line',
|
|
102
|
+
:blue]} #{Paint[v[:line_number],
|
|
103
|
+
:yellow]}, #{Paint['Column',
|
|
104
|
+
:blue]} #{Paint[v[:column],
|
|
105
|
+
:yellow]}:"
|
|
106
|
+
puts " #{v[:line]}"
|
|
107
|
+
puts " #{' ' * v[:column]}#{Paint['^' * v[:match].length,
|
|
108
|
+
:red]} #{Paint['Non-ASCII sequence',
|
|
109
|
+
:red]}"
|
|
110
|
+
|
|
111
|
+
v[:char_details].each do |cd|
|
|
112
|
+
character = file_violation.unique_characters.find do |c|
|
|
113
|
+
c.char == cd[:char]
|
|
114
|
+
end
|
|
115
|
+
next unless character
|
|
116
|
+
|
|
117
|
+
puts " #{Paint["\"#{cd[:char]}\"",
|
|
118
|
+
:yellow]} - Hex: #{Paint[cd[:hex],
|
|
119
|
+
:magenta]}, UTF-8 bytes: #{Paint[cd[:utf8],
|
|
120
|
+
:magenta]}"
|
|
121
|
+
puts " #{Paint['Replacement:',
|
|
122
|
+
:green]} #{character.replacement_text}"
|
|
123
|
+
end
|
|
124
|
+
puts ""
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
puts " #{Paint['Found',
|
|
128
|
+
:green]} #{Paint[file_violation.violation_count,
|
|
129
|
+
:red]} #{Paint['non-ASCII sequence(s) in',
|
|
130
|
+
:green]} #{Paint[file_violation.filename,
|
|
131
|
+
:cyan]}\n"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Always print summary
|
|
136
|
+
validation_scope = @check_remarks ? "code and all remarks" : "code and tail remarks (embedded remarks excluded)"
|
|
137
|
+
puts "\n#{Paint['Summary:', :blue, :bold]}"
|
|
138
|
+
puts " #{Paint['Validation scope:',
|
|
139
|
+
:green]} #{Paint[validation_scope,
|
|
140
|
+
:cyan]}"
|
|
141
|
+
puts " #{Paint['Scanned',
|
|
142
|
+
:green]} #{Paint[@total_files,
|
|
143
|
+
:yellow]} #{Paint['EXPRESS file(s)',
|
|
144
|
+
:green]}"
|
|
145
|
+
puts " #{Paint['Found',
|
|
146
|
+
:green]} #{Paint[total_violations,
|
|
147
|
+
:red]} #{Paint['non-ASCII sequence(s) in',
|
|
148
|
+
:green]} #{Paint[files_with_violations,
|
|
149
|
+
:red]} #{Paint['file(s)',
|
|
150
|
+
:green]}"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def print_table_output
|
|
154
|
+
return if @file_violations.empty?
|
|
155
|
+
|
|
156
|
+
# Build rows array
|
|
157
|
+
rows = []
|
|
158
|
+
total_occurrences = 0
|
|
159
|
+
|
|
160
|
+
@file_violations.each_value do |file_violation|
|
|
161
|
+
file_violation.unique_characters.each do |character|
|
|
162
|
+
occurrence_count = character.occurrence_count
|
|
163
|
+
total_occurrences += occurrence_count
|
|
164
|
+
|
|
165
|
+
rows << {
|
|
166
|
+
file: file_violation.display_path,
|
|
167
|
+
symbol: "\"#{character.char}\" (#{character.hex})",
|
|
168
|
+
replacement: character.replacement_text,
|
|
169
|
+
occurrences: occurrence_count,
|
|
170
|
+
}
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Add total row
|
|
175
|
+
rows << {
|
|
176
|
+
file: "TOTAL",
|
|
177
|
+
symbol: "#{unique_character_count} unique",
|
|
178
|
+
replacement: "",
|
|
179
|
+
occurrences: total_occurrences,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
# Use TableTennis to render
|
|
183
|
+
options = {
|
|
184
|
+
title: "Non-ASCII Characters Summary",
|
|
185
|
+
columns: %i[file symbol replacement occurrences],
|
|
186
|
+
headers: {
|
|
187
|
+
file: "File",
|
|
188
|
+
symbol: "Symbol",
|
|
189
|
+
replacement: "Replacement",
|
|
190
|
+
occurrences: "Occurrences",
|
|
191
|
+
},
|
|
192
|
+
mark: ->(row) { row[:file] == "TOTAL" },
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
puts "\n#{TableTennis.new(rows, options)}\n"
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
private
|
|
199
|
+
|
|
200
|
+
# Build a masked copy of `source` where every byte inside an embedded
|
|
201
|
+
# remark region is replaced with an ASCII space. Byte positions and the
|
|
202
|
+
# string's encoding are preserved, so masked lines still align 1:1 with
|
|
203
|
+
# the original file for reporting.
|
|
204
|
+
def mask_embedded_remarks(source)
|
|
205
|
+
remarks = Expressir::Express::RemarkScanner.new(source).scan
|
|
206
|
+
embedded = remarks.select(&:embedded?)
|
|
207
|
+
return source if embedded.empty?
|
|
208
|
+
|
|
209
|
+
masked = source.b.dup
|
|
210
|
+
embedded.each do |r|
|
|
211
|
+
(r.position...r.end_position).each { |i| masked.setbyte(i, 0x20) }
|
|
212
|
+
end
|
|
213
|
+
masked.force_encoding(source.encoding)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def scan_lines_for_non_ascii(file_violations, lines)
|
|
217
|
+
lines.each_with_index do |line, line_idx|
|
|
218
|
+
line_number = line_idx + 1
|
|
219
|
+
next unless /[^\x00-\x7F]/.match?(line)
|
|
220
|
+
|
|
221
|
+
line.chomp.scan(/([^\x00-\x7F]+)/) do |match|
|
|
222
|
+
match = match[0]
|
|
223
|
+
column = line.index(match)
|
|
224
|
+
|
|
225
|
+
char_details = match.chars.filter_map do |c|
|
|
226
|
+
process_non_ascii_char(c)
|
|
227
|
+
end
|
|
228
|
+
next if char_details.empty?
|
|
229
|
+
|
|
230
|
+
file_violations.add_violation(line_number, column, match,
|
|
231
|
+
char_details, line.chomp)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def process_file_violations(file)
|
|
237
|
+
file_violations = FileViolations.new(file)
|
|
238
|
+
source = File.read(file, encoding: "UTF-8")
|
|
239
|
+
|
|
240
|
+
# With --check-remarks: scan every line including embedded remarks.
|
|
241
|
+
# Without: scan code + tail remarks only — mask out embedded remark
|
|
242
|
+
# regions so non-ASCII inside `(* ... *)` documentation blocks is
|
|
243
|
+
# not flagged. Tail remarks (`--`) are part of code lines and stay
|
|
244
|
+
# in scope (issue #285).
|
|
245
|
+
scan_source = @check_remarks ? source : mask_embedded_remarks(source)
|
|
246
|
+
scan_lines_for_non_ascii(file_violations, scan_source.lines)
|
|
247
|
+
file_violations
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def process_non_ascii_char(char)
|
|
251
|
+
# Skip ASCII characters
|
|
252
|
+
return nil if char.ord <= 0x7F
|
|
253
|
+
|
|
254
|
+
code_point = char.ord
|
|
255
|
+
hex = "0x#{code_point.to_s(16)}"
|
|
256
|
+
utf8 = code_point.chr(Encoding::UTF_8).bytes.map do |b|
|
|
257
|
+
"0x#{b.to_s(16)}"
|
|
258
|
+
end.join(" ")
|
|
259
|
+
|
|
260
|
+
# Check if it's a math symbol
|
|
261
|
+
if asciimath = @unicode_to_asciimath[char]
|
|
262
|
+
return {
|
|
263
|
+
char: char,
|
|
264
|
+
hex: hex,
|
|
265
|
+
utf8: utf8,
|
|
266
|
+
is_math: true,
|
|
267
|
+
replacement: asciimath,
|
|
268
|
+
replacement_type: "asciimath",
|
|
269
|
+
}
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Not a math symbol, use ISO encoding
|
|
273
|
+
{
|
|
274
|
+
char: char,
|
|
275
|
+
hex: hex,
|
|
276
|
+
utf8: utf8,
|
|
277
|
+
is_math: false,
|
|
278
|
+
replacement: encode_express_format(char),
|
|
279
|
+
replacement_type: "iso-10303-11",
|
|
280
|
+
}
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def encode_express_format(char)
|
|
284
|
+
code_point = char.ord
|
|
285
|
+
|
|
286
|
+
# Format the encoded value with double quotes
|
|
287
|
+
if code_point < 0x10000
|
|
288
|
+
"\"#{sprintf('%08X', code_point)}\"" # e.g., "00000041" for 'A'
|
|
289
|
+
else
|
|
290
|
+
# For higher code points, use all four octets
|
|
291
|
+
group = (code_point >> 24) & 0xFF
|
|
292
|
+
plane = (code_point >> 16) & 0xFF
|
|
293
|
+
row = (code_point >> 8) & 0xFF
|
|
294
|
+
cell = code_point & 0xFF
|
|
295
|
+
|
|
296
|
+
"\"#{sprintf('%02X%02X%02X%02X', group, plane, row, cell)}\""
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|