@herb-tools/core 0.1.0
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.
- package/CHANGELOG.md +3 -0
- package/README.md +9 -0
- package/dist/herb-core.browser.js +2704 -0
- package/dist/herb-core.browser.js.map +1 -0
- package/dist/herb-core.cjs +2759 -0
- package/dist/herb-core.cjs.map +1 -0
- package/dist/herb-core.esm.js +2704 -0
- package/dist/herb-core.esm.js.map +1 -0
- package/dist/herb-core.umd.js +2765 -0
- package/dist/herb-core.umd.js.map +1 -0
- package/dist/types/ast.d.ts +4 -0
- package/dist/types/backend.d.ts +24 -0
- package/dist/types/error.d.ts +16 -0
- package/dist/types/errors.d.ts +222 -0
- package/dist/types/herb-backend.d.ts +87 -0
- package/dist/types/index.d.ts +15 -0
- package/dist/types/lex-result.d.ts +50 -0
- package/dist/types/location.d.ts +18 -0
- package/dist/types/node.d.ts +27 -0
- package/dist/types/nodes.d.ts +682 -0
- package/dist/types/parse-result.d.ts +62 -0
- package/dist/types/position.d.ts +15 -0
- package/dist/types/range.d.ts +12 -0
- package/dist/types/result.d.ts +10 -0
- package/dist/types/token-list.d.ts +16 -0
- package/dist/types/token.d.ts +22 -0
- package/dist/types/util.d.ts +2 -0
- package/dist/types/visitor.d.ts +11 -0
- package/dist/types/warning.d.ts +11 -0
- package/package.json +49 -0
- package/src/ast.ts +7 -0
- package/src/backend.ts +85 -0
- package/src/error.ts +38 -0
- package/src/errors.ts +820 -0
- package/src/herb-backend.ts +152 -0
- package/src/index.ts +15 -0
- package/src/lex-result.ts +78 -0
- package/src/location.ts +51 -0
- package/src/node.ts +106 -0
- package/src/nodes.ts +2294 -0
- package/src/parse-result.ts +101 -0
- package/src/position.ts +38 -0
- package/src/range.ts +35 -0
- package/src/result.ts +26 -0
- package/src/token-list.ts +57 -0
- package/src/token.ts +63 -0
- package/src/util.ts +19 -0
- package/src/visitor.ts +14 -0
- package/src/warning.ts +20 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import packageJSON from "../package.json" with { type: "json" }
|
|
2
|
+
|
|
3
|
+
import { ensureString } from "./util.js"
|
|
4
|
+
import { LexResult } from "./lex-result.js"
|
|
5
|
+
import { ParseResult } from "./parse-result.js"
|
|
6
|
+
|
|
7
|
+
import type { LibHerbBackend, BackendPromise } from "./backend.js"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The main Herb parser interface, providing methods to lex and parse input.
|
|
11
|
+
*/
|
|
12
|
+
export abstract class HerbBackend {
|
|
13
|
+
/** The backend instance handling lexing and parsing. */
|
|
14
|
+
public backend: LibHerbBackend | undefined = undefined
|
|
15
|
+
public readonly backendPromise: BackendPromise
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new Herb instance.
|
|
19
|
+
* @param backendPromise - A promise resolving to a `LibHerbBackend` implementation for lexing and parsing.
|
|
20
|
+
* @throws Error if no valid backend is provided.
|
|
21
|
+
*/
|
|
22
|
+
constructor(backendPromise: BackendPromise) {
|
|
23
|
+
if (!backendPromise) {
|
|
24
|
+
throw new Error("No LibHerb backend provided")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this.backendPromise = backendPromise
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Loads the backend by resolving the backend promise.
|
|
32
|
+
* @returns A promise containing the resolved `HerbBackend` instance after loading it.
|
|
33
|
+
*/
|
|
34
|
+
async load(): Promise<HerbBackend> {
|
|
35
|
+
const backend = await this.backendPromise()
|
|
36
|
+
this.backend = backend
|
|
37
|
+
return this
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Lexes the given source string into a `LexResult`.
|
|
42
|
+
* @param source - The source code to lex.
|
|
43
|
+
* @returns A `LexResult` instance.
|
|
44
|
+
* @throws Error if the backend is not loaded.
|
|
45
|
+
*/
|
|
46
|
+
lex(source: string): LexResult {
|
|
47
|
+
this.ensureBackend()
|
|
48
|
+
|
|
49
|
+
return LexResult.from(this.backend.lex(ensureString(source)))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Lexes a file.
|
|
54
|
+
* @param path - The file path to lex.
|
|
55
|
+
* @returns A `LexResult` instance.
|
|
56
|
+
* @throws Error if the backend is not loaded.
|
|
57
|
+
*/
|
|
58
|
+
lexFile(path: string): LexResult {
|
|
59
|
+
this.ensureBackend()
|
|
60
|
+
|
|
61
|
+
return LexResult.from(this.backend.lexFile(ensureString(path)))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Parses the given source string into a `ParseResult`.
|
|
66
|
+
* @param source - The source code to parse.
|
|
67
|
+
* @returns A `ParseResult` instance.
|
|
68
|
+
* @throws Error if the backend is not loaded.
|
|
69
|
+
*/
|
|
70
|
+
parse(source: string): ParseResult {
|
|
71
|
+
this.ensureBackend()
|
|
72
|
+
|
|
73
|
+
return ParseResult.from(this.backend.parse(ensureString(source)))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Parses a file.
|
|
78
|
+
* @param path - The file path to parse.
|
|
79
|
+
* @returns A `ParseResult` instance.
|
|
80
|
+
* @throws Error if the backend is not loaded.
|
|
81
|
+
*/
|
|
82
|
+
parseFile(path: string): ParseResult {
|
|
83
|
+
this.ensureBackend()
|
|
84
|
+
|
|
85
|
+
return ParseResult.from(this.backend.parseFile(ensureString(path)))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Extracts embedded Ruby code from the given source.
|
|
90
|
+
* @param source - The source code to extract Ruby from.
|
|
91
|
+
* @returns The extracted Ruby code as a string.
|
|
92
|
+
* @throws Error if the backend is not loaded.
|
|
93
|
+
*/
|
|
94
|
+
extractRuby(source: string): string {
|
|
95
|
+
this.ensureBackend()
|
|
96
|
+
|
|
97
|
+
return this.backend.extractRuby(ensureString(source))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Extracts HTML from the given source.
|
|
102
|
+
* @param source - The source code to extract HTML from.
|
|
103
|
+
* @returns The extracted HTML as a string.
|
|
104
|
+
* @throws Error if the backend is not loaded.
|
|
105
|
+
*/
|
|
106
|
+
extractHTML(source: string): string {
|
|
107
|
+
this.ensureBackend()
|
|
108
|
+
|
|
109
|
+
return this.backend.extractHTML(ensureString(source))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Gets the Herb version information, including the core and backend versions.
|
|
114
|
+
* @returns A version string containing backend, core, and libherb versions.
|
|
115
|
+
* @throws Error if the backend is not loaded.
|
|
116
|
+
*/
|
|
117
|
+
get version(): string {
|
|
118
|
+
this.ensureBackend()
|
|
119
|
+
|
|
120
|
+
const backend = this.backendVersion()
|
|
121
|
+
const core = `${packageJSON.name}@${packageJSON.version}`
|
|
122
|
+
const libherb = this.backend.version()
|
|
123
|
+
|
|
124
|
+
return `${backend}, ${core}, ${libherb}`
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Ensures that the backend is loaded.
|
|
129
|
+
* @throws Error if the backend is not loaded.
|
|
130
|
+
*/
|
|
131
|
+
ensureBackend(): asserts this is { backend: LibHerbBackend } {
|
|
132
|
+
if (!this.isLoaded) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
"Herb backend is not loaded. Call `await Herb.load()` first.",
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Checks if the backend is loaded.
|
|
141
|
+
* @returns True if the backend is loaded, false otherwise.
|
|
142
|
+
*/
|
|
143
|
+
get isLoaded() {
|
|
144
|
+
return this.backend !== undefined
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Abstract method to get the backend version.
|
|
149
|
+
* @returns A string representing the backend version.
|
|
150
|
+
*/
|
|
151
|
+
abstract backendVersion(): string
|
|
152
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from "./ast.js"
|
|
2
|
+
export * from "./backend.js"
|
|
3
|
+
export * from "./errors.js"
|
|
4
|
+
export * from "./herb-backend.js"
|
|
5
|
+
export * from "./lex-result.js"
|
|
6
|
+
export * from "./location.js"
|
|
7
|
+
export * from "./nodes.js"
|
|
8
|
+
export * from "./parse-result.js"
|
|
9
|
+
export * from "./position.js"
|
|
10
|
+
export * from "./range.js"
|
|
11
|
+
export * from "./result.js"
|
|
12
|
+
export * from "./token-list.js"
|
|
13
|
+
export * from "./token.js"
|
|
14
|
+
export * from "./util.js"
|
|
15
|
+
export * from "./visitor.js"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Result } from "./result.js"
|
|
2
|
+
import { TokenList, SerializedTokenList } from "./token-list.js"
|
|
3
|
+
|
|
4
|
+
export type SerializedLexResult = {
|
|
5
|
+
tokens: SerializedTokenList
|
|
6
|
+
source: string
|
|
7
|
+
warnings: any[]
|
|
8
|
+
errors: any[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Represents the result of a lexical analysis, extending the base `Result` class.
|
|
13
|
+
* It contains the token list, source code, warnings, and errors.
|
|
14
|
+
*/
|
|
15
|
+
export class LexResult extends Result {
|
|
16
|
+
/** The list of tokens generated from the source code. */
|
|
17
|
+
readonly value: TokenList
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates a `LexResult` instance from a serialized result.
|
|
21
|
+
* @param result - The serialized lexical result containing tokens, source, warnings, and errors.
|
|
22
|
+
* @returns A new `LexResult` instance.
|
|
23
|
+
*/
|
|
24
|
+
static from(result: SerializedLexResult) {
|
|
25
|
+
return new LexResult(
|
|
26
|
+
TokenList.from(result.tokens || []),
|
|
27
|
+
result.source,
|
|
28
|
+
result.warnings,
|
|
29
|
+
result.errors,
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Constructs a new `LexResult`.
|
|
35
|
+
* @param value - The list of tokens.
|
|
36
|
+
* @param source - The source code that was lexed.
|
|
37
|
+
* @param warnings - An array of warnings encountered during lexing.
|
|
38
|
+
* @param errors - An array of errors encountered during lexing.
|
|
39
|
+
*/
|
|
40
|
+
constructor(
|
|
41
|
+
value: TokenList,
|
|
42
|
+
source: string,
|
|
43
|
+
warnings: any[] = [],
|
|
44
|
+
errors: any[] = [],
|
|
45
|
+
) {
|
|
46
|
+
super(source, warnings, errors)
|
|
47
|
+
this.value = value
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Determines if the lexing was successful.
|
|
52
|
+
* @returns `true` if there are no errors, otherwise `false`.
|
|
53
|
+
*/
|
|
54
|
+
override success(): boolean {
|
|
55
|
+
return this.errors.length === 0
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Determines if the lexing failed.
|
|
60
|
+
* @returns `true` if there are errors, otherwise `false`.
|
|
61
|
+
*/
|
|
62
|
+
override failed(): boolean {
|
|
63
|
+
return this.errors.length > 0
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Converts the `LexResult` to a JSON representation.
|
|
68
|
+
* @returns An object containing the token list, source, warnings, and errors.
|
|
69
|
+
*/
|
|
70
|
+
toJSON() {
|
|
71
|
+
return {
|
|
72
|
+
value: this.value,
|
|
73
|
+
source: this.source,
|
|
74
|
+
warnings: this.warnings,
|
|
75
|
+
errors: this.errors,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/location.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Position } from "./position.js"
|
|
2
|
+
import type { SerializedPosition } from "./position.js"
|
|
3
|
+
|
|
4
|
+
export type SerializedLocation = {
|
|
5
|
+
start: SerializedPosition
|
|
6
|
+
end: SerializedPosition
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class Location {
|
|
10
|
+
readonly start: Position
|
|
11
|
+
readonly end: Position
|
|
12
|
+
|
|
13
|
+
static from(location: SerializedLocation) {
|
|
14
|
+
const start = Position.from(location.start)
|
|
15
|
+
const end = Position.from(location.end)
|
|
16
|
+
|
|
17
|
+
return new Location(start, end)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
constructor(start: Position, end: Position) {
|
|
21
|
+
this.start = start
|
|
22
|
+
this.end = end
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
toHash(): SerializedLocation {
|
|
26
|
+
return {
|
|
27
|
+
start: this.start.toHash(),
|
|
28
|
+
end: this.end.toHash(),
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
toJSON(): SerializedLocation {
|
|
33
|
+
return this.toHash()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
treeInspect(): string {
|
|
37
|
+
return `${this.start.treeInspect()}-${this.end.treeInspect()}`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
treeInspectWithLabel(): string {
|
|
41
|
+
return `(location: ${this.treeInspect()})`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
inspect(): string {
|
|
45
|
+
return `#<Herb::Location ${this.treeInspect()}>`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
toString(): string {
|
|
49
|
+
return this.inspect()
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/node.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Location, SerializedLocation } from "./location.js"
|
|
2
|
+
import { HerbError, SerializedHerbError } from "./error.js"
|
|
3
|
+
import { NodeType, SerializedNodeType, fromSerializedNode } from "./nodes.js"
|
|
4
|
+
|
|
5
|
+
export interface SerializedNode {
|
|
6
|
+
type: SerializedNodeType
|
|
7
|
+
location: SerializedLocation
|
|
8
|
+
errors: SerializedHerbError[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface BaseNodeProps {
|
|
12
|
+
type: NodeType
|
|
13
|
+
location: Location
|
|
14
|
+
errors: HerbError[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export abstract class Node implements BaseNodeProps {
|
|
18
|
+
readonly type: NodeType
|
|
19
|
+
readonly location: Location
|
|
20
|
+
readonly errors: HerbError[]
|
|
21
|
+
|
|
22
|
+
static from(node: SerializedNode): Node {
|
|
23
|
+
return fromSerializedNode(node)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
constructor(type: NodeType, location: Location, errors: HerbError[]) {
|
|
27
|
+
this.type = type
|
|
28
|
+
this.location = location
|
|
29
|
+
this.errors = errors
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
toJSON(): SerializedNode {
|
|
33
|
+
return {
|
|
34
|
+
type: this.type,
|
|
35
|
+
location: this.location.toJSON(),
|
|
36
|
+
errors: this.errors,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
inspect(): string {
|
|
41
|
+
return this.treeInspect(0)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
abstract treeInspect(indent?: number): string
|
|
45
|
+
abstract recursiveErrors(): HerbError[]
|
|
46
|
+
abstract childNodes(): Node[]
|
|
47
|
+
|
|
48
|
+
protected inspectArray(
|
|
49
|
+
array: (Node | HerbError)[] | null | undefined,
|
|
50
|
+
prefix: string,
|
|
51
|
+
): string {
|
|
52
|
+
if (!array) return "∅\n"
|
|
53
|
+
if (array.length === 0) return "[]\n"
|
|
54
|
+
|
|
55
|
+
let output = `(${array.length} item${array.length == 1 ? "" : "s"})\n`
|
|
56
|
+
|
|
57
|
+
array.forEach((item, index) => {
|
|
58
|
+
const isLast = index === array.length - 1
|
|
59
|
+
|
|
60
|
+
if (item instanceof Node || item instanceof HerbError) {
|
|
61
|
+
output += this.inspectNode(
|
|
62
|
+
item,
|
|
63
|
+
prefix,
|
|
64
|
+
isLast ? " " : "│ ",
|
|
65
|
+
isLast,
|
|
66
|
+
false,
|
|
67
|
+
)
|
|
68
|
+
} else {
|
|
69
|
+
const symbol = isLast ? "└── " : "├── "
|
|
70
|
+
output += `${prefix}${symbol} ${item}\n`
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
output += `${prefix}\n`
|
|
75
|
+
|
|
76
|
+
return output
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
protected inspectNode(
|
|
80
|
+
node: Node | HerbError | undefined | null,
|
|
81
|
+
prefix: string,
|
|
82
|
+
prefix2: string = " ",
|
|
83
|
+
last: boolean = true,
|
|
84
|
+
trailingNewline: boolean = true,
|
|
85
|
+
): string {
|
|
86
|
+
if (!node) return "∅\n"
|
|
87
|
+
|
|
88
|
+
let output = trailingNewline ? "\n" : ""
|
|
89
|
+
output += `${prefix}`
|
|
90
|
+
|
|
91
|
+
output += last ? "└── " : "├── "
|
|
92
|
+
output += node
|
|
93
|
+
.treeInspect()
|
|
94
|
+
.trimStart()
|
|
95
|
+
.split("\n")
|
|
96
|
+
.map((line, index) =>
|
|
97
|
+
index == 0 ? line.trimStart() : `${prefix}${prefix2}${line}`,
|
|
98
|
+
)
|
|
99
|
+
.join("\n")
|
|
100
|
+
.trimStart()
|
|
101
|
+
|
|
102
|
+
output += `\n`
|
|
103
|
+
|
|
104
|
+
return output
|
|
105
|
+
}
|
|
106
|
+
}
|