Skip to content

Commit 1b517e9

Browse files
authored
feat: add optional parse error locations (#34)
1 parent 48bbbbf commit 1b517e9

File tree

16 files changed

+711
-53
lines changed

16 files changed

+711
-53
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ jobs:
168168
working-directory: rust
169169
run: cargo test
170170

171+
171172
- name: Test (no default features)
172173
working-directory: rust
173174
run: cargo test --no-default-features

README.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,24 @@ const std::optional<lexer_error>& get_last_error();
132132

133133
Returns the last parse error, if any.
134134

135+
### `lexer::get_last_error_location`
136+
137+
```cpp
138+
const std::optional<error_location>& get_last_error_location();
139+
```
140+
141+
Returns the location of the last parse error, if available. Location tracking
142+
is best-effort and may be unavailable.
143+
144+
### `lexer::error_location`
145+
146+
```cpp
147+
struct error_location {
148+
uint32_t line; // 1-based
149+
uint32_t column; // 1-based (byte-oriented)
150+
};
151+
```
152+
135153
## C API
136154
137155
merve provides a C API (`merve_c.h`) for use from C programs, FFI bindings, or any language that can call C functions. The C API is compiled into the merve library alongside the C++ implementation.
@@ -141,11 +159,13 @@ merve provides a C API (`merve_c.h`) for use from C programs, FFI bindings, or a
141159
```c
142160
#include "merve_c.h"
143161
#include <stdio.h>
162+
#include <string.h>
144163
145164
int main(void) {
146165
const char* source = "exports.foo = 1;\nexports.bar = 2;\n";
147166
148-
merve_analysis result = merve_parse_commonjs(source, strlen(source));
167+
merve_error_loc err_loc = {0, 0};
168+
merve_analysis result = merve_parse_commonjs(source, strlen(source), &err_loc);
149169
150170
if (merve_is_valid(result)) {
151171
size_t count = merve_get_exports_count(result);
@@ -157,6 +177,9 @@ int main(void) {
157177
}
158178
} else {
159179
printf("Parse error: %d\n", merve_get_last_error());
180+
if (err_loc.line != 0) {
181+
printf(" at line %u, column %u\n", err_loc.line, err_loc.column);
182+
}
160183
}
161184
162185
merve_free(result);
@@ -180,12 +203,13 @@ Found 2 exports:
180203
| `merve_string` | Non-owning string reference (`data` + `length`). Not null-terminated. |
181204
| `merve_analysis` | Opaque handle to a parse result. Must be freed with `merve_free()`. |
182205
| `merve_version_components` | Struct with `major`, `minor`, `revision` fields. |
206+
| `merve_error_loc` | Error location (`line`, `column`). `{0,0}` means unavailable. |
183207

184208
#### Functions
185209

186210
| Function | Description |
187211
|----------|-------------|
188-
| `merve_parse_commonjs(input, length)` | Parse CommonJS source. Returns a handle (NULL only on OOM). |
212+
| `merve_parse_commonjs(input, length, out_err)` | Parse CommonJS source and optionally fill error location. Returns a handle (NULL only on OOM). |
189213
| `merve_is_valid(result)` | Check if parsing succeeded. NULL-safe. |
190214
| `merve_free(result)` | Free a parse result. NULL-safe. |
191215
| `merve_get_exports_count(result)` | Number of named exports found. |
@@ -198,6 +222,9 @@ Found 2 exports:
198222
| `merve_get_version()` | Version string (e.g. `"1.0.1"`). |
199223
| `merve_get_version_components()` | Version as `{major, minor, revision}`. |
200224

225+
On parse failure, `merve_parse_commonjs` writes a non-zero location when
226+
`out_err` is non-NULL and the location is available.
227+
201228
#### Error Constants
202229

203230
| Constant | Value | Description |

include/merve/parser.h

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

44
#include "merve/version.h"
55

6+
#include <cstddef>
67
#include <cstdint>
78
#include <optional>
89
#include <string>
@@ -37,6 +38,17 @@ enum lexer_error {
3738
TEMPLATE_NEST_OVERFLOW, ///< Template literal nesting too deep
3839
};
3940

41+
/**
42+
* @brief Source location information for a parse error.
43+
*
44+
* - line and column are 1-based.
45+
* - column is byte-oriented.
46+
*/
47+
struct error_location {
48+
uint32_t line;
49+
uint32_t column;
50+
};
51+
4052
/**
4153
* @brief Type alias for export names.
4254
*
@@ -146,6 +158,18 @@ std::optional<lexer_analysis> parse_commonjs(std::string_view file_contents);
146158
*/
147159
const std::optional<lexer_error>& get_last_error();
148160

161+
/**
162+
* @brief Get the location of the last failed parse operation.
163+
*
164+
* @return const std::optional<error_location>& The last error location, or
165+
* std::nullopt if unavailable.
166+
*
167+
* @note This is global state and may be overwritten by subsequent calls
168+
* to parse_commonjs().
169+
* @note Location tracking is best-effort and may be unavailable.
170+
*/
171+
const std::optional<error_location>& get_last_error_location();
172+
149173
} // namespace lexer
150174

151175
#endif // MERVE_PARSER_H

include/merve_c.h

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@ typedef struct {
3939
int revision;
4040
} merve_version_components;
4141

42+
/**
43+
* @brief Source location for a parse error.
44+
*
45+
* - line and column are 1-based.
46+
* - column is byte-oriented.
47+
*
48+
* A zeroed location (`{0, 0}`) means the location is unavailable.
49+
*/
50+
typedef struct {
51+
uint32_t line;
52+
uint32_t column;
53+
} merve_error_loc;
54+
4255
/* Error codes corresponding to lexer::lexer_error values. */
4356
#define MERVE_ERROR_TODO 0
4457
#define MERVE_ERROR_UNEXPECTED_PAREN 1
@@ -59,20 +72,32 @@ extern "C" {
5972
#endif
6073

6174
/**
62-
* Parse CommonJS source code and extract export information.
75+
* Parse CommonJS source code and optionally return error location.
6376
*
6477
* The source buffer must remain valid while accessing string_view-backed
6578
* export names from the returned handle.
6679
*
80+
* If @p out_err is non-NULL, it is always written:
81+
* - On success: set to {0, 0}.
82+
* - On parse failure with known location: set to that location.
83+
* - On parse failure without available location: set to {0, 0}.
84+
*
6785
* You must call merve_free() on the returned handle when done.
6886
*
69-
* @param input Pointer to the JavaScript source (need not be null-terminated).
70-
* NULL is treated as an empty string.
71-
* @param length Length of the input in bytes.
87+
* @param input Pointer to the JavaScript source (need not be
88+
* null-terminated). NULL is treated as an empty string.
89+
* @param length Length of the input in bytes.
90+
* @param out_err Optional output pointer for parse error location.
7291
* @return A handle to the parse result, or NULL on out-of-memory.
7392
* Use merve_is_valid() to check if parsing succeeded.
7493
*/
75-
merve_analysis merve_parse_commonjs(const char* input, size_t length);
94+
#ifdef __cplusplus
95+
merve_analysis merve_parse_commonjs(const char* input, size_t length,
96+
merve_error_loc* out_err = nullptr);
97+
#else
98+
merve_analysis merve_parse_commonjs(const char* input, size_t length,
99+
merve_error_loc* out_err);
100+
#endif
76101

77102
/**
78103
* Check whether the parse result is valid (parsing succeeded).
@@ -165,7 +190,7 @@ const char* merve_get_version(void);
165190
merve_version_components merve_get_version_components(void);
166191

167192
#ifdef __cplusplus
168-
} /* extern "C" */
193+
} /* extern "C" */
169194
#endif
170195

171196
#endif /* MERVE_C_H */

rust/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ merve = { version = "...", features = ["libcpp"] }
5151
### `parse_commonjs`
5252

5353
```rust
54-
pub fn parse_commonjs(source: &str) -> Result<Analysis<'_>, LexerError>
54+
pub fn parse_commonjs(source: &str) -> Result<Analysis<'_>, LocatedLexerError>
5555
```
5656

5757
Parse CommonJS source code and extract export information. The returned
@@ -100,6 +100,17 @@ Returned when the input contains ESM syntax or malformed constructs:
100100

101101
`LexerError` implements `Display` and, with the `std` feature, `std::error::Error`.
102102

103+
### `LocatedLexerError`
104+
105+
```rust
106+
pub struct LocatedLexerError {
107+
pub kind: LexerError,
108+
pub location: Option<ErrorLocation>,
109+
}
110+
```
111+
112+
`ErrorLocation` uses 1-based `line`/`column` (byte-oriented column).
113+
103114
### Versioning helpers
104115

105116
```rust

0 commit comments

Comments
 (0)