postsvg 0.1.0 → 0.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/CHANGELOG.md +105 -0
- data/CLAUDE.md +173 -0
- data/Gemfile +2 -3
- data/README.adoc +456 -179
- data/Rakefile +100 -0
- data/TODO.roadmap/00-architecture.md +139 -0
- data/TODO.roadmap/01-autoload-migration.md +39 -0
- data/TODO.roadmap/02-isolate-dormant-code.md +51 -0
- data/TODO.roadmap/03-domain-model.md +66 -0
- data/TODO.roadmap/04-lexer.md +40 -0
- data/TODO.roadmap/05-parser.md +43 -0
- data/TODO.roadmap/06-graphics-state.md +45 -0
- data/TODO.roadmap/07-matrix-and-color.md +37 -0
- data/TODO.roadmap/08-svg-builder.md +51 -0
- data/TODO.roadmap/09-renderer.md +52 -0
- data/TODO.roadmap/10-visitor.md +58 -0
- data/TODO.roadmap/11-operator-coverage.md +103 -0
- data/TODO.roadmap/12-svg-domain.md +62 -0
- data/TODO.roadmap/13-translation-handlers.md +69 -0
- data/TODO.roadmap/14-ps-serializer.md +49 -0
- data/TODO.roadmap/15-cli-and-public-api.md +47 -0
- data/TODO.roadmap/16-specs.md +84 -0
- data/TODO.roadmap/17-docs-sync.md +47 -0
- data/TODO.roadmap/18-performance-and-determinism.md +47 -0
- data/TODO.roadmap/19-error-model.md +45 -0
- data/TODO.roadmap/20-font-and-text.md +46 -0
- data/TODO.roadmap/21-images.md +45 -0
- data/TODO.roadmap/22-forms-and-resources.md +25 -0
- data/TODO.roadmap/23-level2-level3.md +52 -0
- data/TODO.roadmap/24-ci-and-release.md +42 -0
- data/TODO.roadmap/README.md +77 -0
- data/docs/.gitignore +29 -0
- data/docs/CHANGELOG.md +114 -0
- data/docs/COMPLETE_DOCUMENTATION_STATUS.md +376 -0
- data/docs/DEPLOYMENT.md +456 -0
- data/docs/DEPLOYMENT_INSTRUCTIONS.md +229 -0
- data/docs/DOCUMENTATION_PLAN.md +425 -0
- data/docs/FINAL_SUMMARY.md +657 -0
- data/docs/Gemfile +15 -0
- data/docs/README.md +327 -0
- data/docs/_config.yml +99 -0
- data/docs/advanced-topics.adoc +370 -0
- data/docs/api-reference/colors.adoc +705 -0
- data/docs/api-reference/converter.adoc +699 -0
- data/docs/api-reference/execution-context.adoc +1210 -0
- data/docs/api-reference/graphics-state.adoc +1070 -0
- data/docs/api-reference/interpreter.adoc +810 -0
- data/docs/api-reference/matrix.adoc +1179 -0
- data/docs/api-reference/path-builder.adoc +1284 -0
- data/docs/api-reference/postsvg-module.adoc +388 -0
- data/docs/api-reference/svg-generator.adoc +891 -0
- data/docs/api-reference/tokenizer.adoc +925 -0
- data/docs/api-reference.adoc +221 -0
- data/docs/architecture/command-registry.adoc +1191 -0
- data/docs/architecture/conversion-pipeline.adoc +746 -0
- data/docs/architecture/design-decisions.adoc +999 -0
- data/docs/architecture/generator-stage.adoc +1115 -0
- data/docs/architecture/graphics-state-model.adoc +1089 -0
- data/docs/architecture/interpreter-stage.adoc +1125 -0
- data/docs/architecture/parser-stage.adoc +1051 -0
- data/docs/architecture.adoc +354 -0
- data/docs/cli-reference/batch-command.adoc +616 -0
- data/docs/cli-reference/check-command.adoc +677 -0
- data/docs/cli-reference/cli-options.adoc +802 -0
- data/docs/cli-reference/convert-command.adoc +462 -0
- data/docs/cli-reference/version-command.adoc +296 -0
- data/docs/cli-reference.adoc +317 -0
- data/docs/concepts/conversion-pipeline.adoc +903 -0
- data/docs/concepts/coordinate-systems.adoc +836 -0
- data/docs/concepts/graphics-state.adoc +861 -0
- data/docs/concepts/path-operations.adoc +1076 -0
- data/docs/concepts/postscript-language.adoc +859 -0
- data/docs/concepts/svg-generation.adoc +937 -0
- data/docs/concepts.adoc +198 -0
- data/docs/contributing.adoc +443 -0
- data/docs/development.adoc +420 -0
- data/docs/faq.adoc +493 -0
- data/docs/getting-started/basic-usage.adoc +538 -0
- data/docs/getting-started/common-workflows.adoc +577 -0
- data/docs/getting-started/first-conversion.adoc +492 -0
- data/docs/getting-started/installation.adoc +534 -0
- data/docs/getting-started.adoc +94 -0
- data/docs/index.adoc +248 -0
- data/docs/optimization.adoc +196 -0
- data/docs/ps2svg_compatibility.adoc +149 -0
- data/docs/quick-reference.adoc +453 -0
- data/docs/sitemap.adoc +337 -0
- data/docs/troubleshooting.adoc +486 -0
- data/docs/validation.adoc +772 -0
- data/exe/postsvg +1 -0
- data/lib/postsvg/cli.rb +104 -57
- data/lib/postsvg/color.rb +132 -0
- data/lib/postsvg/errors.rb +68 -3
- data/lib/postsvg/format_number.rb +22 -0
- data/lib/postsvg/graphics_context.rb +80 -0
- data/lib/postsvg/graphics_stack.rb +43 -0
- data/lib/postsvg/model/literals/array.rb +41 -0
- data/lib/postsvg/model/literals/dictionary.rb +34 -0
- data/lib/postsvg/model/literals/hex.rb +37 -0
- data/lib/postsvg/model/literals/name.rb +40 -0
- data/lib/postsvg/model/literals/number.rb +36 -0
- data/lib/postsvg/model/literals/procedure.rb +41 -0
- data/lib/postsvg/model/literals/string.rb +30 -0
- data/lib/postsvg/model/literals.rb +19 -0
- data/lib/postsvg/model/operator.rb +58 -0
- data/lib/postsvg/model/operators/arithmetic.rb +264 -0
- data/lib/postsvg/model/operators/boolean.rb +182 -0
- data/lib/postsvg/model/operators/color.rb +74 -0
- data/lib/postsvg/model/operators/container.rb +186 -0
- data/lib/postsvg/model/operators/control_flow.rb +119 -0
- data/lib/postsvg/model/operators/device.rb +21 -0
- data/lib/postsvg/model/operators/dictionary.rb +118 -0
- data/lib/postsvg/model/operators/font.rb +121 -0
- data/lib/postsvg/model/operators/graphics_state.rb +84 -0
- data/lib/postsvg/model/operators/painting.rb +29 -0
- data/lib/postsvg/model/operators/path.rb +169 -0
- data/lib/postsvg/model/operators/stack.rb +72 -0
- data/lib/postsvg/model/operators/transformations.rb +103 -0
- data/lib/postsvg/model/operators.rb +89 -0
- data/lib/postsvg/model/program.rb +68 -0
- data/lib/postsvg/model/token.rb +43 -0
- data/lib/postsvg/model.rb +17 -0
- data/lib/postsvg/options.rb +29 -0
- data/lib/postsvg/renderer.rb +85 -0
- data/lib/postsvg/serializer.rb +325 -0
- data/lib/postsvg/source/ast_builder.rb +308 -0
- data/lib/postsvg/source/lexer.rb +322 -0
- data/lib/postsvg/source/operand_stack.rb +55 -0
- data/lib/postsvg/source.rb +21 -0
- data/lib/postsvg/svg/attribute_parser.rb +45 -0
- data/lib/postsvg/svg/clip_path_registry.rb +44 -0
- data/lib/postsvg/svg/document.rb +22 -0
- data/lib/postsvg/svg/element.rb +84 -0
- data/lib/postsvg/svg/elements/circle.rb +36 -0
- data/lib/postsvg/svg/elements/clip_path.rb +26 -0
- data/lib/postsvg/svg/elements/defs.rb +24 -0
- data/lib/postsvg/svg/elements/ellipse.rb +38 -0
- data/lib/postsvg/svg/elements/group.rb +37 -0
- data/lib/postsvg/svg/elements/image.rb +35 -0
- data/lib/postsvg/svg/elements/line.rb +36 -0
- data/lib/postsvg/svg/elements/path.rb +32 -0
- data/lib/postsvg/svg/elements/polygon.rb +12 -0
- data/lib/postsvg/svg/elements/polyline.rb +32 -0
- data/lib/postsvg/svg/elements/rect.rb +44 -0
- data/lib/postsvg/svg/elements/svg.rb +39 -0
- data/lib/postsvg/svg/elements/text.rb +42 -0
- data/lib/postsvg/svg/elements.rb +31 -0
- data/lib/postsvg/svg/paint.rb +34 -0
- data/lib/postsvg/svg/parser.rb +39 -0
- data/lib/postsvg/svg/path_data/command.rb +27 -0
- data/lib/postsvg/svg/path_data/parser.rb +82 -0
- data/lib/postsvg/svg/path_data.rb +17 -0
- data/lib/postsvg/svg/stroke.rb +31 -0
- data/lib/postsvg/svg/transform_list.rb +59 -0
- data/lib/postsvg/svg.rb +22 -0
- data/lib/postsvg/svg_builder.rb +249 -0
- data/lib/postsvg/translation/arc_converter.rb +86 -0
- data/lib/postsvg/translation/bounding_box.rb +59 -0
- data/lib/postsvg/translation/context.rb +34 -0
- data/lib/postsvg/translation/handler_registry.rb +38 -0
- data/lib/postsvg/translation/handlers/circle_handler.rb +28 -0
- data/lib/postsvg/translation/handlers/clip_path_handler.rb +13 -0
- data/lib/postsvg/translation/handlers/defs_handler.rb +15 -0
- data/lib/postsvg/translation/handlers/ellipse_handler.rb +31 -0
- data/lib/postsvg/translation/handlers/group_handler.rb +21 -0
- data/lib/postsvg/translation/handlers/image_handler.rb +20 -0
- data/lib/postsvg/translation/handlers/line_handler.rb +23 -0
- data/lib/postsvg/translation/handlers/open_handler.rb +18 -0
- data/lib/postsvg/translation/handlers/path_handler.rb +356 -0
- data/lib/postsvg/translation/handlers/polygon_handler.rb +33 -0
- data/lib/postsvg/translation/handlers/polyline_handler.rb +31 -0
- data/lib/postsvg/translation/handlers/rect_handler.rb +27 -0
- data/lib/postsvg/translation/handlers/shared.rb +110 -0
- data/lib/postsvg/translation/handlers/svg_handler.rb +25 -0
- data/lib/postsvg/translation/handlers/text_handler.rb +56 -0
- data/lib/postsvg/translation/handlers.rb +25 -0
- data/lib/postsvg/translation/ps_renderer.rb +105 -0
- data/lib/postsvg/translation/record_emitter.rb +35 -0
- data/lib/postsvg/translation.rb +17 -0
- data/lib/postsvg/version.rb +1 -1
- data/lib/postsvg/visitors/ps_visitor/arithmetic.rb +125 -0
- data/lib/postsvg/visitors/ps_visitor/boolean.rb +105 -0
- data/lib/postsvg/visitors/ps_visitor/color.rb +53 -0
- data/lib/postsvg/visitors/ps_visitor/common.rb +66 -0
- data/lib/postsvg/visitors/ps_visitor/container.rb +164 -0
- data/lib/postsvg/visitors/ps_visitor/control_flow.rb +110 -0
- data/lib/postsvg/visitors/ps_visitor/device.rb +20 -0
- data/lib/postsvg/visitors/ps_visitor/dictionary.rb +89 -0
- data/lib/postsvg/visitors/ps_visitor/font.rb +93 -0
- data/lib/postsvg/visitors/ps_visitor/graphics_state.rb +55 -0
- data/lib/postsvg/visitors/ps_visitor/painting.rb +90 -0
- data/lib/postsvg/visitors/ps_visitor/path.rb +112 -0
- data/lib/postsvg/visitors/ps_visitor/stack.rb +47 -0
- data/lib/postsvg/visitors/ps_visitor/transformations.rb +101 -0
- data/lib/postsvg/visitors/ps_visitor.rb +208 -0
- data/lib/postsvg/visitors.rb +9 -0
- data/lib/postsvg.rb +93 -59
- data/lychee.toml +86 -0
- metadata +216 -11
- data/postsvg.gemspec +0 -38
|
@@ -0,0 +1,925 @@
|
|
|
1
|
+
= Tokenizer Class
|
|
2
|
+
:page-nav_order: 11
|
|
3
|
+
:page-parent: API Reference
|
|
4
|
+
|
|
5
|
+
== Purpose
|
|
6
|
+
|
|
7
|
+
The [`Tokenizer`](../../lib/postsvg/tokenizer.rb:8) class provides lexical analysis for PostScript code, breaking source code into discrete tokens that can be interpreted. It handles all PostScript token types including numbers, strings, operators, procedures, arrays, and dictionaries.
|
|
8
|
+
|
|
9
|
+
== References
|
|
10
|
+
|
|
11
|
+
* link:../index.adoc[Documentation Home]
|
|
12
|
+
* link:../api-reference.adoc[API Reference Overview]
|
|
13
|
+
* link:interpreter.adoc[Interpreter Class]
|
|
14
|
+
* link:converter.adoc[Converter Class]
|
|
15
|
+
* link:../architecture.adoc[Architecture Overview]
|
|
16
|
+
|
|
17
|
+
== Concepts
|
|
18
|
+
|
|
19
|
+
**Tokenization**:: The process of breaking source code into meaningful units (tokens) for interpretation.
|
|
20
|
+
|
|
21
|
+
**Lexical Analysis**:: The first phase of parsing that converts character sequences into tokens.
|
|
22
|
+
|
|
23
|
+
**Token**:: A categorized unit of PostScript code with a type and value (e.g., number, string, operator).
|
|
24
|
+
|
|
25
|
+
**PostScript Syntax**:: The grammar rules defining valid PostScript code structure, including literals, operators, and composite objects.
|
|
26
|
+
|
|
27
|
+
**Escape Sequences**:: Special character sequences in strings starting with backslash (`\`) that represent non-printable or special characters.
|
|
28
|
+
|
|
29
|
+
**Hexadecimal Strings**:: String literals encoded in hexadecimal format, enclosed in angle brackets `<...>`.
|
|
30
|
+
|
|
31
|
+
== Class Overview
|
|
32
|
+
|
|
33
|
+
The [`Tokenizer`](../../lib/postsvg/tokenizer.rb:8) class is defined in [`lib/postsvg/tokenizer.rb`](../../lib/postsvg/tokenizer.rb:1).
|
|
34
|
+
|
|
35
|
+
**Responsibilities:**
|
|
36
|
+
|
|
37
|
+
* Parse PostScript source code into tokens
|
|
38
|
+
* Remove PostScript comments
|
|
39
|
+
* Handle string escape sequences
|
|
40
|
+
* Parse numeric literals (integers, floats, scientific notation)
|
|
41
|
+
* Recognize operators and names
|
|
42
|
+
* Parse composite structures (procedures, arrays, dictionaries)
|
|
43
|
+
* Handle hexadecimal string encoding
|
|
44
|
+
|
|
45
|
+
**Token Structure:**
|
|
46
|
+
|
|
47
|
+
Tokens are represented using the [`Token`](../../lib/postsvg/tokenizer.rb:5) struct:
|
|
48
|
+
|
|
49
|
+
[source,ruby]
|
|
50
|
+
----
|
|
51
|
+
Token = Struct.new(:type, :value, keyword_init: true)
|
|
52
|
+
----
|
|
53
|
+
|
|
54
|
+
**Token Types:**
|
|
55
|
+
|
|
56
|
+
* `"number"` - Numeric literals
|
|
57
|
+
* `"string"` - String literals in parentheses
|
|
58
|
+
* `"hexstring"` - Hexadecimal encoded strings
|
|
59
|
+
* `"operator"` - PostScript operators
|
|
60
|
+
* `"name"` - Name literals (start with `/`)
|
|
61
|
+
* `"brace"` - Procedure delimiters `{` `}`
|
|
62
|
+
* `"bracket"` - Array delimiters `[` `]`
|
|
63
|
+
* `"dict"` - Dictionary delimiters `<<` `>>`
|
|
64
|
+
|
|
65
|
+
== Class Methods
|
|
66
|
+
|
|
67
|
+
=== tokenize
|
|
68
|
+
|
|
69
|
+
Tokenize PostScript source code into an array of tokens.
|
|
70
|
+
|
|
71
|
+
**Syntax:**
|
|
72
|
+
|
|
73
|
+
[source,ruby]
|
|
74
|
+
----
|
|
75
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code) <1>
|
|
76
|
+
----
|
|
77
|
+
<1> Parse PostScript code and return token array
|
|
78
|
+
|
|
79
|
+
**Where:**
|
|
80
|
+
|
|
81
|
+
`ps_code`:: String containing PostScript source code
|
|
82
|
+
|
|
83
|
+
**Returns:**
|
|
84
|
+
|
|
85
|
+
Array of [`Token`](../../lib/postsvg/tokenizer.rb:5) objects, each with:
|
|
86
|
+
* `type` - String indicating token type
|
|
87
|
+
* `value` - Token value (String or as-is)
|
|
88
|
+
|
|
89
|
+
**Processing Steps:**
|
|
90
|
+
|
|
91
|
+
1. Remove PostScript comments (text from `%` to end of line)
|
|
92
|
+
2. Skip whitespace characters
|
|
93
|
+
3. Match and classify tokens sequentially
|
|
94
|
+
4. Handle escape sequences in strings
|
|
95
|
+
5. Convert hexadecimal strings to binary
|
|
96
|
+
|
|
97
|
+
**Source:**
|
|
98
|
+
|
|
99
|
+
[`lib/postsvg/tokenizer.rb:9-33`](../../lib/postsvg/tokenizer.rb:9)
|
|
100
|
+
|
|
101
|
+
.Basic tokenization
|
|
102
|
+
[example]
|
|
103
|
+
====
|
|
104
|
+
[source,ruby]
|
|
105
|
+
----
|
|
106
|
+
require 'postsvg'
|
|
107
|
+
|
|
108
|
+
ps_code = "10 20 add"
|
|
109
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
110
|
+
|
|
111
|
+
tokens.each do |token|
|
|
112
|
+
puts "#{token.type}: #{token.value}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Output:
|
|
116
|
+
# number: 10
|
|
117
|
+
# number: 20
|
|
118
|
+
# operator: add
|
|
119
|
+
----
|
|
120
|
+
====
|
|
121
|
+
|
|
122
|
+
.Tokenize complex PostScript
|
|
123
|
+
[example]
|
|
124
|
+
====
|
|
125
|
+
[source,ruby]
|
|
126
|
+
----
|
|
127
|
+
ps_code = <<~PS
|
|
128
|
+
/name 100 def
|
|
129
|
+
{ 10 20 moveto } exec
|
|
130
|
+
[1 2 3] length
|
|
131
|
+
PS
|
|
132
|
+
|
|
133
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
134
|
+
|
|
135
|
+
puts "Total tokens: #{tokens.length}"
|
|
136
|
+
tokens.each do |token|
|
|
137
|
+
puts " #{token.type.ljust(10)} #{token.value}"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Output:
|
|
141
|
+
# Total tokens: 15
|
|
142
|
+
# name name
|
|
143
|
+
# number 100
|
|
144
|
+
# operator def
|
|
145
|
+
# brace {
|
|
146
|
+
# number 10
|
|
147
|
+
# number 20
|
|
148
|
+
# operator moveto
|
|
149
|
+
# brace }
|
|
150
|
+
# operator exec
|
|
151
|
+
# bracket [
|
|
152
|
+
# number 1
|
|
153
|
+
# number 2
|
|
154
|
+
# number 3
|
|
155
|
+
# bracket ]
|
|
156
|
+
# operator length
|
|
157
|
+
----
|
|
158
|
+
====
|
|
159
|
+
|
|
160
|
+
.Handle comments
|
|
161
|
+
[example]
|
|
162
|
+
====
|
|
163
|
+
[source,ruby]
|
|
164
|
+
----
|
|
165
|
+
ps_code = <<~PS
|
|
166
|
+
% This is a comment
|
|
167
|
+
10 20 add % inline comment
|
|
168
|
+
% Another comment
|
|
169
|
+
30 sub
|
|
170
|
+
PS
|
|
171
|
+
|
|
172
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
173
|
+
|
|
174
|
+
# Comments are removed during tokenization
|
|
175
|
+
puts tokens.map(&:value).join(" ")
|
|
176
|
+
# → "10 20 add 30 sub"
|
|
177
|
+
----
|
|
178
|
+
====
|
|
179
|
+
|
|
180
|
+
.Tokenize strings
|
|
181
|
+
[example]
|
|
182
|
+
====
|
|
183
|
+
[source,ruby]
|
|
184
|
+
----
|
|
185
|
+
ps_code = '(Hello World) show'
|
|
186
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
187
|
+
|
|
188
|
+
puts tokens[0].type # → "string"
|
|
189
|
+
puts tokens[0].value # → "Hello World"
|
|
190
|
+
puts tokens[1].type # → "operator"
|
|
191
|
+
puts tokens[1].value # → "show"
|
|
192
|
+
----
|
|
193
|
+
====
|
|
194
|
+
|
|
195
|
+
=== match_token
|
|
196
|
+
|
|
197
|
+
Match a single token at the specified position (private class method).
|
|
198
|
+
|
|
199
|
+
**Syntax:**
|
|
200
|
+
|
|
201
|
+
[source,ruby]
|
|
202
|
+
----
|
|
203
|
+
token, new_index = Postsvg::Tokenizer.match_token(ps, index) <1>
|
|
204
|
+
----
|
|
205
|
+
<1> Internal method for matching tokens
|
|
206
|
+
|
|
207
|
+
**Where:**
|
|
208
|
+
|
|
209
|
+
`ps`:: PostScript source string
|
|
210
|
+
|
|
211
|
+
`index`:: Current parsing position (Integer)
|
|
212
|
+
|
|
213
|
+
**Returns:**
|
|
214
|
+
|
|
215
|
+
Array containing:
|
|
216
|
+
* `token` - [`Token`](../../lib/postsvg/tokenizer.rb:5) object or `nil` if no match
|
|
217
|
+
* `new_index` - Updated position after token
|
|
218
|
+
|
|
219
|
+
**Matching Order:**
|
|
220
|
+
|
|
221
|
+
1. Strings `(...)`
|
|
222
|
+
2. Numbers (integers, floats, scientific notation)
|
|
223
|
+
3. Braces `{` `}`
|
|
224
|
+
4. Brackets `[` `]`
|
|
225
|
+
5. Dict markers `<<` `>>`
|
|
226
|
+
6. Hex strings `<...>`
|
|
227
|
+
7. Names `/name` and operators `name`
|
|
228
|
+
|
|
229
|
+
**Source:**
|
|
230
|
+
|
|
231
|
+
[`lib/postsvg/tokenizer.rb:35-77`](../../lib/postsvg/tokenizer.rb:35)
|
|
232
|
+
|
|
233
|
+
=== match_string
|
|
234
|
+
|
|
235
|
+
Parse a PostScript string literal (private class method).
|
|
236
|
+
|
|
237
|
+
**Syntax:**
|
|
238
|
+
|
|
239
|
+
[source,ruby]
|
|
240
|
+
----
|
|
241
|
+
token, new_index = Postsvg::Tokenizer.match_string(ps, index) <1>
|
|
242
|
+
----
|
|
243
|
+
<1> Internal method for parsing strings
|
|
244
|
+
|
|
245
|
+
**Where:**
|
|
246
|
+
|
|
247
|
+
`ps`:: PostScript source string
|
|
248
|
+
|
|
249
|
+
`index`:: Position of opening `(`
|
|
250
|
+
|
|
251
|
+
**Returns:**
|
|
252
|
+
|
|
253
|
+
Array containing:
|
|
254
|
+
* `token` - Token with type `"string"` and parsed value
|
|
255
|
+
* `new_index` - Position after closing `)`
|
|
256
|
+
|
|
257
|
+
**Features:**
|
|
258
|
+
|
|
259
|
+
* Handles nested parentheses
|
|
260
|
+
* Processes escape sequences
|
|
261
|
+
* Supports multi-line strings
|
|
262
|
+
* Tracks string depth for proper closure
|
|
263
|
+
|
|
264
|
+
**Escape Sequences:**
|
|
265
|
+
|
|
266
|
+
* `\n` → newline
|
|
267
|
+
* `\r` → carriage return
|
|
268
|
+
* `\t` → tab
|
|
269
|
+
* `\b` → backspace
|
|
270
|
+
* `\f` → form feed
|
|
271
|
+
* `\(` → left parenthesis
|
|
272
|
+
* `\)` → right parenthesis
|
|
273
|
+
* `\\` → backslash
|
|
274
|
+
* `\ddd` → octal character code (up to 3 digits)
|
|
275
|
+
|
|
276
|
+
**Source:**
|
|
277
|
+
|
|
278
|
+
[`lib/postsvg/tokenizer.rb:79-140`](../../lib/postsvg/tokenizer.rb:79)
|
|
279
|
+
|
|
280
|
+
.String with escape sequences
|
|
281
|
+
[example]
|
|
282
|
+
====
|
|
283
|
+
[source,ruby]
|
|
284
|
+
----
|
|
285
|
+
ps_code = '(Line1\nLine2\tTabbed)'
|
|
286
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
287
|
+
|
|
288
|
+
puts tokens[0].value
|
|
289
|
+
# Output:
|
|
290
|
+
# Line1
|
|
291
|
+
# Line2 Tabbed
|
|
292
|
+
----
|
|
293
|
+
====
|
|
294
|
+
|
|
295
|
+
.Nested parentheses
|
|
296
|
+
[example]
|
|
297
|
+
====
|
|
298
|
+
[source,ruby]
|
|
299
|
+
----
|
|
300
|
+
ps_code = '(outer (nested) string)'
|
|
301
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
302
|
+
|
|
303
|
+
puts tokens[0].value # → "outer (nested) string"
|
|
304
|
+
----
|
|
305
|
+
====
|
|
306
|
+
|
|
307
|
+
.Octal escape sequences
|
|
308
|
+
[example]
|
|
309
|
+
====
|
|
310
|
+
[source,ruby]
|
|
311
|
+
----
|
|
312
|
+
# \101 is octal for 'A'
|
|
313
|
+
ps_code = '(\101\102\103)'
|
|
314
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
315
|
+
|
|
316
|
+
puts tokens[0].value # → "ABC"
|
|
317
|
+
----
|
|
318
|
+
====
|
|
319
|
+
|
|
320
|
+
=== match_hex_string
|
|
321
|
+
|
|
322
|
+
Parse a hexadecimal string literal (private class method).
|
|
323
|
+
|
|
324
|
+
**Syntax:**
|
|
325
|
+
|
|
326
|
+
[source,ruby]
|
|
327
|
+
----
|
|
328
|
+
token, new_index = Postsvg::Tokenizer.match_hex_string(ps, index) <1>
|
|
329
|
+
----
|
|
330
|
+
<1> Internal method for parsing hex strings
|
|
331
|
+
|
|
332
|
+
**Where:**
|
|
333
|
+
|
|
334
|
+
`ps`:: PostScript source string
|
|
335
|
+
|
|
336
|
+
`index`:: Position of opening `<`
|
|
337
|
+
|
|
338
|
+
**Returns:**
|
|
339
|
+
|
|
340
|
+
Array containing:
|
|
341
|
+
* `token` - Token with type `"hexstring"` and decoded value
|
|
342
|
+
* `new_index` - Position after closing `>`
|
|
343
|
+
|
|
344
|
+
**Processing:**
|
|
345
|
+
|
|
346
|
+
1. Extract hexadecimal characters between `<` and `>`
|
|
347
|
+
2. Ignore whitespace in hex data
|
|
348
|
+
3. Convert pairs of hex digits to bytes
|
|
349
|
+
4. Return decoded binary string
|
|
350
|
+
|
|
351
|
+
**Source:**
|
|
352
|
+
|
|
353
|
+
[`lib/postsvg/tokenizer.rb:142-159`](../../lib/postsvg/tokenizer.rb:142)
|
|
354
|
+
|
|
355
|
+
.Hexadecimal string
|
|
356
|
+
[example]
|
|
357
|
+
====
|
|
358
|
+
[source,ruby]
|
|
359
|
+
----
|
|
360
|
+
# <48656C6C6F> is "Hello" in hex
|
|
361
|
+
ps_code = '<48656C6C6F>'
|
|
362
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
363
|
+
|
|
364
|
+
puts tokens[0].type # → "hexstring"
|
|
365
|
+
puts tokens[0].value # → "Hello"
|
|
366
|
+
----
|
|
367
|
+
====
|
|
368
|
+
|
|
369
|
+
.Hex string with whitespace
|
|
370
|
+
[example]
|
|
371
|
+
====
|
|
372
|
+
[source,ruby]
|
|
373
|
+
----
|
|
374
|
+
# Whitespace is ignored in hex strings
|
|
375
|
+
ps_code = '<48 65 6C 6C 6F>'
|
|
376
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
377
|
+
|
|
378
|
+
puts tokens[0].value # → "Hello"
|
|
379
|
+
----
|
|
380
|
+
====
|
|
381
|
+
|
|
382
|
+
== Token Types
|
|
383
|
+
|
|
384
|
+
=== Number Tokens
|
|
385
|
+
|
|
386
|
+
**Formats:**
|
|
387
|
+
|
|
388
|
+
* Integers: `10`, `-5`, `0`
|
|
389
|
+
* Floats: `10.5`, `.5`, `10.`
|
|
390
|
+
* Scientific notation: `1e3`, `3.14e-2`, `2E+5`
|
|
391
|
+
|
|
392
|
+
**Detection:**
|
|
393
|
+
|
|
394
|
+
[source,ruby]
|
|
395
|
+
----
|
|
396
|
+
/\A-?(?:\d+\.\d+|\d+\.|\.\d+|\d+)(?:[eE][+-]?\d+)?/
|
|
397
|
+
----
|
|
398
|
+
|
|
399
|
+
.Number examples
|
|
400
|
+
[example]
|
|
401
|
+
====
|
|
402
|
+
[source,ruby]
|
|
403
|
+
----
|
|
404
|
+
ps_code = "10 -5 3.14 .5 1e3 2.5e-2"
|
|
405
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
406
|
+
|
|
407
|
+
tokens.each do |token|
|
|
408
|
+
puts token.value
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Output:
|
|
412
|
+
# 10
|
|
413
|
+
# -5
|
|
414
|
+
# 3.14
|
|
415
|
+
# .5
|
|
416
|
+
# 1e3
|
|
417
|
+
# 2.5e-2
|
|
418
|
+
----
|
|
419
|
+
====
|
|
420
|
+
|
|
421
|
+
=== String Tokens
|
|
422
|
+
|
|
423
|
+
**Format:** `(string content)`
|
|
424
|
+
|
|
425
|
+
**Features:**
|
|
426
|
+
|
|
427
|
+
* Nested parentheses support
|
|
428
|
+
* Escape sequence processing
|
|
429
|
+
* Multi-line strings
|
|
430
|
+
* Binary data support
|
|
431
|
+
|
|
432
|
+
.String token examples
|
|
433
|
+
[example]
|
|
434
|
+
====
|
|
435
|
+
[source,ruby]
|
|
436
|
+
----
|
|
437
|
+
examples = [
|
|
438
|
+
'(simple)',
|
|
439
|
+
'(with (nested) parens)',
|
|
440
|
+
'(line1\nline2)',
|
|
441
|
+
'(tab\there)',
|
|
442
|
+
'(\101\102\103)' # Octal
|
|
443
|
+
]
|
|
444
|
+
|
|
445
|
+
examples.each do |ps|
|
|
446
|
+
token = Postsvg::Tokenizer.tokenize(ps)[0]
|
|
447
|
+
puts "Input: #{ps}"
|
|
448
|
+
puts "Value: #{token.value.inspect}\n\n"
|
|
449
|
+
end
|
|
450
|
+
----
|
|
451
|
+
====
|
|
452
|
+
|
|
453
|
+
=== Operator Tokens
|
|
454
|
+
|
|
455
|
+
**Format:** Alphanumeric names not starting with `/`
|
|
456
|
+
|
|
457
|
+
**Pattern:** `[A-Za-z_\-.?*][A-Za-z0-9_\-.?*]*`
|
|
458
|
+
|
|
459
|
+
**Examples:** `add`, `moveto`, `gsave`, `setrgbcolor`
|
|
460
|
+
|
|
461
|
+
.Operator examples
|
|
462
|
+
[example]
|
|
463
|
+
====
|
|
464
|
+
[source,ruby]
|
|
465
|
+
----
|
|
466
|
+
ps_code = "moveto lineto stroke gsave grestore"
|
|
467
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
468
|
+
|
|
469
|
+
tokens.each do |token|
|
|
470
|
+
puts "#{token.type}: #{token.value}"
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# All recognized as operators
|
|
474
|
+
----
|
|
475
|
+
====
|
|
476
|
+
|
|
477
|
+
=== Name Tokens
|
|
478
|
+
|
|
479
|
+
**Format:** Names starting with `/`
|
|
480
|
+
|
|
481
|
+
**Value:** Name without the leading `/`
|
|
482
|
+
|
|
483
|
+
**Examples:** `/FontName`, `/MyVariable`, `/123`
|
|
484
|
+
|
|
485
|
+
.Name token examples
|
|
486
|
+
[example]
|
|
487
|
+
====
|
|
488
|
+
[source,ruby]
|
|
489
|
+
----
|
|
490
|
+
ps_code = "/name1 /name2 /123"
|
|
491
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
492
|
+
|
|
493
|
+
tokens.each do |token|
|
|
494
|
+
puts "#{token.type}: #{token.value}"
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
# Output:
|
|
498
|
+
# name: name1
|
|
499
|
+
# name: name2
|
|
500
|
+
# name: 123
|
|
501
|
+
----
|
|
502
|
+
====
|
|
503
|
+
|
|
504
|
+
=== Brace Tokens
|
|
505
|
+
|
|
506
|
+
**Format:** `{` and `}`
|
|
507
|
+
|
|
508
|
+
**Usage:** Delimit procedure bodies
|
|
509
|
+
|
|
510
|
+
.Procedure tokenization
|
|
511
|
+
[example]
|
|
512
|
+
====
|
|
513
|
+
[source,ruby]
|
|
514
|
+
----
|
|
515
|
+
ps_code = "{ 10 20 moveto }"
|
|
516
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
517
|
+
|
|
518
|
+
tokens.each do |token|
|
|
519
|
+
puts "#{token.type}: #{token.value}"
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
# Output:
|
|
523
|
+
# brace: {
|
|
524
|
+
# number: 10
|
|
525
|
+
# number: 20
|
|
526
|
+
# operator: moveto
|
|
527
|
+
# brace: }
|
|
528
|
+
----
|
|
529
|
+
====
|
|
530
|
+
|
|
531
|
+
=== Bracket Tokens
|
|
532
|
+
|
|
533
|
+
**Format:** `[` and `]`
|
|
534
|
+
|
|
535
|
+
**Usage:** Delimit array literals
|
|
536
|
+
|
|
537
|
+
.Array tokenization
|
|
538
|
+
[example]
|
|
539
|
+
====
|
|
540
|
+
[source,ruby]
|
|
541
|
+
----
|
|
542
|
+
ps_code = "[1 2 3 4 5]"
|
|
543
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
544
|
+
|
|
545
|
+
tokens.each do |token|
|
|
546
|
+
puts "#{token.type}: #{token.value}"
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# Output:
|
|
550
|
+
# bracket: [
|
|
551
|
+
# number: 1
|
|
552
|
+
# number: 2
|
|
553
|
+
# number: 3
|
|
554
|
+
# number: 4
|
|
555
|
+
# number: 5
|
|
556
|
+
# bracket: ]
|
|
557
|
+
----
|
|
558
|
+
====
|
|
559
|
+
|
|
560
|
+
=== Dict Tokens
|
|
561
|
+
|
|
562
|
+
**Format:** `<<` and `>>`
|
|
563
|
+
|
|
564
|
+
**Usage:** Delimit dictionary literals
|
|
565
|
+
|
|
566
|
+
.Dictionary tokenization
|
|
567
|
+
[example]
|
|
568
|
+
====
|
|
569
|
+
[source,ruby]
|
|
570
|
+
----
|
|
571
|
+
ps_code = "<< /Type /Pattern /Width 100 >>"
|
|
572
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
573
|
+
|
|
574
|
+
tokens.each do |token|
|
|
575
|
+
puts "#{token.type}: #{token.value}"
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
# Output:
|
|
579
|
+
# dict: <<
|
|
580
|
+
# name: Type
|
|
581
|
+
# name: Pattern
|
|
582
|
+
# name: Width
|
|
583
|
+
# number: 100
|
|
584
|
+
# dict: >>
|
|
585
|
+
----
|
|
586
|
+
====
|
|
587
|
+
|
|
588
|
+
== Usage Patterns
|
|
589
|
+
|
|
590
|
+
=== Pattern 1: Pre-processing for Interpretation
|
|
591
|
+
|
|
592
|
+
[source,ruby]
|
|
593
|
+
----
|
|
594
|
+
require 'postsvg'
|
|
595
|
+
|
|
596
|
+
def process_postscript(ps_code)
|
|
597
|
+
# Tokenize
|
|
598
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
599
|
+
|
|
600
|
+
# Analyze tokens
|
|
601
|
+
stats = {
|
|
602
|
+
total: tokens.length,
|
|
603
|
+
numbers: tokens.count { |t| t.type == "number" },
|
|
604
|
+
strings: tokens.count { |t| t.type == "string" },
|
|
605
|
+
operators: tokens.count { |t| t.type == "operator" },
|
|
606
|
+
names: tokens.count { |t| t.type == "name" }
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
puts "Token Statistics:"
|
|
610
|
+
stats.each { |key, value| puts " #{key}: #{value}" }
|
|
611
|
+
|
|
612
|
+
# Pass to interpreter
|
|
613
|
+
interpreter = Postsvg::Interpreter.new
|
|
614
|
+
interpreter.interpret(tokens, bbox)
|
|
615
|
+
end
|
|
616
|
+
----
|
|
617
|
+
|
|
618
|
+
=== Pattern 2: Token Stream Analysis
|
|
619
|
+
|
|
620
|
+
[source,ruby]
|
|
621
|
+
----
|
|
622
|
+
require 'postsvg'
|
|
623
|
+
|
|
624
|
+
class TokenAnalyzer
|
|
625
|
+
def initialize(ps_code)
|
|
626
|
+
@tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
def find_operator_usage(operator_name)
|
|
630
|
+
indices = []
|
|
631
|
+
@tokens.each_with_index do |token, i|
|
|
632
|
+
indices << i if token.type == "operator" && token.value == operator_name
|
|
633
|
+
end
|
|
634
|
+
indices
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
def extract_procedures
|
|
638
|
+
procedures = []
|
|
639
|
+
depth = 0
|
|
640
|
+
current_proc = []
|
|
641
|
+
|
|
642
|
+
@tokens.each do |token|
|
|
643
|
+
if token.type == "brace"
|
|
644
|
+
if token.value == "{"
|
|
645
|
+
depth += 1
|
|
646
|
+
current_proc = [] if depth == 1
|
|
647
|
+
elsif token.value == "}"
|
|
648
|
+
depth -= 1
|
|
649
|
+
if depth == 0 && !current_proc.empty?
|
|
650
|
+
procedures << current_proc.dup
|
|
651
|
+
current_proc = []
|
|
652
|
+
end
|
|
653
|
+
end
|
|
654
|
+
elsif depth > 0
|
|
655
|
+
current_proc << token
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
procedures
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
def list_defined_names
|
|
663
|
+
names = []
|
|
664
|
+
@tokens.each_with_index do |token, i|
|
|
665
|
+
if token.type == "name" &&
|
|
666
|
+
i + 2 < @tokens.length &&
|
|
667
|
+
@tokens[i + 2].type == "operator" &&
|
|
668
|
+
@tokens[i + 2].value == "def"
|
|
669
|
+
names << token.value
|
|
670
|
+
end
|
|
671
|
+
end
|
|
672
|
+
names
|
|
673
|
+
end
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
# Usage
|
|
677
|
+
ps_code = <<~PS
|
|
678
|
+
/myvar 100 def
|
|
679
|
+
/myproc { 10 20 moveto } def
|
|
680
|
+
myvar myproc
|
|
681
|
+
PS
|
|
682
|
+
|
|
683
|
+
analyzer = TokenAnalyzer.new(ps_code)
|
|
684
|
+
puts "Defined names: #{analyzer.list_defined_names.join(', ')}"
|
|
685
|
+
puts "Procedures found: #{analyzer.extract_procedures.length}"
|
|
686
|
+
----
|
|
687
|
+
|
|
688
|
+
=== Pattern 3: Token Filtering
|
|
689
|
+
|
|
690
|
+
[source,ruby]
|
|
691
|
+
----
|
|
692
|
+
require 'postsvg'
|
|
693
|
+
|
|
694
|
+
def filter_tokens(ps_code, &block)
|
|
695
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
696
|
+
tokens.select(&block)
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
# Find all numbers
|
|
700
|
+
numbers = filter_tokens(ps_code) { |t| t.type == "number" }
|
|
701
|
+
puts "Numbers: #{numbers.map(&:value).join(', ')}"
|
|
702
|
+
|
|
703
|
+
# Find all operators
|
|
704
|
+
operators = filter_tokens(ps_code) { |t| t.type == "operator" }
|
|
705
|
+
puts "Operators: #{operators.map(&:value).join(', ')}"
|
|
706
|
+
|
|
707
|
+
# Find all strings
|
|
708
|
+
strings = filter_tokens(ps_code) { |t| t.type == "string" }
|
|
709
|
+
puts "Strings: #{strings.length} found"
|
|
710
|
+
----
|
|
711
|
+
|
|
712
|
+
=== Pattern 4: Token Validation
|
|
713
|
+
|
|
714
|
+
[source,ruby]
|
|
715
|
+
----
|
|
716
|
+
require 'postsvg'
|
|
717
|
+
|
|
718
|
+
class TokenValidator
|
|
719
|
+
def initialize(ps_code)
|
|
720
|
+
@tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
721
|
+
@errors = []
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
def validate
|
|
725
|
+
check_balanced_braces
|
|
726
|
+
check_balanced_brackets
|
|
727
|
+
check_balanced_dicts
|
|
728
|
+
|
|
729
|
+
{
|
|
730
|
+
valid: @errors.empty?,
|
|
731
|
+
errors: @errors
|
|
732
|
+
}
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
private
|
|
736
|
+
|
|
737
|
+
def check_balanced_braces
|
|
738
|
+
depth = 0
|
|
739
|
+
@tokens.each do |token|
|
|
740
|
+
next unless token.type == "brace"
|
|
741
|
+
depth += (token.value == "{" ? 1 : -1)
|
|
742
|
+
@errors << "Unmatched '}'" if depth < 0
|
|
743
|
+
end
|
|
744
|
+
@errors << "Unclosed '{'" if depth > 0
|
|
745
|
+
end
|
|
746
|
+
|
|
747
|
+
def check_balanced_brackets
|
|
748
|
+
depth = 0
|
|
749
|
+
@tokens.each do |token|
|
|
750
|
+
next unless token.type == "bracket"
|
|
751
|
+
depth += (token.value == "[" ? 1 : -1)
|
|
752
|
+
@errors << "Unmatched ']'" if depth < 0
|
|
753
|
+
end
|
|
754
|
+
@errors << "Unclosed '['" if depth > 0
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
def check_balanced_dicts
|
|
758
|
+
depth = 0
|
|
759
|
+
@tokens.each do |token|
|
|
760
|
+
next unless token.type == "dict"
|
|
761
|
+
depth += (token.value == "<<" ? 1 : -1)
|
|
762
|
+
@errors << "Unmatched '>>'" if depth < 0
|
|
763
|
+
end
|
|
764
|
+
@errors << "Unclosed '<<'" if depth > 0
|
|
765
|
+
end
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
# Usage
|
|
769
|
+
validator = TokenValidator.new(ps_code)
|
|
770
|
+
result = validator.validate
|
|
771
|
+
|
|
772
|
+
if result[:valid]
|
|
773
|
+
puts "✓ Valid PostScript syntax"
|
|
774
|
+
else
|
|
775
|
+
puts "✗ Validation errors:"
|
|
776
|
+
result[:errors].each { |e| puts " - #{e}" }
|
|
777
|
+
end
|
|
778
|
+
----
|
|
779
|
+
|
|
780
|
+
== Thread Safety
|
|
781
|
+
|
|
782
|
+
The `Tokenizer` class is **completely thread-safe** because:
|
|
783
|
+
|
|
784
|
+
1. All methods are stateless class methods
|
|
785
|
+
2. No shared mutable state
|
|
786
|
+
3. Each tokenization creates independent token array
|
|
787
|
+
4. Pure functions (same input → same output)
|
|
788
|
+
|
|
789
|
+
.Thread-safe usage
|
|
790
|
+
[example]
|
|
791
|
+
====
|
|
792
|
+
[source,ruby]
|
|
793
|
+
----
|
|
794
|
+
# Safe: Multiple threads can tokenize concurrently
|
|
795
|
+
ps_files = Dir.glob('*.ps')
|
|
796
|
+
|
|
797
|
+
threads = ps_files.map do |file|
|
|
798
|
+
Thread.new do
|
|
799
|
+
ps_code = File.read(file)
|
|
800
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
801
|
+
{ file: file, token_count: tokens.length }
|
|
802
|
+
end
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
results = threads.map(&:value)
|
|
806
|
+
results.each do |r|
|
|
807
|
+
puts "#{r[:file]}: #{r[:token_count]} tokens"
|
|
808
|
+
end
|
|
809
|
+
----
|
|
810
|
+
====
|
|
811
|
+
|
|
812
|
+
== Performance Considerations
|
|
813
|
+
|
|
814
|
+
**Time Complexity:**
|
|
815
|
+
|
|
816
|
+
* Overall: O(n) where n = source code length
|
|
817
|
+
* Each character processed at most once
|
|
818
|
+
* Regex matching at each position
|
|
819
|
+
|
|
820
|
+
**Space Complexity:**
|
|
821
|
+
|
|
822
|
+
* O(t) where t = number of tokens
|
|
823
|
+
* Each token stores type and value
|
|
824
|
+
* String tokens duplicate content
|
|
825
|
+
|
|
826
|
+
**Performance Characteristics:**
|
|
827
|
+
|
|
828
|
+
* Linear time complexity
|
|
829
|
+
* Efficient single-pass parsing
|
|
830
|
+
* No backtracking
|
|
831
|
+
* Minimal memory overhead
|
|
832
|
+
|
|
833
|
+
.Performance measurement
|
|
834
|
+
[example]
|
|
835
|
+
====
|
|
836
|
+
[source,ruby]
|
|
837
|
+
----
|
|
838
|
+
require 'postsvg'
|
|
839
|
+
require 'benchmark'
|
|
840
|
+
|
|
841
|
+
# Generate large PostScript file
|
|
842
|
+
ps_code = (1..10_000).map do |i|
|
|
843
|
+
"#{i} #{i * 2} moveto #{i + 10} #{i * 2 + 10} lineto"
|
|
844
|
+
end.join("\n")
|
|
845
|
+
|
|
846
|
+
puts "Source size: #{ps_code.bytesize / 1024} KB"
|
|
847
|
+
|
|
848
|
+
time = Benchmark.measure do
|
|
849
|
+
@tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
puts "Tokenized in #{'%.3f' % time.real}s"
|
|
853
|
+
puts "Tokens: #{@tokens.length}"
|
|
854
|
+
puts "Rate: #{(ps_code.bytesize / time.real / 1024).to_i} KB/sec"
|
|
855
|
+
puts "Throughput: #{(@tokens.length / time.real).to_i} tokens/sec"
|
|
856
|
+
----
|
|
857
|
+
====
|
|
858
|
+
|
|
859
|
+
**Optimization Tips:**
|
|
860
|
+
|
|
861
|
+
1. **Batch processing**: Tokenize once, interpret many times if needed
|
|
862
|
+
2. **Cache tokens**: Store tokenized representation for repeated use
|
|
863
|
+
3. **Pre-filter**: Remove comments before tokenization if not needed
|
|
864
|
+
4. **Stream processing**: For huge files, consider streaming tokenization
|
|
865
|
+
|
|
866
|
+
== Error Handling
|
|
867
|
+
|
|
868
|
+
The tokenizer is designed to be tolerant:
|
|
869
|
+
|
|
870
|
+
**Behavior:**
|
|
871
|
+
|
|
872
|
+
* Invalid characters are skipped
|
|
873
|
+
* Malformed tokens result in nil return
|
|
874
|
+
* Unclosed strings/hexstrings proceed to end
|
|
875
|
+
* No exceptions thrown during tokenization
|
|
876
|
+
|
|
877
|
+
**Validation:**
|
|
878
|
+
|
|
879
|
+
Consider post-tokenization validation for critical applications (see Pattern 4 above).
|
|
880
|
+
|
|
881
|
+
.Handle potentially invalid input
|
|
882
|
+
[example]
|
|
883
|
+
====
|
|
884
|
+
[source,ruby]
|
|
885
|
+
----
|
|
886
|
+
def safe_tokenize(ps_code)
|
|
887
|
+
begin
|
|
888
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
889
|
+
|
|
890
|
+
# Check for reasonable token count
|
|
891
|
+
if tokens.length > 1_000_000
|
|
892
|
+
{ success: false, error: "Token limit exceeded" }
|
|
893
|
+
else
|
|
894
|
+
{ success: true, tokens: tokens, count: tokens.length }
|
|
895
|
+
end
|
|
896
|
+
rescue => e
|
|
897
|
+
{ success: false, error: e.message }
|
|
898
|
+
end
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
result = safe_tokenize(ps_code)
|
|
902
|
+
|
|
903
|
+
if result[:success]
|
|
904
|
+
puts "Tokenized #{result[:count]} tokens"
|
|
905
|
+
else
|
|
906
|
+
puts "Error: #{result[:error]}"
|
|
907
|
+
end
|
|
908
|
+
----
|
|
909
|
+
====
|
|
910
|
+
|
|
911
|
+
== Next Steps
|
|
912
|
+
|
|
913
|
+
* Learn about link:interpreter.adoc[Interpreter] which processes tokenized output
|
|
914
|
+
* Review link:converter.adoc[Converter] for the complete conversion pipeline
|
|
915
|
+
* See link:../architecture.adoc[Architecture] for system design
|
|
916
|
+
* Check link:../getting-started/basic-usage.adoc[Basic Usage] for examples
|
|
917
|
+
|
|
918
|
+
== Bibliography
|
|
919
|
+
|
|
920
|
+
* link:interpreter.adoc[Interpreter Documentation]
|
|
921
|
+
* link:converter.adoc[Converter Documentation]
|
|
922
|
+
* link:../architecture.adoc[Architecture Overview]
|
|
923
|
+
* link:../getting-started/basic-usage.adoc[Basic Usage Guide]
|
|
924
|
+
* link:https://www.adobe.com/jp/print/postscript/pdfs/PLRM.pdf[PostScript Language Reference Manual - Syntax]
|
|
925
|
+
* link:https://en.wikipedia.org/wiki/Lexical_analysis[Wikipedia: Lexical Analysis]
|