-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathIndenter.cs
More file actions
127 lines (110 loc) · 3.99 KB
/
Indenter.cs
File metadata and controls
127 lines (110 loc) · 3.99 KB
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
using System;
using System.Collections.Generic;
using GameDialog.Lang.Diagnostic;
namespace GameDialog.Lang
{
/// <summary>
/// Handles indentation tokens based on the current indentation level.
/// </summary>
internal class Indenter
{
/// <summary>
/// The underlying reader for source code.
/// </summary>
private readonly Reader _reader;
/// <summary>
/// The current state of the indenter.
/// </summary>
private IndenterState _state = IndenterState.Default;
/// <summary>
/// The stack of indentation levels.
/// </summary>
private readonly Stack<int> _levels = new();
/// <summary>
/// Initializes a new instance of the <see cref="Indenter"/> class.
/// </summary>
public Indenter(Reader reader)
{
_reader = reader;
_levels.Push(0);
}
/// <summary>
/// Reads the current indentation level and generates corresponding tokens.
/// </summary>
public void Read(Queue<Token> output)
{
Assert.IsTrue(_reader.IsAtLineStart(), $"Expected start of line, column {_reader.Column}.");
if (_state is IndenterState.Locked)
{
Unlock();
}
var startLocation = _reader.GetLocation();
var last = _levels.Peek();
int current = _reader.ReadIndentLevel();
if (current > last)
{
_levels.Push(current);
var indentLocation = new Location(_reader.Source, startLocation.Line, last + 1, current + 1);
output.Enqueue(Token.Indent(indentLocation));
return;
}
while (current < last)
{
var final = _levels.Pop();
last = _levels.TryPeek(out var level) ? level : 0;
output.Enqueue(Token.Dedent(new Location(_reader.Source, startLocation.Line, last + 1, final + 1)));
}
if (current != last)
{
throw new SyntaxError("Inconsistent indentation", last > 0 ? startLocation | last + 1 : startLocation);
}
}
/// <summary>
/// Locks the indenter to enforce consistent indentation.
/// </summary>
public void Locking()
{
Assert.IsTrue(_reader.IsAtLineStart(), $"Expected start of line, column {_reader.Column}.");
var startLocation = _reader.GetLocation();
var peek = _levels.Peek();
int current = _state is IndenterState.Locked ? _reader.ReadIndentLevel(peek) : _reader.ReadIndentLevel();
switch (_state)
{
case IndenterState.Default:
_levels.Push(current);
_state = IndenterState.Locked;
break;
case IndenterState.Locked when current != peek:
throw new SyntaxError("Inconsistent indentation", startLocation | _reader);
case IndenterState.Locked:
break;
default:
throw new InvalidOperationException($"Unknown indenter state: {_state}.");
}
}
/// <summary>
/// Empties all indentation levels, generating dedent tokens as needed.
/// </summary>
public void Empty(Queue<Token> output)
{
if (_state is IndenterState.Locked)
{
Unlock();
}
while (_levels.Count > 1)
{
_levels.Pop();
output.Enqueue(Token.Dedent(_reader.GetLocation()));
}
}
/// <summary>
/// Unlocks the indenter from locked state.
/// </summary>
private void Unlock()
{
Assert.IsTrue(_state is IndenterState.Locked, "Indenter is not locked.");
_state = IndenterState.Default;
_levels.Pop();
}
}
}