-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathreserved.ts
129 lines (122 loc) · 4.09 KB
/
reserved.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import { ParseNode } from "./parsenode.ts";
import { Visitor } from "./visitor.ts";
import { nextTokenIs, Scanner, Token, TokenError } from "./deps.ts";
type RangeSet = Array<[number, number]> | Array<string>;
function rangeToString(value: [number, number] | string): string {
if (typeof value === "string") return `"${value}"`;
const [from, to] = value;
if (from === to) return `${from}`;
if (to === Infinity) return `${from} to max`;
return `${from} to ${to}`;
}
/**
* Represents a Reserved definition.
*
* Reserved statements declare a range of field numbers or field names that cannot be used in this message.
*
* https://developers.google.com/protocol-buffers/docs/reference/proto3-spec#reserved
*/
export class Reserved extends ParseNode {
constructor(
/**
* A collection of ranges; either as numerical tuples, or as an array of
* field name identifiers.
*
* The numerical ranges are inclusive, and are always a number tuple. If
* the reserved range was just one field ID then both numbers in the tuple
* will be equal.
*
* For example `reserved 4, 20 to max;` will result in:
* `[[4, 4], [20, Infinity]]`
*/
public ranges: RangeSet = [],
/**
* The starting [line, column]
*/
public start: [number, number] = [0, 0],
/**
* The ending [line, column]
*/
public end: [number, number] = [0, 0],
) {
super();
}
toProto() {
return `reserved ${
(this.ranges as Array<[number, number] | string>).map(rangeToString).join(
", ",
)
};`;
}
toJSON() {
return {
type: "Reserved",
start: this.start,
end: this.end,
ranges: this.ranges,
};
}
accept(visitor: Visitor) {
visitor.visit?.(this);
visitor.visitReserved?.(this);
}
static async parse(scanner: Scanner): Promise<Reserved> {
if (scanner.contents !== "reserved") {
await nextTokenIs(scanner, Token.identifier, "reserved");
}
const start = scanner.startPos;
const token = await scanner.scan();
let passComma = true;
if (token === Token.string) {
const fieldNames: string[] = [scanner.contents.slice(1, -1)];
for await (const token of scanner) {
if (token === Token.token && scanner.contents === "," && passComma) {
passComma = false;
} else if (token === Token.string) {
fieldNames.push(scanner.contents.slice(1, -1));
passComma = true;
} else if (token === Token.token && scanner.contents === ";") {
return new Reserved(fieldNames, start, scanner.endPos);
} else {
throw new TokenError(scanner, token);
}
}
} else if (token === Token.int) {
const ranges: [number, number][] = [];
let from: number | void = Number(scanner.contents);
for await (const token of scanner) {
const str = scanner.contents;
if (token === Token.token && str === "," && passComma) {
if (from !== undefined) ranges.push([from, from]);
passComma = false;
from = undefined;
} else if (token === Token.int && from === undefined) {
passComma = true;
from = Number(str);
} else if (
token === Token.identifier && str === "to" && from !== undefined
) {
passComma = true;
const token = await scanner.scan();
if (token === Token.identifier && scanner.contents === "max") {
ranges.push([from, Infinity]);
from = undefined;
} else if (token === Token.int) {
ranges.push([from, Number(scanner.contents)]);
from = undefined;
} else {
throw new TokenError(scanner, token, Token.int, "or max");
}
} else if (token === Token.token && scanner.contents === ";") {
if (from !== undefined) ranges.push([from, from]);
return new Reserved(ranges, start, scanner.endPos);
} else {
throw new TokenError(scanner, token);
}
}
} else {
throw new TokenError(scanner, token);
}
throw new TokenError(scanner, Token.eof);
}
}