fmt 0.1.3 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +179 -86
- data/lib/fmt/boot.rb +50 -0
- data/lib/fmt/lru_cache.rb +181 -0
- data/lib/fmt/mixins/matchable.rb +26 -0
- data/lib/fmt/models/arguments.rb +194 -0
- data/lib/fmt/models/embed.rb +48 -0
- data/lib/fmt/models/macro.rb +58 -0
- data/lib/fmt/models/model.rb +66 -0
- data/lib/fmt/models/pipeline.rb +47 -0
- data/lib/fmt/models/template.rb +55 -0
- data/lib/fmt/node.rb +128 -0
- data/lib/fmt/parsers/arguments_parser.rb +43 -0
- data/lib/fmt/parsers/embed_parser.rb +54 -0
- data/lib/fmt/parsers/macro_parser.rb +113 -0
- data/lib/fmt/parsers/parser.rb +56 -0
- data/lib/fmt/parsers/pipeline_parser.rb +41 -0
- data/lib/fmt/parsers/template_parser.rb +125 -0
- data/lib/fmt/refinements/kernel_refinement.rb +33 -0
- data/lib/fmt/registries/native_registry.rb +66 -0
- data/lib/fmt/registries/rainbow_registry.rb +36 -0
- data/lib/fmt/registries/registry.rb +127 -0
- data/lib/fmt/renderer.rb +132 -0
- data/lib/fmt/sigils.rb +23 -0
- data/lib/fmt/token.rb +126 -0
- data/lib/fmt/tokenizer.rb +96 -0
- data/lib/fmt/version.rb +3 -1
- data/lib/fmt.rb +50 -12
- data/sig/generated/fmt/boot.rbs +2 -0
- data/sig/generated/fmt/lru_cache.rbs +122 -0
- data/sig/generated/fmt/mixins/matchable.rbs +18 -0
- data/sig/generated/fmt/models/arguments.rbs +115 -0
- data/sig/generated/fmt/models/embed.rbs +34 -0
- data/sig/generated/fmt/models/macro.rbs +37 -0
- data/sig/generated/fmt/models/model.rbs +45 -0
- data/sig/generated/fmt/models/pipeline.rbs +31 -0
- data/sig/generated/fmt/models/template.rbs +33 -0
- data/sig/generated/fmt/node.rbs +64 -0
- data/sig/generated/fmt/parsers/arguments_parser.rbs +25 -0
- data/sig/generated/fmt/parsers/embed_parser.rbs +36 -0
- data/sig/generated/fmt/parsers/macro_parser.rbs +60 -0
- data/sig/generated/fmt/parsers/parser.rbs +44 -0
- data/sig/generated/fmt/parsers/pipeline_parser.rbs +25 -0
- data/sig/generated/fmt/parsers/template_parser.rbs +50 -0
- data/sig/generated/fmt/refinements/kernel_refinement.rbs +23 -0
- data/sig/generated/fmt/registries/native_registry.rbs +19 -0
- data/sig/generated/fmt/registries/rainbow_registry.rbs +11 -0
- data/sig/generated/fmt/registries/registry.rbs +69 -0
- data/sig/generated/fmt/renderer.rbs +70 -0
- data/sig/generated/fmt/sigils.rbs +30 -0
- data/sig/generated/fmt/token.rbs +77 -0
- data/sig/generated/fmt/tokenizer.rbs +51 -0
- data/sig/generated/fmt/version.rbs +5 -0
- data/sig/generated/fmt.rbs +41 -0
- metadata +126 -18
- data/lib/fmt/embed.rb +0 -19
- data/lib/fmt/filter.rb +0 -32
- data/lib/fmt/filter_groups/filter_group.rb +0 -56
- data/lib/fmt/filter_groups/rainbow_filter_group.rb +0 -27
- data/lib/fmt/filter_groups/string_filter_group.rb +0 -28
- data/lib/fmt/formatter.rb +0 -60
- data/lib/fmt/scanners/base_scanner.rb +0 -41
- data/lib/fmt/scanners/embed_scanner.rb +0 -56
- data/lib/fmt/scanners/filter_scanner.rb +0 -31
- data/lib/fmt/scanners/key_scanner.rb +0 -15
- data/lib/fmt/scanners.rb +0 -3
- 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
|