Skip to content

Commit 07cb479

Browse files
committed
Herb: Expose Herb.parse_puby to parse Ruby with Prism
1 parent 5fff477 commit 07cb479

File tree

12 files changed

+175
-0
lines changed

12 files changed

+175
-0
lines changed

java/herb_jni.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,35 @@ Java_org_herb_Herb_extractRuby(JNIEnv* env, jclass clazz, jstring source, jobjec
185185
return result;
186186
}
187187

188+
JNIEXPORT jbyteArray JNICALL
189+
Java_org_herb_Herb_parseRuby(JNIEnv* env, jclass clazz, jstring source) {
190+
const char* src = (*env)->GetStringUTFChars(env, source, 0);
191+
size_t src_len = strlen(src);
192+
193+
herb_ruby_parse_result_T* parse_result = herb_parse_ruby(src, src_len);
194+
195+
if (!parse_result) {
196+
(*env)->ReleaseStringUTFChars(env, source, src);
197+
return NULL;
198+
}
199+
200+
pm_buffer_t buffer = { 0 };
201+
pm_serialize(&parse_result->parser, parse_result->root, &buffer);
202+
203+
jbyteArray result = NULL;
204+
205+
if (buffer.length > 0) {
206+
result = (*env)->NewByteArray(env, (jsize) buffer.length);
207+
(*env)->SetByteArrayRegion(env, result, 0, (jsize) buffer.length, (const jbyte*) buffer.value);
208+
}
209+
210+
pm_buffer_free(&buffer);
211+
herb_free_ruby_parse_result(parse_result);
212+
(*env)->ReleaseStringUTFChars(env, source, src);
213+
214+
return result;
215+
}
216+
188217
JNIEXPORT jstring JNICALL
189218
Java_org_herb_Herb_extractHTML(JNIEnv* env, jclass clazz, jstring source) {
190219
const char* src = (*env)->GetStringUTFChars(env, source, 0);

java/herb_jni.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ JNIEXPORT jobject JNICALL Java_org_herb_Herb_parse(JNIEnv*, jclass, jstring, job
1313
JNIEXPORT jobject JNICALL Java_org_herb_Herb_lex(JNIEnv*, jclass, jstring);
1414
JNIEXPORT jstring JNICALL Java_org_herb_Herb_extractRuby(JNIEnv*, jclass, jstring, jobject);
1515
JNIEXPORT jstring JNICALL Java_org_herb_Herb_extractHTML(JNIEnv*, jclass, jstring);
16+
JNIEXPORT jbyteArray JNICALL Java_org_herb_Herb_parseRuby(JNIEnv*, jclass, jstring);
1617

1718
#ifdef __cplusplus
1819
}

java/org/herb/Herb.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class Herb {
2020
public static native LexResult lex(String source);
2121
public static native String extractRuby(String source, ExtractRubyOptions options);
2222
public static native String extractHTML(String source);
23+
public static native byte[] parseRuby(String source);
2324

2425
public static ParseResult parse(String source) {
2526
return parse(source, null);

java/org/herb/HerbTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,22 @@ void testParserOptionsPrismNodesDeep() {
147147
assertTrue(inspectShallow.contains("prism_node:"));
148148
}
149149

150+
@Test
151+
void testParseRuby() {
152+
byte[] result = Herb.parseRuby("link_to('Home', root_path)");
153+
154+
assertNotNull(result);
155+
assertTrue(result.length > 0);
156+
}
157+
158+
@Test
159+
void testParseRubyEmpty() {
160+
byte[] result = Herb.parseRuby("");
161+
162+
assertNotNull(result);
163+
assertTrue(result.length > 0);
164+
}
165+
150166
@Test
151167
void testParserOptionsAnalyze() {
152168
String source = "<% if true %><div></div><% end %>";

javascript/packages/core/src/backend.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ interface LibHerbBackendFunctions {
1111
extractRuby: (source: string, options?: ExtractRubyOptions) => string
1212
extractHTML: (source: string) => string
1313

14+
parseRuby: (source: string) => Uint8Array | null
15+
1416
version: () => string
1517
}
1618

@@ -21,6 +23,7 @@ const expectedFunctions = [
2123
"lex",
2224
"extractRuby",
2325
"extractHTML",
26+
"parseRuby",
2427
"version",
2528
] as const
2629

javascript/packages/core/src/herb-backend.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import { LexResult } from "./lex-result.js"
55
import { ParseResult } from "./parse-result.js"
66
import { DEFAULT_PARSER_OPTIONS } from "./parser-options.js"
77
import { DEFAULT_EXTRACT_RUBY_OPTIONS } from "./extract-ruby-options.js"
8+
import { deserializePrismParseResult } from "./prism/index.js"
89

910
import type { LibHerbBackend, BackendPromise } from "./backend.js"
1011
import type { ParseOptions } from "./parser-options.js"
1112
import type { ExtractRubyOptions } from "./extract-ruby-options.js"
13+
import type { PrismParseResult } from "./prism/index.js"
1214

1315
/**
1416
* The main Herb parser interface, providing methods to lex and parse input.
@@ -97,6 +99,24 @@ export abstract class HerbBackend {
9799
return this.backend.extractRuby(ensureString(source), mergedOptions)
98100
}
99101

102+
/**
103+
* Parses a Ruby source string using Prism via the libherb backend.
104+
* @param source - The Ruby source code to parse.
105+
* @returns A Prism ParseResult containing the AST.
106+
* @throws Error if the backend is not loaded.
107+
*/
108+
parseRuby(source: string): PrismParseResult {
109+
this.ensureBackend()
110+
111+
const bytes = this.backend.parseRuby(ensureString(source))
112+
113+
if (!bytes) {
114+
throw new Error("Failed to parse Ruby source")
115+
}
116+
117+
return deserializePrismParseResult(bytes, source)
118+
}
119+
100120
/**
101121
* Extracts HTML from the given source.
102122
* @param source - The source code to extract HTML from.

javascript/packages/node/extension/herb.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,43 @@ napi_value Herb_extract_html(napi_env env, napi_callback_info info) {
285285
return result;
286286
}
287287

288+
napi_value Herb_parse_ruby(napi_env env, napi_callback_info info) {
289+
size_t argc = 1;
290+
napi_value args[1];
291+
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
292+
293+
if (argc < 1) {
294+
napi_throw_error(env, nullptr, "Wrong number of arguments");
295+
return nullptr;
296+
}
297+
298+
char* string = CheckString(env, args[0]);
299+
if (!string) { return nullptr; }
300+
301+
herb_ruby_parse_result_T* parse_result = herb_parse_ruby(string, strlen(string));
302+
303+
if (!parse_result) {
304+
free(string);
305+
return nullptr;
306+
}
307+
308+
pm_buffer_t buffer = { 0 };
309+
pm_serialize(&parse_result->parser, parse_result->root, &buffer);
310+
311+
napi_value result = nullptr;
312+
313+
if (buffer.length > 0) {
314+
void* data;
315+
napi_create_buffer_copy(env, buffer.length, buffer.value, &data, &result);
316+
}
317+
318+
pm_buffer_free(&buffer);
319+
herb_free_ruby_parse_result(parse_result);
320+
free(string);
321+
322+
return result;
323+
}
324+
288325
napi_value Herb_version(napi_env env, napi_callback_info info) {
289326
const char* libherb_version = herb_version();
290327
const char* libprism_version = herb_prism_version();
@@ -305,6 +342,7 @@ napi_value Init(napi_env env, napi_value exports) {
305342
{ "extractRuby", nullptr, Herb_extract_ruby, nullptr, nullptr, nullptr, napi_default, nullptr },
306343
{ "extractHTML", nullptr, Herb_extract_html, nullptr, nullptr, nullptr, napi_default, nullptr },
307344
{ "version", nullptr, Herb_version, nullptr, nullptr, nullptr, napi_default, nullptr },
345+
{ "parseRuby", nullptr, Herb_parse_ruby, nullptr, nullptr, nullptr, napi_default, nullptr },
308346
};
309347

310348
napi_define_properties(env, exports, sizeof(descriptors) / sizeof(descriptors[0]), descriptors);

lib/herb.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,14 @@ def parse_file(path, **options)
7777
parse(File.read(path), **options)
7878
end
7979

80+
#: (String source) -> Prism::ParseResult
81+
def parse_ruby(source)
82+
require "prism"
83+
84+
ruby = extract_ruby(source, semicolons: true, preserve_positions: true)
85+
Prism.parse(ruby, partial_script: true)
86+
end
87+
8088
def configuration(project_path = nil)
8189
@configuration ||= Configuration.load(project_path)
8290
end

sig/herb.rbs

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/herb.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include <prism.h>
1212
#include <stdlib.h>
13+
#include <string.h>
1314

1415
HERB_EXPORTED_FUNCTION hb_array_T* herb_lex(const char* source, hb_allocator_T* allocator) {
1516
if (!source) { source = ""; }
@@ -83,3 +84,26 @@ HERB_EXPORTED_FUNCTION const char* herb_version(void) {
8384
HERB_EXPORTED_FUNCTION const char* herb_prism_version(void) {
8485
return PRISM_VERSION;
8586
}
87+
88+
HERB_EXPORTED_FUNCTION herb_ruby_parse_result_T* herb_parse_ruby(const char* source, size_t length) {
89+
if (!source) { return NULL; }
90+
91+
herb_ruby_parse_result_T* result = malloc(sizeof(herb_ruby_parse_result_T));
92+
if (!result) { return NULL; }
93+
94+
memset(&result->options, 0, sizeof(pm_options_t));
95+
pm_parser_init(&result->parser, (const uint8_t*) source, length, &result->options);
96+
result->root = pm_parse(&result->parser);
97+
98+
return result;
99+
}
100+
101+
HERB_EXPORTED_FUNCTION void herb_free_ruby_parse_result(herb_ruby_parse_result_T* result) {
102+
if (!result) { return; }
103+
104+
if (result->root) { pm_node_destroy(&result->parser, result->root); }
105+
106+
pm_parser_free(&result->parser);
107+
pm_options_free(&result->options);
108+
free(result);
109+
}

0 commit comments

Comments
 (0)