/
IndentationSensitive.js
141 lines (114 loc) · 3.62 KB
/
IndentationSensitive.js
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
130
131
132
133
134
135
136
137
138
139
140
141
import BuiltInRules from '../dist/built-in-rules.js';
import {Builder} from '../src/Builder.js';
import {Failure} from '../src/Failure.js';
import {TerminalNode} from '../src/nodes.js';
import * as pexprs from '../src/pexprs.js';
import {findIndentation} from './findIndentation.js';
import {InputStream} from './InputStream.js';
const INDENT_DESCRIPTION = 'an indented block';
const DEDENT_DESCRIPTION = 'a dedent';
// A sentinel value that is out of range for both charCodeAt() and codePointAt().
const INVALID_CODE_POINT = 0x10ffff + 1;
class InputStreamWithIndentation extends InputStream {
constructor(state) {
super(state.input);
this.state = state;
}
_indentationAt(pos) {
return this.state.userData[pos] || 0;
}
atEnd() {
return super.atEnd() && this._indentationAt(this.pos) === 0;
}
next() {
if (this._indentationAt(this.pos) !== 0) {
this.examinedLength = Math.max(this.examinedLength, this.pos);
return undefined;
}
return super.next();
}
nextCharCode() {
if (this._indentationAt(this.pos) !== 0) {
this.examinedLength = Math.max(this.examinedLength, this.pos);
return INVALID_CODE_POINT;
}
return super.nextCharCode();
}
nextCodePoint() {
if (this._indentationAt(this.pos) !== 0) {
this.examinedLength = Math.max(this.examinedLength, this.pos);
return INVALID_CODE_POINT;
}
return super.nextCodePoint();
}
}
class Indentation extends pexprs.PExpr {
constructor(isIndent = true) {
super();
this.isIndent = isIndent;
}
allowsSkippingPrecedingSpace() {
return true;
}
eval(state) {
const {inputStream} = state;
const pseudoTokens = state.userData;
state.doNotMemoize = true;
const origPos = inputStream.pos;
const sign = this.isIndent ? 1 : -1;
const count = (pseudoTokens[origPos] || 0) * sign;
if (count > 0) {
// Update the count to consume the pseudotoken.
state.userData = Object.create(pseudoTokens);
state.userData[origPos] -= sign;
state.pushBinding(new TerminalNode(0), origPos);
return true;
} else {
state.processFailure(origPos, this);
return false;
}
}
getArity() {
return 1;
}
_assertAllApplicationsAreValid(ruleName, grammar) {}
_isNullable(grammar, memo) {
return false;
}
assertChoicesHaveUniformArity(ruleName) {}
assertIteratedExprsAreNotNullable(grammar) {}
introduceParams(formals) {
return this;
}
substituteParams(actuals) {
return this;
}
toString() {
return this.isIndent ? 'indent' : 'dedent';
}
toDisplayString() {
return this.toString();
}
toFailure(grammar) {
const description = this.isIndent ? INDENT_DESCRIPTION : DEDENT_DESCRIPTION;
return new Failure(this, description, 'description');
}
}
// Create a new definition for `any` that can consume indent & dedent.
const applyIndent = new pexprs.Apply('indent');
const applyDedent = new pexprs.Apply('dedent');
const newAnyBody = new pexprs.Splice(BuiltInRules, 'any', [applyIndent, applyDedent], []);
export const IndentationSensitive = new Builder()
.newGrammar('IndentationSensitive')
.withSuperGrammar(BuiltInRules)
.define('indent', [], new Indentation(true), INDENT_DESCRIPTION, undefined, true)
.define('dedent', [], new Indentation(false), DEDENT_DESCRIPTION, undefined, true)
.extend('any', [], newAnyBody, 'any character', undefined)
.build();
Object.assign(IndentationSensitive, {
_matchStateInitializer(state) {
state.userData = findIndentation(state.input);
state.inputStream = new InputStreamWithIndentation(state);
},
supportsIncrementalParsing: false,
});