-
Notifications
You must be signed in to change notification settings - Fork 622
/
Copy pathParser.ts
126 lines (99 loc) · 3.23 KB
/
Parser.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
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { ParseError } from './ParseError';
import { type Tokenizer, type Token, TokenKind } from './Tokenizer';
import { type AstNode, AstScript, AstCommand, AstCompoundWord, AstText } from './AstNode';
export class Parser {
private readonly _tokenizer: Tokenizer;
private _peekedToken: Token | undefined;
public constructor(tokenizer: Tokenizer) {
this._tokenizer = tokenizer;
this._peekedToken = undefined;
}
public parse(): AstScript {
const script: AstScript = new AstScript();
const startingToken: Token = this._peekToken();
const astCommand: AstCommand | undefined = this._parseCommand();
if (!astCommand) {
throw new ParseError('Expecting a command', startingToken.range);
}
const nextToken: Token = this._peekToken();
if (nextToken.kind !== TokenKind.EndOfInput) {
throw new ParseError(`Unexpected token: ${TokenKind[nextToken.kind]}`, nextToken.range);
}
script.body = astCommand;
return script;
}
private _parseCommand(): AstCommand | undefined {
this._skipWhitespace();
const startingToken: Token = this._peekToken();
const command: AstCommand = new AstCommand();
command.commandPath = this._parseCompoundWord();
if (!command.commandPath) {
throw new ParseError('Expecting a command path', startingToken.range);
}
while (this._skipWhitespace()) {
const compoundWord: AstCompoundWord | undefined = this._parseCompoundWord();
if (!compoundWord) {
break;
}
command.arguments.push(compoundWord);
}
return command;
}
private _parseCompoundWord(): AstCompoundWord | undefined {
const compoundWord: AstCompoundWord = new AstCompoundWord();
for (;;) {
const node: AstNode | undefined = this._parseText();
if (!node) {
break;
}
compoundWord.parts.push(node);
}
if (compoundWord.parts.length === 0) {
// We didn't parse a word
return undefined;
}
return compoundWord;
}
private _parseText(): AstText | undefined {
const token: Token = this._peekToken();
if (token.kind === TokenKind.Text) {
this._readToken();
const astText: AstText = new AstText();
astText.token = token;
astText.range = token.range;
return astText;
}
return undefined;
}
/**
* Skips any whitespace tokens. Returns true if any whitespace was actually encountered.
*/
private _skipWhitespace(): boolean {
let sawWhitespace: boolean = false;
while (this._peekToken().kind === TokenKind.Spaces) {
this._readToken();
sawWhitespace = true;
}
if (this._peekToken().kind === TokenKind.EndOfInput) {
sawWhitespace = true;
}
return sawWhitespace;
}
private _readToken(): Token {
if (this._peekedToken) {
const token: Token = this._peekedToken;
this._peekedToken = undefined;
return token;
} else {
return this._tokenizer.readToken();
}
}
private _peekToken(): Token {
if (!this._peekedToken) {
this._peekedToken = this._tokenizer.readToken();
}
return this._peekedToken;
}
}