Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions java/herb_jni.c
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,35 @@ Java_org_herb_Herb_extractRuby(JNIEnv* env, jclass clazz, jstring source, jobjec
return result;
}

JNIEXPORT jbyteArray JNICALL
Java_org_herb_Herb_parseRuby(JNIEnv* env, jclass clazz, jstring source) {
const char* src = (*env)->GetStringUTFChars(env, source, 0);
size_t src_len = strlen(src);

herb_ruby_parse_result_T* parse_result = herb_parse_ruby(src, src_len);

if (!parse_result) {
(*env)->ReleaseStringUTFChars(env, source, src);
return NULL;
}

pm_buffer_t buffer = { 0 };
pm_serialize(&parse_result->parser, parse_result->root, &buffer);

jbyteArray result = NULL;

if (buffer.length > 0) {
result = (*env)->NewByteArray(env, (jsize) buffer.length);
(*env)->SetByteArrayRegion(env, result, 0, (jsize) buffer.length, (const jbyte*) buffer.value);
}

pm_buffer_free(&buffer);
herb_free_ruby_parse_result(parse_result);
(*env)->ReleaseStringUTFChars(env, source, src);

return result;
}

JNIEXPORT jstring JNICALL
Java_org_herb_Herb_extractHTML(JNIEnv* env, jclass clazz, jstring source) {
const char* src = (*env)->GetStringUTFChars(env, source, 0);
Expand Down
1 change: 1 addition & 0 deletions java/herb_jni.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ JNIEXPORT jobject JNICALL Java_org_herb_Herb_parse(JNIEnv*, jclass, jstring, job
JNIEXPORT jobject JNICALL Java_org_herb_Herb_lex(JNIEnv*, jclass, jstring);
JNIEXPORT jstring JNICALL Java_org_herb_Herb_extractRuby(JNIEnv*, jclass, jstring, jobject);
JNIEXPORT jstring JNICALL Java_org_herb_Herb_extractHTML(JNIEnv*, jclass, jstring);
JNIEXPORT jbyteArray JNICALL Java_org_herb_Herb_parseRuby(JNIEnv*, jclass, jstring);

#ifdef __cplusplus
}
Expand Down
1 change: 1 addition & 0 deletions java/org/herb/Herb.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class Herb {
public static native LexResult lex(String source);
public static native String extractRuby(String source, ExtractRubyOptions options);
public static native String extractHTML(String source);
public static native byte[] parseRuby(String source);

public static ParseResult parse(String source) {
return parse(source, null);
Expand Down
16 changes: 16 additions & 0 deletions java/org/herb/HerbTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,22 @@ void testParserOptionsPrismNodesDeep() {
assertTrue(inspectShallow.contains("prism_node:"));
}

@Test
void testParseRuby() {
byte[] result = Herb.parseRuby("link_to('Home', root_path)");

assertNotNull(result);
assertTrue(result.length > 0);
}

@Test
void testParseRubyEmpty() {
byte[] result = Herb.parseRuby("");

assertNotNull(result);
assertTrue(result.length > 0);
}

@Test
void testParserOptionsAnalyze() {
String source = "<% if true %><div></div><% end %>";
Expand Down
3 changes: 3 additions & 0 deletions javascript/packages/core/src/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ interface LibHerbBackendFunctions {
extractRuby: (source: string, options?: ExtractRubyOptions) => string
extractHTML: (source: string) => string

parseRuby: (source: string) => Uint8Array | null

version: () => string
}

Expand All @@ -21,6 +23,7 @@ const expectedFunctions = [
"lex",
"extractRuby",
"extractHTML",
"parseRuby",
"version",
] as const

Expand Down
20 changes: 20 additions & 0 deletions javascript/packages/core/src/herb-backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import { LexResult } from "./lex-result.js"
import { ParseResult } from "./parse-result.js"
import { DEFAULT_PARSER_OPTIONS } from "./parser-options.js"
import { DEFAULT_EXTRACT_RUBY_OPTIONS } from "./extract-ruby-options.js"
import { deserializePrismParseResult } from "./prism/index.js"

import type { LibHerbBackend, BackendPromise } from "./backend.js"
import type { ParseOptions } from "./parser-options.js"
import type { ExtractRubyOptions } from "./extract-ruby-options.js"
import type { PrismParseResult } from "./prism/index.js"

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

/**
* Parses a Ruby source string using Prism via the libherb backend.
* @param source - The Ruby source code to parse.
* @returns A Prism ParseResult containing the AST.
* @throws Error if the backend is not loaded.
*/
parseRuby(source: string): PrismParseResult {
this.ensureBackend()

const bytes = this.backend.parseRuby(ensureString(source))

if (!bytes) {
throw new Error("Failed to parse Ruby source")
}

return deserializePrismParseResult(bytes, source)
}

/**
* Extracts HTML from the given source.
* @param source - The source code to extract HTML from.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions javascript/packages/node-wasm/test/parse-ruby.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, test, expect, beforeAll } from "vitest"
import { Herb, inspectPrismNode } from "../src"

describe("parseRuby", () => {
beforeAll(async () => {
await Herb.load()
})

test("parseRuby() parses a simple expression", () => {
const source = "1 + 2"
const result = Herb.parseRuby(source)
expect(inspectPrismNode(result.value, source)).toMatchSnapshot()
})

test("parseRuby() parses a class definition", () => {
const source = "class Foo; end"
const result = Herb.parseRuby(source)
expect(inspectPrismNode(result.value, source)).toMatchSnapshot()
})

test("parseRuby() parses a method definition", () => {
const source = 'def greet(name)\n "Hello, #{name}!"\nend'
const result = Herb.parseRuby(source)
expect(inspectPrismNode(result.value, source)).toMatchSnapshot()
})

test("parseRuby() parses raw Ruby, not ERB", () => {
const source = "x = 1 + 2"
const result = Herb.parseRuby(source)
expect(inspectPrismNode(result.value, source)).toMatchSnapshot()
})
})
38 changes: 38 additions & 0 deletions javascript/packages/node/extension/herb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,43 @@ napi_value Herb_extract_html(napi_env env, napi_callback_info info) {
return result;
}

napi_value Herb_parse_ruby(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

if (argc < 1) {
napi_throw_error(env, nullptr, "Wrong number of arguments");
return nullptr;
}

char* string = CheckString(env, args[0]);
if (!string) { return nullptr; }

herb_ruby_parse_result_T* parse_result = herb_parse_ruby(string, strlen(string));

if (!parse_result) {
free(string);
return nullptr;
}

pm_buffer_t buffer = { 0 };
pm_serialize(&parse_result->parser, parse_result->root, &buffer);

napi_value result = nullptr;

if (buffer.length > 0) {
void* data;
napi_create_buffer_copy(env, buffer.length, buffer.value, &data, &result);
}

pm_buffer_free(&buffer);
herb_free_ruby_parse_result(parse_result);
free(string);

return result;
}

napi_value Herb_version(napi_env env, napi_callback_info info) {
const char* libherb_version = herb_version();
const char* libprism_version = herb_prism_version();
Expand All @@ -305,6 +342,7 @@ napi_value Init(napi_env env, napi_value exports) {
{ "extractRuby", nullptr, Herb_extract_ruby, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "extractHTML", nullptr, Herb_extract_html, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "version", nullptr, Herb_version, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "parseRuby", nullptr, Herb_parse_ruby, nullptr, nullptr, nullptr, napi_default, nullptr },
};

napi_define_properties(env, exports, sizeof(descriptors) / sizeof(descriptors[0]), descriptors);
Expand Down
Loading
Loading