herb 0.1.0-x86_64-linux-gnu

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 (110) hide show
  1. checksums.yaml +7 -0
  2. data/License.txt +21 -0
  3. data/Makefile +121 -0
  4. data/README.md +166 -0
  5. data/Rakefile +184 -0
  6. data/exe/herb +5 -0
  7. data/ext/herb/error_helpers.c +302 -0
  8. data/ext/herb/error_helpers.h +15 -0
  9. data/ext/herb/extconf.rb +75 -0
  10. data/ext/herb/extension.c +110 -0
  11. data/ext/herb/extension.h +6 -0
  12. data/ext/herb/extension_helpers.c +117 -0
  13. data/ext/herb/extension_helpers.h +24 -0
  14. data/ext/herb/nodes.c +936 -0
  15. data/ext/herb/nodes.h +12 -0
  16. data/herb.gemspec +49 -0
  17. data/lib/herb/3.0/herb.so +0 -0
  18. data/lib/herb/3.1/herb.so +0 -0
  19. data/lib/herb/3.2/herb.so +0 -0
  20. data/lib/herb/3.3/herb.so +0 -0
  21. data/lib/herb/3.4/herb.so +0 -0
  22. data/lib/herb/ast/node.rb +61 -0
  23. data/lib/herb/ast/nodes.rb +1542 -0
  24. data/lib/herb/ast.rb +6 -0
  25. data/lib/herb/cli.rb +164 -0
  26. data/lib/herb/errors.rb +352 -0
  27. data/lib/herb/lex_result.rb +20 -0
  28. data/lib/herb/libherb/array.rb +48 -0
  29. data/lib/herb/libherb/ast_node.rb +47 -0
  30. data/lib/herb/libherb/buffer.rb +53 -0
  31. data/lib/herb/libherb/extract_result.rb +17 -0
  32. data/lib/herb/libherb/lex_result.rb +29 -0
  33. data/lib/herb/libherb/libherb.rb +49 -0
  34. data/lib/herb/libherb/parse_result.rb +17 -0
  35. data/lib/herb/libherb/token.rb +43 -0
  36. data/lib/herb/libherb.rb +32 -0
  37. data/lib/herb/location.rb +42 -0
  38. data/lib/herb/parse_result.rb +26 -0
  39. data/lib/herb/position.rb +36 -0
  40. data/lib/herb/project.rb +361 -0
  41. data/lib/herb/range.rb +40 -0
  42. data/lib/herb/result.rb +21 -0
  43. data/lib/herb/token.rb +43 -0
  44. data/lib/herb/token_list.rb +11 -0
  45. data/lib/herb/version.rb +5 -0
  46. data/lib/herb.rb +32 -0
  47. data/src/analyze.c +989 -0
  48. data/src/analyze_helpers.c +241 -0
  49. data/src/analyzed_ruby.c +35 -0
  50. data/src/array.c +137 -0
  51. data/src/ast_node.c +81 -0
  52. data/src/ast_nodes.c +866 -0
  53. data/src/ast_pretty_print.c +588 -0
  54. data/src/buffer.c +199 -0
  55. data/src/errors.c +740 -0
  56. data/src/extract.c +110 -0
  57. data/src/herb.c +103 -0
  58. data/src/html_util.c +143 -0
  59. data/src/include/analyze.h +36 -0
  60. data/src/include/analyze_helpers.h +43 -0
  61. data/src/include/analyzed_ruby.h +33 -0
  62. data/src/include/array.h +33 -0
  63. data/src/include/ast_node.h +35 -0
  64. data/src/include/ast_nodes.h +303 -0
  65. data/src/include/ast_pretty_print.h +17 -0
  66. data/src/include/buffer.h +36 -0
  67. data/src/include/errors.h +125 -0
  68. data/src/include/extract.h +20 -0
  69. data/src/include/herb.h +32 -0
  70. data/src/include/html_util.h +13 -0
  71. data/src/include/io.h +9 -0
  72. data/src/include/json.h +28 -0
  73. data/src/include/lexer.h +13 -0
  74. data/src/include/lexer_peek_helpers.h +23 -0
  75. data/src/include/lexer_struct.h +32 -0
  76. data/src/include/location.h +25 -0
  77. data/src/include/macros.h +10 -0
  78. data/src/include/memory.h +12 -0
  79. data/src/include/parser.h +22 -0
  80. data/src/include/parser_helpers.h +33 -0
  81. data/src/include/position.h +22 -0
  82. data/src/include/pretty_print.h +53 -0
  83. data/src/include/prism_helpers.h +18 -0
  84. data/src/include/range.h +23 -0
  85. data/src/include/ruby_parser.h +6 -0
  86. data/src/include/token.h +25 -0
  87. data/src/include/token_matchers.h +21 -0
  88. data/src/include/token_struct.h +51 -0
  89. data/src/include/util.h +25 -0
  90. data/src/include/version.h +6 -0
  91. data/src/include/visitor.h +11 -0
  92. data/src/io.c +30 -0
  93. data/src/json.c +205 -0
  94. data/src/lexer.c +284 -0
  95. data/src/lexer_peek_helpers.c +59 -0
  96. data/src/location.c +41 -0
  97. data/src/main.c +162 -0
  98. data/src/memory.c +53 -0
  99. data/src/parser.c +704 -0
  100. data/src/parser_helpers.c +161 -0
  101. data/src/position.c +33 -0
  102. data/src/pretty_print.c +242 -0
  103. data/src/prism_helpers.c +50 -0
  104. data/src/range.c +38 -0
  105. data/src/ruby_parser.c +47 -0
  106. data/src/token.c +194 -0
  107. data/src/token_matchers.c +32 -0
  108. data/src/util.c +128 -0
  109. data/src/visitor.c +321 -0
  110. metadata +159 -0
