fmt 0.1.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +167 -93
- 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 +38 -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
|