-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathLineGenerator.cs
More file actions
221 lines (184 loc) · 9.42 KB
/
LineGenerator.cs
File metadata and controls
221 lines (184 loc) · 9.42 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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Diz.Core.Interfaces;
using JetBrains.Annotations;
namespace Diz.LogWriter;
public class LineGenerator
{
public ILogCreatorForGenerator LogCreator { get; }
public Dictionary<string, AssemblyPartialLineGenerator> Generators { get; }
public LogCreatorLineFormatter LogCreatorLineFormatter { get; }
public LineGenerator(ILogCreatorForGenerator logCreator, string formatStr)
{
LogCreator = logCreator;
Generators = CreateAssemblyGenerators();
LogCreatorLineFormatter = new LogCreatorLineFormatter(formatStr, Generators);
}
public string[] GenerateSpecialLines(string type, int offset = -1, [CanBeNull] TokenExtraContext context = null) =>
GenerateLines(offset: offset, forceGenerateFullLine: LogCreator.Settings.GenerateFullLine, overrideFormatterName: type, context: context);
public string[] GenerateNormalLines(int offset, [CanBeNull] TokenExtraContext context = null) =>
GenerateLines(offset: offset, context: context);
// all tokens generated by a column
private class ColumnTokens
{
public LogCreatorLineFormatter.ColumnFormat ColumnFormat { get; init; }
public TokenBase[] Tokens { get; init; } = [];
}
// optional extra info that can get passed along and used by certain types of generators
public abstract record TokenExtraContext {}
public record TokenExtraContextSnes(int SnesAddress) : TokenExtraContext;
public record TokenExtraContextFilename(string Filename) : TokenExtraContext;
// this is the important thing. generate one "line" of output text from an offset.
// this runs through each "column" (like the label colum, code, comments, etc)
// and generates a bunch of tokens.
// // each column corresponds to a AssemblyPartialLineGenerator -dervied class to create the actual tokens.
// the tokens are then mashed together and turned into strings here in order to form the final output
// typically this is one line per offset but, sometimes offsets can generate multiple lines of output (like with multi-line columns).
private string[] GenerateLines(
int offset, // we could actually stuff this inside the context, but haven't for efficiency/convenience because MOST things want an offset
bool forceGenerateFullLine = true,
[CanBeNull] string overrideFormatterName = null,
[CanBeNull] TokenExtraContext context = null)
{
var columnTokens = LogCreatorLineFormatter.ColumnFormats
.Select(columnFormat => new ColumnTokens {
ColumnFormat = columnFormat,
Tokens = GenerateColumn(offset, columnFormat, overrideFormatterName, context)
})
.ToList();
var mainLineText = "";
var prependCommentsLines = new List<string>();
foreach (var columnTokensEntry in columnTokens)
{
var columnFormatType = columnTokensEntry.ColumnFormat.Value;
// special case for "labelassign"
if (overrideFormatterName == "labelassign")
{
if (columnFormatType != "code")
continue;
var uniform = columnTokensEntry.Tokens.All(x => x is TokenLabelAssign);
if (!uniform)
throw new InvalidDataException("Invalid Emit(): expected all tokens to be TokenLabelAssign here.");
return columnTokensEntry.Tokens
.OfType<TokenLabelAssign>()
.Select(token => token.Value)
.ToArray();
}
// special case for multi-line comments:
if (columnFormatType == "comment" && string.IsNullOrEmpty(overrideFormatterName))
{
var everythingIsAComment = columnTokensEntry.Tokens.All(x => x is TokenComment);
if (!everythingIsAComment)
throw new InvalidDataException("Invalid Emit(): received mix of comment and non-comment tokens.");
var prependCommentsTokens = columnTokensEntry.Tokens
.OfType<TokenComment>()
.ToList();
// if there's multiple comments for this one line, we'll prepend them all before the line.
// normally, there will only be one comment per line.
if (prependCommentsTokens.Count > 1)
{
prependCommentsLines.AddRange(
prependCommentsTokens
.Select(x => x.Value)
.ToList()
);
// don't process the rest of the line
continue;
}
}
// non-special-case comment (normal) line processing here:
var lineTokensForColumn = columnTokensEntry.Tokens
.OfType<TokenString>()
.ToList();
if (columnTokensEntry.Tokens.Length != lineTokensForColumn.Count || lineTokensForColumn.Count > 1)
throw new InvalidDataException("Invalid Emit(): can't have multiple or mismatched tokens per column here.");
var tokenString = lineTokensForColumn.FirstOrDefault();
var columnFinalText = tokenString?.Value ?? "";
var lineIsEmpty = string.IsNullOrWhiteSpace(columnFinalText); // [is this right?]
if (forceGenerateFullLine || columnFormatType == "label" || columnFormatType == "code" || lineIsEmpty)
mainLineText += columnFinalText;
}
// cleanup "main" text
mainLineText = mainLineText.TrimEnd();
mainLineText = string.IsNullOrEmpty(mainLineText) ? " " : mainLineText;
var prependComments = prependCommentsLines
.Select(comment => $"; {comment}")
.ToList();
// make the final string for this offset's generated output text here:
var result = new List<string>();
result.AddRange(prependComments);
result.Add(mainLineText);
// that's the normal stuff, let's add in a few hacky extras here that should be
// re-integrated better if we want them to be real options.
if (overrideFormatterName == null)
AppendFlagTypeToComment(ref result, offset, context);
return result.ToArray();
}
private void AppendFlagTypeToComment(ref List<string> result, int offset, TokenExtraContext context)
{
// this is kinda hacked in here. if useful, do it with tokens/etc the proper way.
// we'll append the flag type (data vs code vs unreached/etc) to each comment.
if (!LogCreator.Settings.AppendFlagTypeToComment || offset == -1)
return;
var data = LogCreator.Data;
var flag = data.GetFlag(offset);
if (flag is not (FlagType.Opcode or FlagType.Operand)) {
result[^1] += " >> " + flag;
}
}
private TokenBase[] GenerateColumn(int offset, LogCreatorLineFormatter.ColumnFormat columnFormat,
string overrideFormatterName = null, TokenExtraContext context = null)
{
if (columnFormat.IsLiteral) {
return AssemblyPartialLineGenerator.GenerateFromStr(columnFormat.Value);
}
var formatter = SelectFinalColumnFormatter(columnFormat, overrideFormatterName);
return GenerateColumnTokensFromFormatter(offset, formatter, context);
}
private TokenBase[] GenerateColumnTokensFromFormatter(int offset, LogCreatorLineFormatter.ColumnFormat columnFormat, TokenExtraContext context = null)
{
var columnGenerator = GetGeneratorFor(columnFormat.Value);
var sanitizedOffset = columnFormat.SanitizeOffset(offset);
return columnGenerator.GenerateTokens(sanitizedOffset, columnFormat.LengthOverride, context);
}
private LogCreatorLineFormatter.ColumnFormat SelectFinalColumnFormatter(LogCreatorLineFormatter.ColumnFormat columnFormat, string overrideName = null)
{
return overrideName != null
? BuildSpecialFormatterFrom(columnFormat, overrideName)
: columnFormat;
}
private LogCreatorLineFormatter.ColumnFormat BuildSpecialFormatterFrom(LogCreatorLineFormatter.ColumnFormat originalColumn, string specialModifierStr)
{
var ignoreOffset = false;
string val;
if (originalColumn.Value != "code")
{
ignoreOffset = true;
val = "%empty";
}
else
{
val = $"%{specialModifierStr}";
}
return new LogCreatorLineFormatter.ColumnFormat
{
LengthOverride = originalColumn.LengthOverride ?? GetGeneratorFor(originalColumn.Value).DefaultLength,
IgnoreOffset = ignoreOffset,
Value = val,
};
}
public AssemblyPartialLineGenerator GetGeneratorFor(string parameter)
{
if (!Generators.TryGetValue(parameter, out var generator))
throw new InvalidOperationException($"Can't find generator for {parameter}");
return generator;
}
public Dictionary<string, AssemblyPartialLineGenerator> CreateAssemblyGenerators()
{
var generators = AssemblyGeneratorRegistration.Create();
foreach (var v in generators.Values) v.LogCreator = LogCreator;
return generators;
}
}