data/lib/herb/ast.rb ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Herb
4
+ module AST
5
+ end
6
+ end
data/lib/herb/cli.rb ADDED
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+ require "lz_string"
5
+
6
+ class Herb::CLI
7
+ attr_accessor :json, :silent
8
+
9
+ def initialize(args)
10
+ @args = args
11
+ @command = args[0]
12
+ @file = args[1]
13
+ end
14
+
15
+ def call
16
+ options
17
+
18
+ if silent
19
+ if result.failed?
20
+ puts "Failed"
21
+ else
22
+ puts "Success"
23
+ end
24
+ elsif json
25
+ puts result.value.to_json
26
+ else
27
+ puts result.value.inspect
28
+ end
29
+ end
30
+
31
+ def directory
32
+ unless @file
33
+ puts "No directory provided."
34
+ puts
35
+ puts "Usage:"
36
+ puts " bundle exec herb #{@command} [directory] [options]"
37
+ puts
38
+ puts "Tip: Use `.` for the current directory"
39
+ puts " bundle exec herb #{@command} . [options]"
40
+
41
+ exit(1)
42
+ end
43
+
44
+ unless File.exist?(@file)
45
+ puts "Not a directory: '#{@file}'."
46
+ puts
47
+ end
48
+
49
+ unless File.directory?(@file)
50
+ puts "Not a directory: '#{@file}'."
51
+ puts
52
+ end
53
+
54
+ @file
55
+ end
56
+
57
+ def file_content
58
+ if @file && File.exist?(@file)
59
+ File.read(@file)
60
+ elsif @file
61
+ puts "File doesn't exist: #{@file}"
62
+ exit(1)
63
+ else
64
+ puts "No file provided."
65
+ puts
66
+ puts "Usage:"
67
+ puts " bundle exec herb #{@command} [file] [options]"
68
+ exit(1)
69
+ end
70
+ end
71
+
72
+ def help(exit_code = 0)
73
+ message = <<~HELP
74
+ ▗▖ ▗▖▗▄▄▄▖▗▄▄▖ ▗▄▄▖
75
+ ▐▌ ▐▌▐▌ ▐▌ ▐▌▐▌ ▐▌
76
+ ▐▛▀▜▌▐▛▀▀▘▐▛▀▚▖▐▛▀▚▖
77
+ ▐▌ ▐▌▐▙▄▄▖▐▌ ▐▌▐▙▄▞▘
78
+
79
+ Herb 🌿 Powerful and seamless HTML-aware ERB parsing.
80
+
81
+ Usage:
82
+ bundle exec herb [command] [options]
83
+
84
+ Commands:
85
+ bundle exec herb lex [file] Lex a file.
86
+ bundle exec herb parse [file] Parse a file.
87
+ bundle exec herb analyze [path] Analyze a project by passing a directory to the root of the project
88
+ bundle exec herb ruby [file] Extract Ruby from a file.
89
+ bundle exec herb html [file] Extract HTML from a file.
90
+ bundle exec herb prism [file] Extract Ruby from a file and parse the Ruby source with Prism.
91
+ bundle exec herb playground [file] Open the content of the source file in the playground
92
+ bundle exec herb version Prints the versions of the Herb gem and the libherb library.
93
+
94
+ Options:
95
+ #{option_parser.to_s.strip.gsub(/^ /, " ")}
96
+
97
+ HELP
98
+
99
+ puts message
100
+
101
+ exit(exit_code)
102
+ end
103
+
104
+ def result
105
+ @result ||= case @command
106
+ when "analyze"
107
+ Herb::Project.new(directory).parse!
108
+ exit(0)
109
+ when "parse"
110
+ Herb.parse(file_content)
111
+ when "lex"
112
+ Herb.lex(file_content)
113
+ when "ruby"
114
+ puts Herb.extract_ruby(file_content)
115
+ exit(0)
116
+ when "html"
117
+ puts Herb.extract_html(file_content)
118
+ exit(0)
119
+ when "playground"
120
+ if Dir.pwd.include?("/herb")
121
+ system(%(npx concurrently "nx dev playground" "sleep 1 && open http://localhost:5173##{LZString::UriSafe.compress(file_content)}"))
122
+ exit(0)
123
+ else
124
+ puts "This command can currently only be run within the herb repo itself"
125
+ exit(1)
126
+ end
127
+ when "help", "-h", "--help"
128
+ help
129
+ when "version", "-v", "--version"
130
+ puts Herb.version
131
+ exit(0)
132
+ when String
133
+ puts "Unknown command: '#{@command}'"
134
+ puts
135
+
136
+ help(1)
137
+ else
138
+ help(1)
139
+ end
140
+ end
141
+
142
+ def option_parser
143
+ @option_parser ||= OptionParser.new do |parser|
144
+ parser.banner = ""
145
+
146
+ parser.on_tail("-h", "--help", "Show this message") do
147
+ help
148
+ exit(0)
149
+ end
150
+
151
+ parser.on("-j", "--json", "Return result in the JSON format") do
152
+ self.json = true
153
+ end
154
+
155
+ parser.on("-s", "--silent", "Log no result to stdout") do
156
+ self.silent = true
157
+ end
158
+ end
159
+ end
160
+
161
+ def options
162
+ option_parser.parse!(@args)
163
+ end
164
+ end
@@ -0,0 +1,352 @@
1
+ # frozen_string_literal: true
2
+
3
+ # NOTE: This file is generated by the templates/template.rb script and should not be
4
+ # modified manually. See /Users/marcoroth/Development/herb-release/templates/lib/herb/errors.rb.erb
5
+
6
+ module Herb
7
+ module Errors
8
+ class Error
9
+ attr_reader :type
10
+ attr_reader :location
11
+ attr_reader :message
12
+
13
+ def initialize(type, location, message)
14
+ @type = type
15
+ @location = location
16
+ @message = message
17
+ end
18
+
19
+ def to_hash
20
+ {
21
+ type: type,
22
+ location: location&.to_hash,
23
+ message: message,
24
+ }
25
+ end
26
+
27
+ def error_name
28
+ self.class.name.split("::").last
29
+ end
30
+
31
+ def to_json(*args)
32
+ to_hash.to_json(*args)
33
+ end
34
+ end
35
+
36
+ class UnexpectedError < Error
37
+ attr_reader :description
38
+ attr_reader :expected
39
+ attr_reader :found
40
+
41
+ def initialize(type, location, message, description, expected, found)
42
+ super(type, location, message)
43
+
44
+ @description = description
45
+ @expected = expected
46
+ @found = found
47
+ end
48
+
49
+ def inspect
50
+ tree_inspect.rstrip.gsub(/\s+$/, "")
51
+ end
52
+
53
+ def to_hash
54
+ super.merge({
55
+ description: description,
56
+ expected: expected,
57
+ found: found,
58
+ })
59
+ end
60
+
61
+ def tree_inspect(indent = 0)
62
+ output = +""
63
+
64
+ output += %(@ #{error_name} #{location.tree_inspect}\n)
65
+ output += %(├── message: #{message.inspect}\n)
66
+ output += %(├── description: #{description.inspect}\n)
67
+ output += %(├── expected: #{expected.inspect}\n)
68
+ output += %(└── found: #{found.inspect}\n)
69
+ output += %(\n)
70
+
71
+ output.gsub(/^/, " " * indent)
72
+ end
73
+ end
74
+
75
+ class UnexpectedTokenError < Error
76
+ attr_reader :expected_type
77
+ attr_reader :found
78
+
79
+ def initialize(type, location, message, expected_type, found)
80
+ super(type, location, message)
81
+
82
+ @expected_type = expected_type
83
+ @found = found
84
+ end
85
+
86
+ def inspect
87
+ tree_inspect.rstrip.gsub(/\s+$/, "")
88
+ end
89
+
90
+ def to_hash
91
+ super.merge({
92
+ expected_type: expected_type,
93
+ found: found,
94
+ })
95
+ end
96
+
97
+ def tree_inspect(indent = 0)
98
+ output = +""
99
+
100
+ output += %(@ #{error_name} #{location.tree_inspect}\n)
101
+ output += %(├── message: #{message.inspect}\n)
102
+ output += %(├── expected_type: #{expected_type.inspect}\n)
103
+ output += %(└── found: #{found ? found.tree_inspect : "∅"}\n)
104
+ output += %(\n)
105
+
106
+ output.gsub(/^/, " " * indent)
107
+ end
108
+ end
109
+
110
+ class MissingOpeningTagError < Error
111
+ attr_reader :closing_tag
112
+
113
+ def initialize(type, location, message, closing_tag)
114
+ super(type, location, message)
115
+
116
+ @closing_tag = closing_tag
117
+ end
118
+
119
+ def inspect
120
+ tree_inspect.rstrip.gsub(/\s+$/, "")
121
+ end
122
+
123
+ def to_hash
124
+ super.merge({
125
+ closing_tag: closing_tag,
126
+ })
127
+ end
128
+
129
+ def tree_inspect(indent = 0)
130
+ output = +""
131
+
132
+ output += %(@ #{error_name} #{location.tree_inspect}\n)
133
+ output += %(├── message: #{message.inspect}\n)
134
+ output += %(└── closing_tag: #{closing_tag ? closing_tag.tree_inspect : "∅"}\n)
135
+ output += %(\n)
136
+
137
+ output.gsub(/^/, " " * indent)
138
+ end
139
+ end
140
+
141
+ class MissingClosingTagError < Error
142
+ attr_reader :opening_tag
143
+
144
+ def initialize(type, location, message, opening_tag)
145
+ super(type, location, message)
146
+
147
+ @opening_tag = opening_tag
148
+ end
149
+
150
+ def inspect
151
+ tree_inspect.rstrip.gsub(/\s+$/, "")
152
+ end
153
+
154
+ def to_hash
155
+ super.merge({
156
+ opening_tag: opening_tag,
157
+ })
158
+ end
159
+
160
+ def tree_inspect(indent = 0)
161
+ output = +""
162
+
163
+ output += %(@ #{error_name} #{location.tree_inspect}\n)
164
+ output += %(├── message: #{message.inspect}\n)
165
+ output += %(└── opening_tag: #{opening_tag ? opening_tag.tree_inspect : "∅"}\n)
166
+ output += %(\n)
167
+
168
+ output.gsub(/^/, " " * indent)
169
+ end
170
+ end
171
+
172
+ class TagNamesMismatchError < Error
173
+ attr_reader :opening_tag
174
+ attr_reader :closing_tag
175
+
176
+ def initialize(type, location, message, opening_tag, closing_tag)
177
+ super(type, location, message)
178
+
179
+ @opening_tag = opening_tag
180
+ @closing_tag = closing_tag
181
+ end
182
+
183
+ def inspect
184
+ tree_inspect.rstrip.gsub(/\s+$/, "")
185
+ end
186
+
187
+ def to_hash
188
+ super.merge({
189
+ opening_tag: opening_tag,
190
+ closing_tag: closing_tag,
191
+ })
192
+ end
193
+
194
+ def tree_inspect(indent = 0)
195
+ output = +""
196
+
197
+ output += %(@ #{error_name} #{location.tree_inspect}\n)
198
+ output += %(├── message: #{message.inspect}\n)
199
+ output += %(├── opening_tag: #{opening_tag ? opening_tag.tree_inspect : "∅"}\n)
200
+ output += %(└── closing_tag: #{closing_tag ? closing_tag.tree_inspect : "∅"}\n)
201
+ output += %(\n)
202
+
203
+ output.gsub(/^/, " " * indent)
204
+ end
205
+ end
206
+
207
+ class QuotesMismatchError < Error
208
+ attr_reader :opening_quote
209
+ attr_reader :closing_quote
210
+
211
+ def initialize(type, location, message, opening_quote, closing_quote)
212
+ super(type, location, message)
213
+
214
+ @opening_quote = opening_quote
215
+ @closing_quote = closing_quote
216
+ end
217
+
218
+ def inspect
219
+ tree_inspect.rstrip.gsub(/\s+$/, "")
220
+ end
221
+
222
+ def to_hash
223
+ super.merge({
224
+ opening_quote: opening_quote,
225
+ closing_quote: closing_quote,
226
+ })
227
+ end
228
+
229
+ def tree_inspect(indent = 0)
230
+ output = +""
231
+
232
+ output += %(@ #{error_name} #{location.tree_inspect}\n)
233
+ output += %(├── message: #{message.inspect}\n)
234
+ output += %(├── opening_quote: #{opening_quote ? opening_quote.tree_inspect : "∅"}\n)
235
+ output += %(└── closing_quote: #{closing_quote ? closing_quote.tree_inspect : "∅"}\n)
236
+ output += %(\n)
237
+
238
+ output.gsub(/^/, " " * indent)
239
+ end
240
+ end
241
+
242
+ class VoidElementClosingTagError < Error
243
+ attr_reader :tag_name
244
+ attr_reader :expected
245
+ attr_reader :found
246
+
247
+ def initialize(type, location, message, tag_name, expected, found)
248
+ super(type, location, message)
249
+
250
+ @tag_name = tag_name
251
+ @expected = expected
252
+ @found = found
253
+ end
254
+
255
+ def inspect
256
+ tree_inspect.rstrip.gsub(/\s+$/, "")
257
+ end
258
+
259
+ def to_hash
260
+ super.merge({
261
+ tag_name: tag_name,
262
+ expected: expected,
263
+ found: found,
264
+ })
265
+ end
266
+
267
+ def tree_inspect(indent = 0)
268
+ output = +""
269
+
270
+ output += %(@ #{error_name} #{location.tree_inspect}\n)
271
+ output += %(├── message: #{message.inspect}\n)
272
+ output += %(├── tag_name: #{tag_name ? tag_name.tree_inspect : "∅"}\n)
273
+ output += %(├── expected: #{expected.inspect}\n)
274
+ output += %(└── found: #{found.inspect}\n)
275
+ output += %(\n)
276
+
277
+ output.gsub(/^/, " " * indent)
278
+ end
279
+ end
280
+
281
+ class UnclosedElementError < Error
282
+ attr_reader :opening_tag
283
+
284
+ def initialize(type, location, message, opening_tag)
285
+ super(type, location, message)
286
+
287
+ @opening_tag = opening_tag
288
+ end
289
+
290
+ def inspect
291
+ tree_inspect.rstrip.gsub(/\s+$/, "")
292
+ end
293
+
294
+ def to_hash
295
+ super.merge({
296
+ opening_tag: opening_tag,
297
+ })
298
+ end
299
+
300
+ def tree_inspect(indent = 0)
301
+ output = +""
302
+
303
+ output += %(@ #{error_name} #{location.tree_inspect}\n)
304
+ output += %(├── message: #{message.inspect}\n)
305
+ output += %(└── opening_tag: #{opening_tag ? opening_tag.tree_inspect : "∅"}\n)
306
+ output += %(\n)
307
+
308
+ output.gsub(/^/, " " * indent)
309
+ end
310
+ end
311
+
312
+ class RubyParseError < Error
313
+ attr_reader :error_message
314
+ attr_reader :diagnostic_id
315
+ attr_reader :level
316
+
317
+ def initialize(type, location, message, error_message, diagnostic_id, level)
318
+ super(type, location, message)
319
+
320
+ @error_message = error_message
321
+ @diagnostic_id = diagnostic_id
322
+ @level = level
323
+ end
324
+
325
+ def inspect
326
+ tree_inspect.rstrip.gsub(/\s+$/, "")
327
+ end
328
+
329
+ def to_hash
330
+ super.merge({
331
+ error_message: error_message,
332
+ diagnostic_id: diagnostic_id,
333
+ level: level,
334
+ })
335
+ end
336
+
337
+ def tree_inspect(indent = 0)
338
+ output = +""
339
+
340
+ output += %(@ #{error_name} #{location.tree_inspect}\n)
341
+ output += %(├── message: #{message.inspect}\n)
342
+ output += %(├── error_message: #{error_message.inspect}\n)
343
+ output += %(├── diagnostic_id: #{diagnostic_id.inspect}\n)
344
+ output += %(└── level: #{level.inspect}\n)
345
+ output += %(\n)
346
+
347
+ output.gsub(/^/, " " * indent)
348
+ end
349
+ end
350
+
351
+ end
352
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Herb
4
+ class LexResult < Result
5
+ attr_reader :value
6
+
7
+ def initialize(value, source, warnings, errors)
8
+ @value = TokenList.new(value)
9
+ super(source, warnings, errors)
10
+ end
11
+
12
+ def success?
13
+ errors.empty?
14
+ end
15
+
16
+ def failed?
17
+ errors.any?
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module Herb
6
+ module LibHerb
7
+ attach_function :array_get, [:pointer, :int], :pointer
8
+ attach_function :array_capacity, [:pointer], :size_t
9
+ attach_function :array_size, [:pointer], :size_t
10
+
11
+ class Array
12
+ extend Forwardable
13
+
14
+ attr_reader :pointer, :item_class
15
+
16
+ def_delegators :items, :first, :last, :[]
17
+
18
+ def initialize(pointer, item_class)
19
+ @pointer = pointer
20
+ @item_class = item_class
21
+ end
22
+
23
+ def capacity
24
+ LibHerb.array_capacity(pointer)
25
+ end
26
+
27
+ def size
28
+ LibHerb.array_size(pointer)
29
+ end
30
+
31
+ def item_pointers
32
+ size.times.map { |item|
33
+ LibHerb.array_get(pointer, item)
34
+ }
35
+ end
36
+
37
+ def items
38
+ item_pointers.map { |item_pointer|
39
+ item_class.new(item_pointer)
40
+ }
41
+ end
42
+
43
+ def inspect
44
+ %(#<#{self.class} size=#{size} capacity=#{capacity} item_class=#{item_class} pointer=#{pointer}>)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Herb
4
+ module LibHerb
5
+ attach_function :ast_node_type, [:pointer], :int
6
+ attach_function :ast_node_type_to_string, [:pointer], :string
7
+ attach_function :ast_node_children, [:pointer], :pointer
8
+ attach_function :ast_node_child_count, [:pointer], :size_t
9
+ attach_function :ast_pretty_print_node, [:pointer, :size_t, :size_t, :pointer], :void
10
+ attach_function :ast_node_free, [:pointer], :void
11
+
12
+ class ASTNode
13
+ attr_reader :pointer
14
+
15
+ def initialize(pointer)
16
+ @pointer = pointer
17
+ end
18
+
19
+ def type_int
20
+ LibHerb.ast_node_type(pointer)
21
+ end
22
+
23
+ def type
24
+ LibHerb.ast_node_type_to_string(pointer)
25
+ end
26
+
27
+ def child_count
28
+ LibHerb.ast_node_child_count(pointer)
29
+ end
30
+
31
+ def children
32
+ LibHerb::Array.new(
33
+ LibHerb.ast_node_children(pointer),
34
+ ASTNode
35
+ )
36
+ end
37
+
38
+ def inspect
39
+ LibHerb::Buffer.with do |output|
40
+ LibHerb.ast_pretty_print_node(pointer, 0, 0, output.pointer)
41
+
42
+ output.read.force_encoding("utf-8") # TODO: remove force_encoding
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Herb
4
+ module LibHerb
5
+ attach_function :buffer_init, [:pointer], :bool
6
+ attach_function :buffer_free, [:pointer], :void
7
+ attach_function :buffer_value, [:pointer], :pointer
8
+ attach_function :buffer_length, [:pointer], :size_t
9
+ attach_function :buffer_capacity, [:pointer], :size_t
10
+ attach_function :buffer_sizeof, [], :size_t
11
+ attach_function :buffer_append, [:pointer, :pointer], :void
12
+
13
+ class Buffer
14
+ SIZEOF = LibHerb.buffer_sizeof
15
+
16
+ attr_reader :pointer
17
+
18
+ def initialize(pointer)
19
+ @pointer = pointer
20
+ end
21
+
22
+ def append(string)
23
+ LibHerb.buffer_append(pointer, string)
24
+ end
25
+
26
+ def value
27
+ LibHerb.buffer_value(pointer)
28
+ end
29
+
30
+ def length
31
+ LibHerb.buffer_length(pointer)
32
+ end
33
+
34
+ def capacity
35
+ LibHerb.buffer_capacity(pointer)
36
+ end
37
+
38
+ def read
39
+ value.read_string(length)
40
+ end
41
+
42
+ def self.with
43
+ FFI::MemoryPointer.new(SIZEOF) do |pointer|
44
+ raise "couldn't allocate Buffer" unless LibHerb.buffer_init(pointer)
45
+
46
+ return yield new(pointer)
47
+ ensure
48
+ LibHerb.buffer_free(pointer)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module Herb
6
+ class ExtractResult
7
+ extend Forwardable
8
+
9
+ def_delegators :@buffer, :read
10
+
11
+ attr_accessor :buffer
12
+
13
+ def initialize(pointer)
14
+ @buffer = LibHerb::Buffer.new(pointer)
15
+ end
16
+ end
17
+ end