fmt 0.1.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +167 -93
  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 +50 -12
  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 -18
  56. data/lib/fmt/embed.rb +0 -19
  57. data/lib/fmt/filter.rb +0 -32
  58. data/lib/fmt/filter_groups/filter_group.rb +0 -56
  59. data/lib/fmt/filter_groups/rainbow_filter_group.rb +0 -27
  60. data/lib/fmt/filter_groups/string_filter_group.rb +0 -28
  61. data/lib/fmt/formatter.rb +0 -60
  62. data/lib/fmt/scanners/base_scanner.rb +0 -41
  63. data/lib/fmt/scanners/embed_scanner.rb +0 -56
  64. data/lib/fmt/scanners/filter_scanner.rb +0 -31
  65. data/lib/fmt/scanners/key_scanner.rb +0 -15
  66. data/lib/fmt/scanners.rb +0 -3
  67. data/lib/fmt/transformer.rb +0 -57
@@ -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