fmt 0.1.2 → 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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +170 -87
  3. data/lib/fmt/boot.rb +50 -0
  4. data/lib/fmt/lru_cache.rb +181 -0
  5. data/lib/fmt/mixins/matchable.rb +26 -0
  6. data/lib/fmt/models/arguments.rb +194 -0
  7. data/lib/fmt/models/embed.rb +48 -0
  8. data/lib/fmt/models/macro.rb +58 -0
  9. data/lib/fmt/models/model.rb +66 -0
  10. data/lib/fmt/models/pipeline.rb +47 -0
  11. data/lib/fmt/models/template.rb +55 -0
  12. data/lib/fmt/node.rb +128 -0
  13. data/lib/fmt/parsers/arguments_parser.rb +43 -0
  14. data/lib/fmt/parsers/embed_parser.rb +54 -0
  15. data/lib/fmt/parsers/macro_parser.rb +113 -0
  16. data/lib/fmt/parsers/parser.rb +56 -0
  17. data/lib/fmt/parsers/pipeline_parser.rb +41 -0
  18. data/lib/fmt/parsers/template_parser.rb +125 -0
  19. data/lib/fmt/refinements/kernel_refinement.rb +38 -0
  20. data/lib/fmt/registries/native_registry.rb +66 -0
  21. data/lib/fmt/registries/rainbow_registry.rb +36 -0
  22. data/lib/fmt/registries/registry.rb +127 -0
  23. data/lib/fmt/renderer.rb +132 -0
  24. data/lib/fmt/sigils.rb +23 -0
  25. data/lib/fmt/token.rb +126 -0
  26. data/lib/fmt/tokenizer.rb +96 -0
  27. data/lib/fmt/version.rb +3 -1
  28. data/lib/fmt.rb +51 -11
  29. data/sig/generated/fmt/boot.rbs +2 -0
  30. data/sig/generated/fmt/lru_cache.rbs +122 -0
  31. data/sig/generated/fmt/mixins/matchable.rbs +18 -0
  32. data/sig/generated/fmt/models/arguments.rbs +115 -0
  33. data/sig/generated/fmt/models/embed.rbs +34 -0
  34. data/sig/generated/fmt/models/macro.rbs +37 -0
  35. data/sig/generated/fmt/models/model.rbs +45 -0
  36. data/sig/generated/fmt/models/pipeline.rbs +31 -0
  37. data/sig/generated/fmt/models/template.rbs +33 -0
  38. data/sig/generated/fmt/node.rbs +64 -0
  39. data/sig/generated/fmt/parsers/arguments_parser.rbs +25 -0
  40. data/sig/generated/fmt/parsers/embed_parser.rbs +36 -0
  41. data/sig/generated/fmt/parsers/macro_parser.rbs +60 -0
  42. data/sig/generated/fmt/parsers/parser.rbs +44 -0
  43. data/sig/generated/fmt/parsers/pipeline_parser.rbs +25 -0
  44. data/sig/generated/fmt/parsers/template_parser.rbs +50 -0
  45. data/sig/generated/fmt/refinements/kernel_refinement.rbs +23 -0
  46. data/sig/generated/fmt/registries/native_registry.rbs +19 -0
  47. data/sig/generated/fmt/registries/rainbow_registry.rbs +11 -0
  48. data/sig/generated/fmt/registries/registry.rbs +69 -0
  49. data/sig/generated/fmt/renderer.rbs +70 -0
  50. data/sig/generated/fmt/sigils.rbs +30 -0
  51. data/sig/generated/fmt/token.rbs +77 -0
  52. data/sig/generated/fmt/tokenizer.rbs +51 -0
  53. data/sig/generated/fmt/version.rbs +5 -0
  54. data/sig/generated/fmt.rbs +41 -0
  55. metadata +126 -16
  56. data/lib/fmt/embed.rb +0 -19
  57. data/lib/fmt/filter.rb +0 -32
  58. data/lib/fmt/filters.rb +0 -76
  59. data/lib/fmt/formatter.rb +0 -50
  60. data/lib/fmt/scanners/base_scanner.rb +0 -41
  61. data/lib/fmt/scanners/embed_scanner.rb +0 -56
  62. data/lib/fmt/scanners/filter_scanner.rb +0 -31
  63. data/lib/fmt/scanners/key_scanner.rb +0 -15
  64. data/lib/fmt/scanners.rb +0 -3
  65. data/lib/fmt/transformer.rb +0 -63
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module Fmt
6
+ # Represents arguments for a method call
7
+ #
8
+ # Arguments are comprised of:
9
+ # 1. args: Array[Object]
10
+ # 2. kwargs: Hash[Symbol, Object]
11
+ #
12
+ class Arguments < Model
13
+ # Constructor
14
+ # @rbs ast: Node
15
+ def initialize(ast)
16
+ @args = []
17
+ @kwargs = {}
18
+ super
19
+ end
20
+
21
+ attr_reader :args # : Array[Object] -- positional arguments
22
+ attr_reader :kwargs # : Hash[Symbol, Object] -- keyword arguments
23
+
24
+ # Hash representation of the model (required for pattern matching)
25
+ # @rbs return: Hash[Symbol, Object]
26
+ def to_h
27
+ super.merge(
28
+ args: args,
29
+ kwargs: kwargs
30
+ )
31
+ end
32
+
33
+ # ..........................................................................
34
+ # @!group AST Processors
35
+ # ..........................................................................
36
+
37
+ # Processes an arguments AST node
38
+ # @rbs node: Node
39
+ # @rbs return: void
40
+ def on_arguments(node)
41
+ process_all node.children
42
+ end
43
+
44
+ # Processes a tokens AST node
45
+ # @rbs node: Node
46
+ # @rbs return: void
47
+ def on_tokens(node)
48
+ process_all node.children
49
+ end
50
+
51
+ # Processes a keyword AST node
52
+ # @rbs node: Node
53
+ # @rbs return: nil | true | false | Object
54
+ def on_kw(node)
55
+ case node.children.first
56
+ in "nil" then assign(nil)
57
+ in "true" then assign(true)
58
+ in "false" then assign(false)
59
+ end
60
+ end
61
+
62
+ # Processes a string AST node
63
+ # @rbs node: Node
64
+ # @rbs return: String
65
+ def on_tstring_content(node)
66
+ assign node.children.first
67
+ end
68
+
69
+ # Processes a symbol AST Node
70
+ # @rbs node: Node
71
+ # @rbs return: Symbol
72
+ def on_symbol(node)
73
+ assign node.children.first.to_sym
74
+ end
75
+
76
+ # Processes a symbol start AST Node
77
+ # @rbs node: Node
78
+ # @rbs return: void
79
+ def on_symbeg(node)
80
+ @next_ident_is_symbol = true
81
+ end
82
+
83
+ # Processes an identifier AST Node
84
+ # @rbs node: Node
85
+ # @rbs return: Symbol?
86
+ def on_ident(node)
87
+ assign node.children.first.to_sym if @next_ident_is_symbol
88
+ ensure
89
+ @next_ident_is_symbol = false
90
+ end
91
+
92
+ # Processes an integer AST node
93
+ # @rbs node: Node
94
+ # @rbs return: Integer
95
+ def on_int(node)
96
+ assign node.children.first.to_i
97
+ end
98
+
99
+ # Processes a float AST node
100
+ # @rbs node: Node
101
+ # @rbs return: Float
102
+ def on_float(node)
103
+ assign node.children.first.to_f
104
+ end
105
+
106
+ # Processes a rational AST node
107
+ # @rbs node: Node
108
+ # @rbs return: Rational
109
+ def on_rational(node)
110
+ assign Rational(node.children.first)
111
+ end
112
+
113
+ # Processes an imaginary (complex) AST node
114
+ # @rbs node: Node
115
+ # @rbs return: Complex
116
+ def on_imaginary(node)
117
+ assign Complex(0, node.children.first.to_f)
118
+ end
119
+
120
+ # ..........................................................................
121
+ # @!group Composite Data Types (Arrays, Hashes, Sets)
122
+ # ..........................................................................
123
+
124
+ # Processes a left bracket AST node
125
+ # @rbs node: Node
126
+ # @rbs return: Array
127
+ def on_lbracket(node)
128
+ assign([])
129
+ end
130
+
131
+ # Processes a left brace AST node
132
+ # @rbs node: Node
133
+ # @rbs return: Hash
134
+ def on_lbrace(node)
135
+ assign({})
136
+ end
137
+
138
+ # Process a label (hash key) AST node
139
+ # @rbs node: Node
140
+ # @rbs return: void
141
+ def on_label(node)
142
+ label = node.children.first
143
+ label = label.chop.to_sym if label.end_with?(":")
144
+ assign nil, label: label # assign placeholder
145
+ end
146
+
147
+ private
148
+
149
+ # Assigns a value to the receiver
150
+ # @rbs value: Object -- value to assign
151
+ # @rbs label: Symbol? -- label to use (if applicable)
152
+ # @rbs return: Object
153
+ def assign(value, label: nil)
154
+ receiver(label: label).tap do |rec|
155
+ case rec
156
+ in Array then rec << value
157
+ in Hash then rec[label || rec.keys.last] = value
158
+ end
159
+ end
160
+ end
161
+
162
+ # Receiver that the processed value will be assigned to
163
+ # @rbs label: Symbol? -- label to use (if applicable)
164
+ # @rbs return: Array | Hash
165
+ def receiver(label: nil)
166
+ obj = find_receiver(kwargs) if kwargs.any?
167
+ obj ||= find_receiver(args) || args
168
+
169
+ case [obj, label]
170
+ in [*, Symbol] then kwargs # <- 1) Array with label
171
+ else obj # <------------------- 2) Composite without label
172
+ end
173
+ end
174
+
175
+ # Finds the receiver that the processed value will be assigned to
176
+ # @rbs obj: Object
177
+ # @rbs return: Array? | Hash?
178
+ def find_receiver(obj)
179
+ case obj
180
+ in [] | {} then obj # <------------------------------------------ 1) empty array/hash
181
+ in [*, [*] | {**}] => array then find_receiver(array.last) # <--- 2) array with array/hash last entry
182
+ in {**} => hash then find_receiver(hash.values.last) || hash # <- 3) hash with values
183
+ else nil
184
+ end
185
+ end
186
+
187
+ # Indicates if the value is a composite type (Array or Hash)
188
+ # @rbs value: Object -- value to check
189
+ # @rbs return: bool
190
+ def composite?(value)
191
+ value.is_a?(Array) || value.is_a?(Hash)
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module Fmt
6
+ class Embed < Model
7
+ attr_reader :key # : Symbol -- key for embed
8
+ attr_reader :placeholder # : String -- placeholder for embed
9
+ attr_reader :template # : Template
10
+
11
+ # Hash representation of the model (required for pattern matching)
12
+ # @rbs return: Hash[Symbol, Object]
13
+ def to_h
14
+ super.merge placeholder: placeholder, template: template&.to_h
15
+ end
16
+
17
+ # ..........................................................................
18
+ # @!group AST Processors
19
+ # ..........................................................................
20
+
21
+ # Processes an embed AST node
22
+ # @rbs node: Node
23
+ # @rbs return: void
24
+ def on_embed(node)
25
+ process_all node.children
26
+ end
27
+
28
+ # Processes a key AST node
29
+ # @rbs node: Node
30
+ # @rbs return: void
31
+ def on_key(node)
32
+ @key = node.children.first
33
+ end
34
+
35
+ # Processes a placeholder AST node
36
+ # @rbs node: Node
37
+ # @rbs return: void
38
+ def on_placeholder(node)
39
+ @placeholder = node.children.first
40
+ end
41
+
42
+ # Processes a template AST node
43
+ # @rbs node: Node
44
+ def on_template(node)
45
+ @template = Template.new(node)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module Fmt
6
+ # Represents an uninvoked method call
7
+ #
8
+ # A Macro is comprised of:
9
+ # 1. name: Symbol
10
+ # 2. arguments: Arguments
11
+ #
12
+ class Macro < Model
13
+ attr_reader :name # : Symbol -- method name
14
+ attr_reader :arguments # : Arguments
15
+
16
+ # Constructor
17
+ # @rbs ast: Node
18
+ def initialize(ast)
19
+ @name = nil
20
+ @arguments = Arguments.new(Node.new(:arguments))
21
+ super
22
+ end
23
+
24
+ # Hash representation of the model (required for pattern matching)
25
+ # @rbs return: Hash[Symbol, Object]
26
+ def to_h
27
+ super.merge(
28
+ name: name,
29
+ arguments: arguments&.to_h
30
+ )
31
+ end
32
+
33
+ # ..........................................................................
34
+ # @!group AST Processors
35
+ # ..........................................................................
36
+
37
+ # Processes a macro AST node
38
+ # @rbs node: Node
39
+ # @rbs return: void
40
+ def on_macro(node)
41
+ process_all node.children
42
+ end
43
+
44
+ # Processes a procedure AST node
45
+ # @rbs node: Node
46
+ # @rbs return: void
47
+ def on_name(node)
48
+ @name = node.find(Symbol)
49
+ end
50
+
51
+ # Processes an arguments AST node
52
+ # @rbs node: Node
53
+ # @rbs return: void
54
+ def on_arguments(node)
55
+ @arguments = Arguments.new(node)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module Fmt
6
+ # Superclass for all models
7
+ # @note Models are constructed from AST nodes
8
+ class Model
9
+ # @see http://whitequark.github.io/ast/AST/Processor/Mixin.html
10
+ include AST::Processor::Mixin
11
+ include Matchable
12
+
13
+ # Constructor
14
+ # @rbs ast: Node
15
+ def initialize(ast)
16
+ @ast = ast
17
+ @urtext = ast.urtext
18
+ @source = ast.source
19
+ process ast
20
+ end
21
+
22
+ attr_reader :ast # : Node
23
+ attr_reader :urtext # : String -- original source code
24
+ attr_reader :source # : String -- parsed source code
25
+
26
+ alias_method :to_s, :source # : String -- alias for source
27
+
28
+ # Model inspection
29
+ # @rbs return: String
30
+ def inspect
31
+ "#<#{self.class.name} #{inspect_properties}>"
32
+ end
33
+
34
+ # Indicates if a given AST node is the same AST used to construct the model
35
+ # @rbs node: Node
36
+ # @rbs return: bool
37
+ def self?(node)
38
+ node == ast
39
+ end
40
+
41
+ # Hash representation of the model (required for pattern matching)
42
+ # @note Subclasses should override this method and call: super.merge(**)
43
+ # @rbs return: Hash[Symbol, Object]
44
+ def to_h
45
+ {}
46
+ end
47
+
48
+ private
49
+
50
+ # Hash of instance variables for inspection
51
+ # @rbs return: Hash[String, Object]
52
+ def inspectable_properties
53
+ instance_variables.each_with_object({}) do |name, memo|
54
+ value = instance_variable_get(name)
55
+ next if value in Node
56
+ memo[name[1..]] = value
57
+ end
58
+ end
59
+
60
+ # String of inspectable properties for inspection
61
+ # @rbs return: String
62
+ def inspect_properties
63
+ inspectable_properties.map { "#{_1}=#{_2.inspect}" }.join " "
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module Fmt
6
+ # Represents a series of Macros
7
+ #
8
+ # A Pipeline is comprised of:
9
+ # 1. macros: Array[Macro]
10
+ #
11
+ # @note Pipelines are processed in sequence (left to right)
12
+ #
13
+ class Pipeline < Model
14
+ # Constructor
15
+ # @rbs ast: Node
16
+ def initialize(ast)
17
+ @macros = []
18
+ super
19
+ end
20
+
21
+ attr_reader :macros # : Array[Node]
22
+
23
+ # Hash representation of the model (required for pattern matching)
24
+ # @rbs return: Hash[Symbol, Object]
25
+ def to_h
26
+ super.merge macros: macros.map(&:to_h)
27
+ end
28
+
29
+ # ..........................................................................
30
+ # @!group AST Processors
31
+ # ..........................................................................
32
+
33
+ # Processes a pipeline AST node
34
+ # @rbs node: Node
35
+ # @rbs return: void
36
+ def on_pipeline(node)
37
+ process_all node.children
38
+ end
39
+
40
+ # Processes a macro AST node
41
+ # @rbs node: Node
42
+ # @rbs return: void
43
+ def on_macro(node)
44
+ @macros << Macro.new(node)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module Fmt
6
+ # Represents a formattable string
7
+ #
8
+ # A Template is comprised of:
9
+ # 1. embeds: Array[Template] -- embedded templates
10
+ # 2. pipelines :: Array[Pipeline] -- sets of Macros
11
+ #
12
+ # @note Embeds are processed from inner to outer
13
+ #
14
+ class Template < Model
15
+ # Constructor
16
+ # @rbs ast: Node
17
+ def initialize(ast)
18
+ @embeds = []
19
+ @pipelines = []
20
+ super
21
+ end
22
+
23
+ attr_reader :embeds # : Array[Template]
24
+ attr_reader :pipelines # : Array[Pipeline]
25
+
26
+ # @rbs return: Hash[Symbol, Object]
27
+ def to_h
28
+ super.merge embeds: embeds.map(&:to_h), pipelines: pipelines.map(&:to_h)
29
+ end
30
+
31
+ # ..........................................................................
32
+ # @!group AST Processors
33
+ # ..........................................................................
34
+
35
+ def on_template(node)
36
+ process_all node.children
37
+ end
38
+
39
+ def on_embeds(node)
40
+ process_all node.children
41
+ end
42
+
43
+ def on_embed(node)
44
+ embeds << Embed.new(node)
45
+ end
46
+
47
+ def on_pipelines(node)
48
+ process_all node.children
49
+ end
50
+
51
+ def on_pipeline(node)
52
+ pipelines << Pipeline.new(node)
53
+ end
54
+ end
55
+ end
data/lib/fmt/node.rb ADDED
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module Fmt
6
+ # Extends behavior of AST::Node
7
+ class Node < AST::Node
8
+ extend Forwardable
9
+
10
+ class << self
11
+ # Finds all Node child nodes
12
+ # @rbs node: Node -- node to search
13
+ # @rbs return: Array[Node]
14
+ def node_children(node)
15
+ list = []
16
+ node.children.each do |child|
17
+ list << child if child.is_a?(Node)
18
+ end
19
+ list
20
+ end
21
+
22
+ # Recursively finds all Nodes in the tree
23
+ # @rbs node: Node -- node to search
24
+ # @rbs return: Array[Node]
25
+ def node_descendants(node)
26
+ list = []
27
+ node.children.each do |child|
28
+ list << child if child.is_a?(Node)
29
+ list.concat node_children(child)
30
+ end
31
+ list
32
+ end
33
+ end
34
+
35
+ # Constructor
36
+ # @rbs type: Symbol
37
+ # @rbs children: Array[Node]
38
+ # @rbs properties: Hash[Symbol, Object]
39
+ def initialize(type, children = [], properties = {urtext: "", source: ""})
40
+ @properties = properties
41
+ define_properties properties
42
+ super
43
+ end
44
+
45
+ attr_reader :properties # : Hash[Symbol, Object]
46
+
47
+ # Returns the child at the specified index
48
+ # @rbs index: Integer -- index of child node
49
+ # @rbs return: Node? | Object?
50
+ def_delegator :children, :[]
51
+
52
+ # Indicates if no children exist
53
+ # @rbs return: bool
54
+ def_delegator :children, :empty?
55
+
56
+ # Returns the number of children
57
+ # @rbs return: Integer
58
+ def_delegator :children, :size
59
+
60
+ # Recursively searches the tree for a descendant node
61
+ # @rbs types: Array[Object] -- node types to find
62
+ # @rbs return: Node?
63
+ def dig(*types)
64
+ node = find(types.shift) if types.any?
65
+ node = node.find(types.shift) while node && types.any?
66
+ node
67
+ end
68
+
69
+ # Finds the first child node of the specified type
70
+ # @rbs type: Object -- node type to find
71
+ # @rbs return: Node?
72
+ def find(type)
73
+ case type
74
+ in Symbol then children.find { _1 in [^type, *] }
75
+ in Class then children.find { _1 in ^type }
76
+ end
77
+ end
78
+
79
+ # Flattens Node descendants into a one dimensional array
80
+ # @rbs return: Array[Node]
81
+ def flatten
82
+ node_descendants.prepend self
83
+ end
84
+
85
+ # Finds all child nodes of the specified type
86
+ # @rbs type: Object -- node type to select
87
+ # @rbs return: Node?
88
+ def select(type)
89
+ [].concat case type
90
+ in Symbol then children.select { _1 in [^type, *] }
91
+ in Class then children.select { _1 in ^type }
92
+ else []
93
+ end
94
+ end
95
+
96
+ # String representation of the node (AST)
97
+ # @rbs squish: bool -- remove extra whitespace
98
+ # @rbs return: String
99
+ def to_s(squish: false)
100
+ value = super()
101
+ return value unless squish
102
+ value.gsub(/\s{2,}/, " ")
103
+ end
104
+
105
+ private
106
+
107
+ # Finds all Node child nodes
108
+ # @rbs return: Array[Node]
109
+ def node_children
110
+ self.class.node_children self
111
+ end
112
+
113
+ # Recursively finds all Node nodes in the tree
114
+ # @rbs return: Array[Node]
115
+ def node_descendants
116
+ self.class.node_descendants self
117
+ end
118
+
119
+ # Defines accessor methods for properties on the receiver
120
+ # @rbs properties: Hash[Symbol, Object] -- exposed as instance methods
121
+ def define_properties(properties)
122
+ properties.each do |key, val|
123
+ next if singleton_class.public_instance_methods(false).include?(key)
124
+ singleton_class.define_method(key) { val }
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module Fmt
6
+ # Parses arguments from a string and builds an AST (Abstract Syntax Tree)
7
+ class ArgumentsParser < Parser
8
+ # Constructor
9
+ # @rbs tokens: Array[Token] -- wrapped ripper tokens
10
+ def initialize(tokens = [])
11
+ @tokens = tokens
12
+ end
13
+
14
+ attr_reader :tokens # : Array[Token] -- wrapped ripper tokens
15
+
16
+ # Parses the urtext (original source code)
17
+ # @rbs return: Node -- AST (Abstract Syntax Tree)
18
+ def parse
19
+ cache(tokens.to_s) { super }
20
+ end
21
+
22
+ protected
23
+
24
+ # Extracts components for building the AST (Abstract Syntax Tree)
25
+ # @rbs return: Hash[Symbol, Object] -- extracted components
26
+ def extract
27
+ {tokens: tokens}
28
+ end
29
+
30
+ # Transforms extracted components into an AST (Abstract Syntax Tree)
31
+ # @rbs tokens: Array[Token] -- extracted tokens
32
+ # @rbs return: Node -- AST (Abstract Syntax Tree)
33
+ def transform(tokens:)
34
+ return Node.new(:arguments) if tokens.none?
35
+
36
+ source = tokens.map(&:value).join
37
+ tokens = tokens.map { |t| Node.new(t.type, [t.value], urtext: t.value, source: t.value) }
38
+ tokens = Node.new(:tokens, tokens, urtext: source, source: source)
39
+
40
+ Node.new :arguments, [tokens], urtext: source, source: source
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module Fmt
6
+ # Parses embeds from a string and builds an AST (Abstract Syntax Tree)
7
+ class EmbedParser < Parser
8
+ # Constructor
9
+ # @rbs urtext: String -- original source code
10
+ # @rbs key: Symbol -- key for embed
11
+ # @rbs placeholder: String -- placeholder for embed
12
+ def initialize(urtext = "", key:, placeholder:)
13
+ @urtext = urtext.to_s
14
+ @key = key
15
+ @placeholder = placeholder
16
+ end
17
+
18
+ attr_reader :urtext # : String -- original source code
19
+ attr_reader :key # : Symbol -- key for embed
20
+ attr_reader :placeholder # : String -- placeholder for embed
21
+
22
+ # Parses the urtext (original source code)
23
+ # @rbs return: Node -- AST (Abstract Syntax Tree)
24
+ def parse
25
+ cache(urtext) { super }
26
+ end
27
+
28
+ protected
29
+
30
+ # Extracts components for building the AST (Abstract Syntax Tree)
31
+ # @rbs return: Hash[Symbol, Object] -- extracted components
32
+ def extract
33
+ {}
34
+ end
35
+
36
+ # Transforms extracted components into an AST (Abstract Syntax Tree)
37
+ # @rbs return: Node -- AST (Abstract Syntax Tree)
38
+ def transform(**)
39
+ key = Node.new(:key, [self.key])
40
+ placeholder = Node.new(:placeholder, [self.placeholder])
41
+ template = TemplateParser.new(template_urtext).parse
42
+ children = [key, placeholder, template].reject(&:empty?)
43
+ Node.new(:embed, children, urtext: urtext, source: urtext)
44
+ end
45
+
46
+ private
47
+
48
+ # Returns the template urtext
49
+ # @rbs return: String
50
+ def template_urtext
51
+ urtext.delete_prefix(Sigils::EMBED_PREFIX).delete_suffix(Sigils::EMBED_SUFFIX)
52
+ end
53
+ end
54
+ end