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.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +105 -0
  3. data/CLAUDE.md +173 -0
  4. data/Gemfile +2 -3
  5. data/README.adoc +456 -179
  6. data/Rakefile +100 -0
  7. data/TODO.roadmap/00-architecture.md +139 -0
  8. data/TODO.roadmap/01-autoload-migration.md +39 -0
  9. data/TODO.roadmap/02-isolate-dormant-code.md +51 -0
  10. data/TODO.roadmap/03-domain-model.md +66 -0
  11. data/TODO.roadmap/04-lexer.md +40 -0
  12. data/TODO.roadmap/05-parser.md +43 -0
  13. data/TODO.roadmap/06-graphics-state.md +45 -0
  14. data/TODO.roadmap/07-matrix-and-color.md +37 -0
  15. data/TODO.roadmap/08-svg-builder.md +51 -0
  16. data/TODO.roadmap/09-renderer.md +52 -0
  17. data/TODO.roadmap/10-visitor.md +58 -0
  18. data/TODO.roadmap/11-operator-coverage.md +103 -0
  19. data/TODO.roadmap/12-svg-domain.md +62 -0
  20. data/TODO.roadmap/13-translation-handlers.md +69 -0
  21. data/TODO.roadmap/14-ps-serializer.md +49 -0
  22. data/TODO.roadmap/15-cli-and-public-api.md +47 -0
  23. data/TODO.roadmap/16-specs.md +84 -0
  24. data/TODO.roadmap/17-docs-sync.md +47 -0
  25. data/TODO.roadmap/18-performance-and-determinism.md +47 -0
  26. data/TODO.roadmap/19-error-model.md +45 -0
  27. data/TODO.roadmap/20-font-and-text.md +46 -0
  28. data/TODO.roadmap/21-images.md +45 -0
  29. data/TODO.roadmap/22-forms-and-resources.md +25 -0
  30. data/TODO.roadmap/23-level2-level3.md +52 -0
  31. data/TODO.roadmap/24-ci-and-release.md +42 -0
  32. data/TODO.roadmap/README.md +77 -0
  33. data/docs/.gitignore +29 -0
  34. data/docs/CHANGELOG.md +114 -0
  35. data/docs/COMPLETE_DOCUMENTATION_STATUS.md +376 -0
  36. data/docs/DEPLOYMENT.md +456 -0
  37. data/docs/DEPLOYMENT_INSTRUCTIONS.md +229 -0
  38. data/docs/DOCUMENTATION_PLAN.md +425 -0
  39. data/docs/FINAL_SUMMARY.md +657 -0
  40. data/docs/Gemfile +15 -0
  41. data/docs/README.md +327 -0
  42. data/docs/_config.yml +99 -0
  43. data/docs/advanced-topics.adoc +370 -0
  44. data/docs/api-reference/colors.adoc +705 -0
  45. data/docs/api-reference/converter.adoc +699 -0
  46. data/docs/api-reference/execution-context.adoc +1210 -0
  47. data/docs/api-reference/graphics-state.adoc +1070 -0
  48. data/docs/api-reference/interpreter.adoc +810 -0
  49. data/docs/api-reference/matrix.adoc +1179 -0
  50. data/docs/api-reference/path-builder.adoc +1284 -0
  51. data/docs/api-reference/postsvg-module.adoc +388 -0
  52. data/docs/api-reference/svg-generator.adoc +891 -0
  53. data/docs/api-reference/tokenizer.adoc +925 -0
  54. data/docs/api-reference.adoc +221 -0
  55. data/docs/architecture/command-registry.adoc +1191 -0
  56. data/docs/architecture/conversion-pipeline.adoc +746 -0
  57. data/docs/architecture/design-decisions.adoc +999 -0
  58. data/docs/architecture/generator-stage.adoc +1115 -0
  59. data/docs/architecture/graphics-state-model.adoc +1089 -0
  60. data/docs/architecture/interpreter-stage.adoc +1125 -0
  61. data/docs/architecture/parser-stage.adoc +1051 -0
  62. data/docs/architecture.adoc +354 -0
  63. data/docs/cli-reference/batch-command.adoc +616 -0
  64. data/docs/cli-reference/check-command.adoc +677 -0
  65. data/docs/cli-reference/cli-options.adoc +802 -0
  66. data/docs/cli-reference/convert-command.adoc +462 -0
  67. data/docs/cli-reference/version-command.adoc +296 -0
  68. data/docs/cli-reference.adoc +317 -0
  69. data/docs/concepts/conversion-pipeline.adoc +903 -0
  70. data/docs/concepts/coordinate-systems.adoc +836 -0
  71. data/docs/concepts/graphics-state.adoc +861 -0
  72. data/docs/concepts/path-operations.adoc +1076 -0
  73. data/docs/concepts/postscript-language.adoc +859 -0
  74. data/docs/concepts/svg-generation.adoc +937 -0
  75. data/docs/concepts.adoc +198 -0
  76. data/docs/contributing.adoc +443 -0
  77. data/docs/development.adoc +420 -0
  78. data/docs/faq.adoc +493 -0
  79. data/docs/getting-started/basic-usage.adoc +538 -0
  80. data/docs/getting-started/common-workflows.adoc +577 -0
  81. data/docs/getting-started/first-conversion.adoc +492 -0
  82. data/docs/getting-started/installation.adoc +534 -0
  83. data/docs/getting-started.adoc +94 -0
  84. data/docs/index.adoc +248 -0
  85. data/docs/optimization.adoc +196 -0
  86. data/docs/ps2svg_compatibility.adoc +149 -0
  87. data/docs/quick-reference.adoc +453 -0
  88. data/docs/sitemap.adoc +337 -0
  89. data/docs/troubleshooting.adoc +486 -0
  90. data/docs/validation.adoc +772 -0
  91. data/exe/postsvg +1 -0
  92. data/lib/postsvg/cli.rb +104 -57
  93. data/lib/postsvg/color.rb +132 -0
  94. data/lib/postsvg/errors.rb +68 -3
  95. data/lib/postsvg/format_number.rb +22 -0
  96. data/lib/postsvg/graphics_context.rb +80 -0
  97. data/lib/postsvg/graphics_stack.rb +43 -0
  98. data/lib/postsvg/model/literals/array.rb +41 -0
  99. data/lib/postsvg/model/literals/dictionary.rb +34 -0
  100. data/lib/postsvg/model/literals/hex.rb +37 -0
  101. data/lib/postsvg/model/literals/name.rb +40 -0
  102. data/lib/postsvg/model/literals/number.rb +36 -0
  103. data/lib/postsvg/model/literals/procedure.rb +41 -0
  104. data/lib/postsvg/model/literals/string.rb +30 -0
  105. data/lib/postsvg/model/literals.rb +19 -0
  106. data/lib/postsvg/model/operator.rb +58 -0
  107. data/lib/postsvg/model/operators/arithmetic.rb +264 -0
  108. data/lib/postsvg/model/operators/boolean.rb +182 -0
  109. data/lib/postsvg/model/operators/color.rb +74 -0
  110. data/lib/postsvg/model/operators/container.rb +186 -0
  111. data/lib/postsvg/model/operators/control_flow.rb +119 -0
  112. data/lib/postsvg/model/operators/device.rb +21 -0
  113. data/lib/postsvg/model/operators/dictionary.rb +118 -0
  114. data/lib/postsvg/model/operators/font.rb +121 -0
  115. data/lib/postsvg/model/operators/graphics_state.rb +84 -0
  116. data/lib/postsvg/model/operators/painting.rb +29 -0
  117. data/lib/postsvg/model/operators/path.rb +169 -0
  118. data/lib/postsvg/model/operators/stack.rb +72 -0
  119. data/lib/postsvg/model/operators/transformations.rb +103 -0
  120. data/lib/postsvg/model/operators.rb +89 -0
  121. data/lib/postsvg/model/program.rb +68 -0
  122. data/lib/postsvg/model/token.rb +43 -0
  123. data/lib/postsvg/model.rb +17 -0
  124. data/lib/postsvg/options.rb +29 -0
  125. data/lib/postsvg/renderer.rb +85 -0
  126. data/lib/postsvg/serializer.rb +325 -0
  127. data/lib/postsvg/source/ast_builder.rb +308 -0
  128. data/lib/postsvg/source/lexer.rb +322 -0
  129. data/lib/postsvg/source/operand_stack.rb +55 -0
  130. data/lib/postsvg/source.rb +21 -0
  131. data/lib/postsvg/svg/attribute_parser.rb +45 -0
  132. data/lib/postsvg/svg/clip_path_registry.rb +44 -0
  133. data/lib/postsvg/svg/document.rb +22 -0
  134. data/lib/postsvg/svg/element.rb +84 -0
  135. data/lib/postsvg/svg/elements/circle.rb +36 -0
  136. data/lib/postsvg/svg/elements/clip_path.rb +26 -0
  137. data/lib/postsvg/svg/elements/defs.rb +24 -0
  138. data/lib/postsvg/svg/elements/ellipse.rb +38 -0
  139. data/lib/postsvg/svg/elements/group.rb +37 -0
  140. data/lib/postsvg/svg/elements/image.rb +35 -0
  141. data/lib/postsvg/svg/elements/line.rb +36 -0
  142. data/lib/postsvg/svg/elements/path.rb +32 -0
  143. data/lib/postsvg/svg/elements/polygon.rb +12 -0
  144. data/lib/postsvg/svg/elements/polyline.rb +32 -0
  145. data/lib/postsvg/svg/elements/rect.rb +44 -0
  146. data/lib/postsvg/svg/elements/svg.rb +39 -0
  147. data/lib/postsvg/svg/elements/text.rb +42 -0
  148. data/lib/postsvg/svg/elements.rb +31 -0
  149. data/lib/postsvg/svg/paint.rb +34 -0
  150. data/lib/postsvg/svg/parser.rb +39 -0
  151. data/lib/postsvg/svg/path_data/command.rb +27 -0
  152. data/lib/postsvg/svg/path_data/parser.rb +82 -0
  153. data/lib/postsvg/svg/path_data.rb +17 -0
  154. data/lib/postsvg/svg/stroke.rb +31 -0
  155. data/lib/postsvg/svg/transform_list.rb +59 -0
  156. data/lib/postsvg/svg.rb +22 -0
  157. data/lib/postsvg/svg_builder.rb +249 -0
  158. data/lib/postsvg/translation/arc_converter.rb +86 -0
  159. data/lib/postsvg/translation/bounding_box.rb +59 -0
  160. data/lib/postsvg/translation/context.rb +34 -0
  161. data/lib/postsvg/translation/handler_registry.rb +38 -0
  162. data/lib/postsvg/translation/handlers/circle_handler.rb +28 -0
  163. data/lib/postsvg/translation/handlers/clip_path_handler.rb +13 -0
  164. data/lib/postsvg/translation/handlers/defs_handler.rb +15 -0
  165. data/lib/postsvg/translation/handlers/ellipse_handler.rb +31 -0
  166. data/lib/postsvg/translation/handlers/group_handler.rb +21 -0
  167. data/lib/postsvg/translation/handlers/image_handler.rb +20 -0
  168. data/lib/postsvg/translation/handlers/line_handler.rb +23 -0
  169. data/lib/postsvg/translation/handlers/open_handler.rb +18 -0
  170. data/lib/postsvg/translation/handlers/path_handler.rb +356 -0
  171. data/lib/postsvg/translation/handlers/polygon_handler.rb +33 -0
  172. data/lib/postsvg/translation/handlers/polyline_handler.rb +31 -0
  173. data/lib/postsvg/translation/handlers/rect_handler.rb +27 -0
  174. data/lib/postsvg/translation/handlers/shared.rb +110 -0
  175. data/lib/postsvg/translation/handlers/svg_handler.rb +25 -0
  176. data/lib/postsvg/translation/handlers/text_handler.rb +56 -0
  177. data/lib/postsvg/translation/handlers.rb +25 -0
  178. data/lib/postsvg/translation/ps_renderer.rb +105 -0
  179. data/lib/postsvg/translation/record_emitter.rb +35 -0
  180. data/lib/postsvg/translation.rb +17 -0
  181. data/lib/postsvg/version.rb +1 -1
  182. data/lib/postsvg/visitors/ps_visitor/arithmetic.rb +125 -0
  183. data/lib/postsvg/visitors/ps_visitor/boolean.rb +105 -0
  184. data/lib/postsvg/visitors/ps_visitor/color.rb +53 -0
  185. data/lib/postsvg/visitors/ps_visitor/common.rb +66 -0
  186. data/lib/postsvg/visitors/ps_visitor/container.rb +164 -0
  187. data/lib/postsvg/visitors/ps_visitor/control_flow.rb +110 -0
  188. data/lib/postsvg/visitors/ps_visitor/device.rb +20 -0
  189. data/lib/postsvg/visitors/ps_visitor/dictionary.rb +89 -0
  190. data/lib/postsvg/visitors/ps_visitor/font.rb +93 -0
  191. data/lib/postsvg/visitors/ps_visitor/graphics_state.rb +55 -0
  192. data/lib/postsvg/visitors/ps_visitor/painting.rb +90 -0
  193. data/lib/postsvg/visitors/ps_visitor/path.rb +112 -0
  194. data/lib/postsvg/visitors/ps_visitor/stack.rb +47 -0
  195. data/lib/postsvg/visitors/ps_visitor/transformations.rb +101 -0
  196. data/lib/postsvg/visitors/ps_visitor.rb +208 -0
  197. data/lib/postsvg/visitors.rb +9 -0
  198. data/lib/postsvg.rb +93 -59
  199. data/lychee.toml +86 -0
  200. metadata +216 -11
  201. data/postsvg.gemspec +0 -38
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postsvg
4
+ module Model
5
+ module Literals
6
+ # A PostScript name. +literal:+ distinguishes +/foo+ (literal,
7
+ # pushed onto the stack) from +foo+ (executable, looked up and
8
+ # executed).
9
+ class Name
10
+ attr_reader :value, :literal
11
+
12
+ def initialize(value, literal: false)
13
+ @value = value.to_s
14
+ @literal = literal
15
+ freeze
16
+ end
17
+
18
+ def literal? = literal
19
+ def executable? = !literal
20
+
21
+ def to_s
22
+ @value
23
+ end
24
+
25
+ def accept(visitor, ctx)
26
+ visitor.visit_name(self, ctx)
27
+ end
28
+
29
+ def ==(other)
30
+ other.is_a?(Name) && other.value == value && other.literal == literal
31
+ end
32
+ alias eql? ==
33
+
34
+ def hash
35
+ [value, literal].hash
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postsvg
4
+ module Model
5
+ module Literals
6
+ # A PostScript numeric literal. Stored as a Ruby Numeric so
7
+ # arithmetic Just Works; class carries the original source
8
+ # representation choice (int vs float) when known.
9
+ class Number
10
+ attr_reader :value
11
+
12
+ def initialize(value)
13
+ @value = value.is_a?(Numeric) ? value : Float(value)
14
+ freeze
15
+ end
16
+
17
+ def to_i = value.to_i
18
+ def to_f = value.to_f
19
+ def integer? = value.is_a?(Integer)
20
+
21
+ def accept(visitor, ctx)
22
+ visitor.visit_number(self, ctx)
23
+ end
24
+
25
+ def ==(other)
26
+ other.is_a?(Number) && other.value == value
27
+ end
28
+ alias eql? ==
29
+
30
+ def hash
31
+ value.hash
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postsvg
4
+ module Model
5
+ module Literals
6
+ # Procedure body: +{ ... }+. Carries the inner token stream as a
7
+ # list of literal/operator nodes. Procedures are not executed by
8
+ # the parser; the renderer descends into them on InvokeProcedure.
9
+ class Procedure
10
+ include Enumerable
11
+
12
+ attr_reader :body
13
+
14
+ def initialize(body = [])
15
+ @body = body.freeze
16
+ freeze
17
+ end
18
+
19
+ def each(&block)
20
+ @body.each(&block)
21
+ end
22
+
23
+ def length = @body.length
24
+ def empty? = @body.empty?
25
+
26
+ def accept(visitor, ctx)
27
+ visitor.visit_procedure(self, ctx)
28
+ end
29
+
30
+ def ==(other)
31
+ other.is_a?(Procedure) && other.body == @body
32
+ end
33
+ alias eql? ==
34
+
35
+ def hash
36
+ @body.hash
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postsvg
4
+ module Model
5
+ module Literals
6
+ # Parenthesized string literal: +(foo bar baz)+.
7
+ class StringLiteral
8
+ attr_reader :value
9
+
10
+ def initialize(value)
11
+ @value = value.to_s
12
+ freeze
13
+ end
14
+
15
+ def accept(visitor, ctx)
16
+ visitor.visit_string_literal(self, ctx)
17
+ end
18
+
19
+ def ==(other)
20
+ other.is_a?(StringLiteral) && other.value == value
21
+ end
22
+ alias eql? ==
23
+
24
+ def hash
25
+ value.hash
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postsvg
4
+ module Model
5
+ # PS literal value objects. Each is immutable, has value equality,
6
+ # and knows how to (a) emit itself to a serializer and (b) be
7
+ # visited by a renderer. They never carry executable behaviour —
8
+ # they are data.
9
+ module Literals
10
+ autoload :Number, "postsvg/model/literals/number"
11
+ autoload :Name, "postsvg/model/literals/name"
12
+ autoload :StringLiteral, "postsvg/model/literals/string"
13
+ autoload :HexLiteral, "postsvg/model/literals/hex"
14
+ autoload :ArrayLiteral, "postsvg/model/literals/array"
15
+ autoload :Procedure, "postsvg/model/literals/procedure"
16
+ autoload :Dictionary, "postsvg/model/literals/dictionary"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postsvg
4
+ module Model
5
+ # Common base for every PS operator. Subclasses live under
6
+ # Model::Operators::* grouped by PLRM chapter.
7
+ #
8
+ # Each operator declares:
9
+ # - +keyword+ (the PS source token, e.g. "moveto")
10
+ # - typed operand fields via +attr_reader+
11
+ # - +self.from_operands(stack)+ that pops operands and returns a new
12
+ # frozen instance
13
+ # - +self.visit_name+ (defaults to the keyword)
14
+ # - +self.consumes+ / +self.produces+ (stack-arity, for parser
15
+ # placeholders)
16
+ #
17
+ # The visitor calls +operator.accept(visitor, ctx)+ which routes to
18
+ # +visitor.visit_<visit_name>(operator, ctx)+ via +public_send+.
19
+ class Operator
20
+ class << self
21
+ # Register this class under +keyword+ in the global Registry.
22
+ # Called at the bottom of each subclass definition.
23
+ def register_as(keyword, consumes: 0, produces: 0)
24
+ Operators.register(keyword, self)
25
+ define_method(:keyword) { keyword }
26
+ define_method(:visit_name) { keyword }
27
+ @consumes = consumes
28
+ @produces = produces
29
+ end
30
+
31
+ attr_reader :consumes, :produces
32
+
33
+ # Default: a no-op factory that pops nothing. Subclasses
34
+ # override to consume operands off the parse stack.
35
+ def from_operands(_stack)
36
+ new
37
+ end
38
+ end
39
+
40
+ def accept(visitor, ctx)
41
+ visitor.dispatch(:"visit_#{visit_name}", self, ctx)
42
+ end
43
+
44
+ def operator? = true
45
+ end
46
+
47
+ # Sentinel value the parser pushes onto the operand stack when an
48
+ # operator's runtime result is unknown at parse time (e.g. the
49
+ # output of +findfont+, +add+, +dup+). Carries no semantic value;
50
+ # its purpose is to keep the parse-stack shape in sync with the
51
+ # runtime stack so chained operators parse cleanly.
52
+ class Computed < ::Struct.new(:operator_keyword, keyword_init: true)
53
+ def to_s
54
+ "<computed:#{operator_keyword}>"
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,264 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postsvg
4
+ module Model
5
+ module Operators
6
+ module Arithmetic
7
+ BINARY_DEFAULT = { consumes: 2, produces: 1 }.freeze
8
+ UNARY_DEFAULT = { consumes: 1, produces: 1 }.freeze
9
+
10
+ class Add < Operator
11
+ register_as "add", **BINARY_DEFAULT
12
+ def self.from_operands(stack)
13
+ b = stack.pop_number
14
+ a = stack.pop_number
15
+ new(operand_a: a, operand_b: b)
16
+ end
17
+ attr_reader :operand_a, :operand_b
18
+ def initialize(operand_a:, operand_b:)
19
+ @operand_a = operand_a
20
+ @operand_b = operand_b
21
+ freeze
22
+ end
23
+ end
24
+
25
+ class Sub < Operator
26
+ register_as "sub", **BINARY_DEFAULT
27
+ def self.from_operands(stack)
28
+ b = stack.pop_number
29
+ a = stack.pop_number
30
+ new(operand_a: a, operand_b: b)
31
+ end
32
+ attr_reader :operand_a, :operand_b
33
+ def initialize(operand_a:, operand_b:)
34
+ @operand_a = operand_a
35
+ @operand_b = operand_b
36
+ freeze
37
+ end
38
+ end
39
+
40
+ class Mul < Operator
41
+ register_as "mul", **BINARY_DEFAULT
42
+ def self.from_operands(stack)
43
+ b = stack.pop_number
44
+ a = stack.pop_number
45
+ new(operand_a: a, operand_b: b)
46
+ end
47
+ attr_reader :operand_a, :operand_b
48
+ def initialize(operand_a:, operand_b:)
49
+ @operand_a = operand_a
50
+ @operand_b = operand_b
51
+ freeze
52
+ end
53
+ end
54
+
55
+ class Div < Operator
56
+ register_as "div", **BINARY_DEFAULT
57
+ def self.from_operands(stack)
58
+ b = stack.pop_number
59
+ a = stack.pop_number
60
+ new(operand_a: a, operand_b: b)
61
+ end
62
+ attr_reader :operand_a, :operand_b
63
+ def initialize(operand_a:, operand_b:)
64
+ @operand_a = operand_a
65
+ @operand_b = operand_b
66
+ freeze
67
+ end
68
+ end
69
+
70
+ class Idiv < Operator
71
+ register_as "idiv", **BINARY_DEFAULT
72
+ def self.from_operands(stack)
73
+ b = stack.pop_number.to_i
74
+ a = stack.pop_number.to_i
75
+ new(operand_a: a, operand_b: b)
76
+ end
77
+ attr_reader :operand_a, :operand_b
78
+ def initialize(operand_a:, operand_b:)
79
+ @operand_a = operand_a
80
+ @operand_b = operand_b
81
+ freeze
82
+ end
83
+ end
84
+
85
+ class Mod < Operator
86
+ register_as "mod", **BINARY_DEFAULT
87
+ def self.from_operands(stack)
88
+ b = stack.pop_number.to_i
89
+ a = stack.pop_number.to_i
90
+ new(operand_a: a, operand_b: b)
91
+ end
92
+ attr_reader :operand_a, :operand_b
93
+ def initialize(operand_a:, operand_b:)
94
+ @operand_a = operand_a
95
+ @operand_b = operand_b
96
+ freeze
97
+ end
98
+ end
99
+
100
+ class Neg < Operator
101
+ register_as "neg", **UNARY_DEFAULT
102
+ attr_reader :operand
103
+ def initialize(operand:)
104
+ @operand = operand
105
+ freeze
106
+ end
107
+ def self.from_operands(stack)
108
+ new(operand: stack.pop_number)
109
+ end
110
+ end
111
+
112
+ class Abs < Operator
113
+ register_as "abs", **UNARY_DEFAULT
114
+ attr_reader :operand
115
+ def initialize(operand:)
116
+ @operand = operand
117
+ freeze
118
+ end
119
+ def self.from_operands(stack)
120
+ new(operand: stack.pop_number)
121
+ end
122
+ end
123
+
124
+ class Ceiling < Operator
125
+ register_as "ceiling", **UNARY_DEFAULT
126
+ attr_reader :operand
127
+ def initialize(operand:)
128
+ @operand = operand
129
+ freeze
130
+ end
131
+ def self.from_operands(stack)
132
+ new(operand: stack.pop_number)
133
+ end
134
+ end
135
+
136
+ class Floor < Operator
137
+ register_as "floor", **UNARY_DEFAULT
138
+ attr_reader :operand
139
+ def initialize(operand:)
140
+ @operand = operand
141
+ freeze
142
+ end
143
+ def self.from_operands(stack)
144
+ new(operand: stack.pop_number)
145
+ end
146
+ end
147
+
148
+ class Round < Operator
149
+ register_as "round", **UNARY_DEFAULT
150
+ attr_reader :operand
151
+ def initialize(operand:)
152
+ @operand = operand
153
+ freeze
154
+ end
155
+ def self.from_operands(stack)
156
+ new(operand: stack.pop_number)
157
+ end
158
+ end
159
+
160
+ class Truncate < Operator
161
+ register_as "truncate", **UNARY_DEFAULT
162
+ attr_reader :operand
163
+ def initialize(operand:)
164
+ @operand = operand
165
+ freeze
166
+ end
167
+ def self.from_operands(stack)
168
+ new(operand: stack.pop_number)
169
+ end
170
+ end
171
+
172
+ class Sqrt < Operator
173
+ register_as "sqrt", **UNARY_DEFAULT
174
+ attr_reader :operand
175
+ def initialize(operand:)
176
+ @operand = operand
177
+ freeze
178
+ end
179
+ def self.from_operands(stack)
180
+ new(operand: stack.pop_number)
181
+ end
182
+ end
183
+
184
+ class Atan < Operator
185
+ register_as "atan", **BINARY_DEFAULT
186
+ def self.from_operands(stack)
187
+ b = stack.pop_number
188
+ a = stack.pop_number
189
+ new(operand_a: a, operand_b: b)
190
+ end
191
+ attr_reader :operand_a, :operand_b
192
+ def initialize(operand_a:, operand_b:)
193
+ @operand_a = operand_a
194
+ @operand_b = operand_b
195
+ freeze
196
+ end
197
+ end
198
+
199
+ class Cos < Operator
200
+ register_as "cos", **UNARY_DEFAULT
201
+ attr_reader :operand
202
+ def initialize(operand:)
203
+ @operand = operand
204
+ freeze
205
+ end
206
+ def self.from_operands(stack)
207
+ new(operand: stack.pop_number)
208
+ end
209
+ end
210
+
211
+ class Sin < Operator
212
+ register_as "sin", **UNARY_DEFAULT
213
+ attr_reader :operand
214
+ def initialize(operand:)
215
+ @operand = operand
216
+ freeze
217
+ end
218
+ def self.from_operands(stack)
219
+ new(operand: stack.pop_number)
220
+ end
221
+ end
222
+
223
+ class Ln < Operator
224
+ register_as "ln", **UNARY_DEFAULT
225
+ attr_reader :operand
226
+ def initialize(operand:)
227
+ @operand = operand
228
+ freeze
229
+ end
230
+ def self.from_operands(stack)
231
+ new(operand: stack.pop_number)
232
+ end
233
+ end
234
+
235
+ class Log < Operator
236
+ register_as "log", **UNARY_DEFAULT
237
+ attr_reader :operand
238
+ def initialize(operand:)
239
+ @operand = operand
240
+ freeze
241
+ end
242
+ def self.from_operands(stack)
243
+ new(operand: stack.pop_number)
244
+ end
245
+ end
246
+
247
+ class Exp < Operator
248
+ register_as "exp", **BINARY_DEFAULT
249
+ def self.from_operands(stack)
250
+ b = stack.pop_number
251
+ a = stack.pop_number
252
+ new(operand_a: a, operand_b: b)
253
+ end
254
+ attr_reader :operand_a, :operand_b
255
+ def initialize(operand_a:, operand_b:)
256
+ @operand_a = operand_a
257
+ @operand_b = operand_b
258
+ freeze
259
+ end
260
+ end
261
+ end
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postsvg
4
+ module Model
5
+ module Operators
6
+ module Boolean
7
+ # PS boolean literals. They look like operator tokens but
8
+ # push constant Boolean values. Modeled as zero-arity
9
+ # operators so the parser/registry handle them naturally.
10
+ class True < Operator
11
+ register_as "true", consumes: 0, produces: 1
12
+ end
13
+
14
+ class False < Operator
15
+ register_as "false", consumes: 0, produces: 1
16
+ end
17
+
18
+ class Eq < Operator
19
+ register_as "eq"
20
+ def self.from_operands(stack)
21
+ b = stack.pop
22
+ a = stack.pop
23
+ new(operand_a: a, operand_b: b)
24
+ end
25
+ attr_reader :operand_a, :operand_b
26
+ def initialize(operand_a:, operand_b:)
27
+ @operand_a = operand_a
28
+ @operand_b = operand_b
29
+ freeze
30
+ end
31
+ end
32
+
33
+ class Ne < Operator
34
+ register_as "ne"
35
+ def self.from_operands(stack)
36
+ b = stack.pop
37
+ a = stack.pop
38
+ new(operand_a: a, operand_b: b)
39
+ end
40
+ attr_reader :operand_a, :operand_b
41
+ def initialize(operand_a:, operand_b:)
42
+ @operand_a = operand_a
43
+ @operand_b = operand_b
44
+ freeze
45
+ end
46
+ end
47
+
48
+ class Gt < Operator
49
+ register_as "gt"
50
+ def self.from_operands(stack)
51
+ b = stack.pop_number
52
+ a = stack.pop_number
53
+ new(operand_a: a, operand_b: b)
54
+ end
55
+ attr_reader :operand_a, :operand_b
56
+ def initialize(operand_a:, operand_b:)
57
+ @operand_a = operand_a
58
+ @operand_b = operand_b
59
+ freeze
60
+ end
61
+ end
62
+
63
+ class Ge < Operator
64
+ register_as "ge"
65
+ def self.from_operands(stack)
66
+ b = stack.pop_number
67
+ a = stack.pop_number
68
+ new(operand_a: a, operand_b: b)
69
+ end
70
+ attr_reader :operand_a, :operand_b
71
+ def initialize(operand_a:, operand_b:)
72
+ @operand_a = operand_a
73
+ @operand_b = operand_b
74
+ freeze
75
+ end
76
+ end
77
+
78
+ class Lt < Operator
79
+ register_as "lt"
80
+ def self.from_operands(stack)
81
+ b = stack.pop_number
82
+ a = stack.pop_number
83
+ new(operand_a: a, operand_b: b)
84
+ end
85
+ attr_reader :operand_a, :operand_b
86
+ def initialize(operand_a:, operand_b:)
87
+ @operand_a = operand_a
88
+ @operand_b = operand_b
89
+ freeze
90
+ end
91
+ end
92
+
93
+ class Le < Operator
94
+ register_as "le"
95
+ def self.from_operands(stack)
96
+ b = stack.pop_number
97
+ a = stack.pop_number
98
+ new(operand_a: a, operand_b: b)
99
+ end
100
+ attr_reader :operand_a, :operand_b
101
+ def initialize(operand_a:, operand_b:)
102
+ @operand_a = operand_a
103
+ @operand_b = operand_b
104
+ freeze
105
+ end
106
+ end
107
+
108
+ class And < Operator
109
+ register_as "and"
110
+ def self.from_operands(stack)
111
+ b = stack.pop
112
+ a = stack.pop
113
+ new(operand_a: a, operand_b: b)
114
+ end
115
+ attr_reader :operand_a, :operand_b
116
+ def initialize(operand_a:, operand_b:)
117
+ @operand_a = operand_a
118
+ @operand_b = operand_b
119
+ freeze
120
+ end
121
+ end
122
+
123
+ class Or < Operator
124
+ register_as "or"
125
+ def self.from_operands(stack)
126
+ b = stack.pop
127
+ a = stack.pop
128
+ new(operand_a: a, operand_b: b)
129
+ end
130
+ attr_reader :operand_a, :operand_b
131
+ def initialize(operand_a:, operand_b:)
132
+ @operand_a = operand_a
133
+ @operand_b = operand_b
134
+ freeze
135
+ end
136
+ end
137
+
138
+ class Xor < Operator
139
+ register_as "xor"
140
+ def self.from_operands(stack)
141
+ b = stack.pop
142
+ a = stack.pop
143
+ new(operand_a: a, operand_b: b)
144
+ end
145
+ attr_reader :operand_a, :operand_b
146
+ def initialize(operand_a:, operand_b:)
147
+ @operand_a = operand_a
148
+ @operand_b = operand_b
149
+ freeze
150
+ end
151
+ end
152
+
153
+ class Not < Operator
154
+ register_as "not"
155
+ attr_reader :operand
156
+ def initialize(operand:)
157
+ @operand = operand
158
+ freeze
159
+ end
160
+ def self.from_operands(stack)
161
+ new(operand: stack.pop)
162
+ end
163
+ end
164
+
165
+ class Bitshift < Operator
166
+ register_as "bitshift"
167
+ def self.from_operands(stack)
168
+ shift = stack.pop_number.to_i
169
+ value = stack.pop_number.to_i
170
+ new(operand: value, shift: shift)
171
+ end
172
+ attr_reader :operand, :shift
173
+ def initialize(operand:, shift:)
174
+ @operand = operand
175
+ @shift = shift
176
+ freeze
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end