Menu

[r4761]: / branches / parsnip / Src / Main / CS.SourceCode.Languages.Persist.pas  Maximize  Restore  History

Download this file

356 lines (325 with data), 10.5 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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
{
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/
*
* Copyright (C) 2013, Peter Johnson (www.delphidabbler.com).
*
* $Rev$
* $Date$
*
* Class that can store and load source code languages.
}
unit CS.SourceCode.Languages.Persist;
interface
uses
SysUtils,
Generics.Collections,
CS.SourceCode.Languages,
UBaseObjects,
UExceptions,
UIStringList;
type
TSourceCodeLanguagesIO = class(TNoConstructObject)
strict private
type
{ TODO: There is a lot code here that is common with
TSyntaxHiliteThemesParser - pull out into base class, possibly in
a CS.Config.SimpleLineBasedParser or similar unit. }
TParser = class(TObject)
strict private
type
/// <summary>Type of list used to record languages read from file.
/// </summary>
TLangList = TList<TSourceCodeLanguage>;
var
/// <summary>Lines of language "file".</summary>
fLines: IStringList;
/// <summary>Index or cursor of line being processed.</summary>
fLineIdx: Integer;
/// <summary>Initialises parser ready to start parsing the given
/// language "source code".</summary>
procedure Init(const Source: string);
/// <summary>Moves line cursor to next line of code, skipping blank and
/// comment lines.</summary>
procedure NextLine;
/// <summary>Returns line currently being processed or empty string if
/// all lines have been processed.</summary>
function CurrentLine: string;
function CurrentStatement: string;
function CurrentParameter: string;
procedure ValidateLanguageIdent(const Ident: string;
Existing: IStringList);
procedure ParseWatermark;
public
constructor Create;
procedure Parse(const Content: string; const IsBuiltIn: Boolean;
const Langs: TSourceCodeLanguages);
procedure ParseLanguages(const LangList: TLangList;
const IsBuiltIn: Boolean);
procedure ParseLanguage(var Language: TSourceCodeLanguage);
end;
strict private
class procedure InternalLoad(const Content: string;
const IsBuiltIn: Boolean; const Langs: TSourceCodeLanguages);
public
class procedure Save(const Langs: TSourceCodeLanguages;
const FileName: string);
class procedure Load(const Langs: TSourceCodeLanguages;
const FileName: string);
class procedure LoadFromResources(const Langs: TSourceCodeLanguages;
const ResName: string; const ResType: PChar);
end;
ESourceCodeLanguagesIO = class(ECodeSnip);
implementation
uses
// Delphi
Classes,
Character,
IOUtils,
// Project
CS.SourceCode.Hiliter.Brushes,
UConsts,
UEncodings,
UIOUtils,
UResourceUtils,
UStructs,
UStrUtils;
const
/// <summary>Watermark that is present on the first line of a valid
/// languages file.</summary>
Watermark = #$25BA + ' CodeSnip Source Code Languages v1 ' + #$25C4;
KwdLanguage = 'Language';
KwdTabSize = 'TabSize';
KwdBrush = 'Brush';
LanguageKwds: array[0..1] of string = (KwdTabSize, KwdBrush);
resourcestring
sBadWatermark = 'Invalid or missing watermark line';
sMissingLangStatement = 'Expected a LANGUAGE statememt';
sMissingID = 'Identifier expected in %s';
sBadID = 'Invalid identifier "%0:s" encountered in %1:s';
sDuplicateID = 'Duplicate identifier "%0:s" encountered in %1:s';
sMissingTabSize = 'Missing tab size parameter in TABSIZE statement';
sBadTabSize = 'Invalid tab size "%s" in TABSIZE statement';
sMissingBrushID = 'Missing brush ID in BRUSH statement';
sBadBrushID = 'Invalid brush ID "%s" in BRUSH statement';
{ TSourceCodeLanguagesIO }
class procedure TSourceCodeLanguagesIO.InternalLoad(const Content: string;
const IsBuiltIn: Boolean; const Langs: TSourceCodeLanguages);
var
Parser: TParser;
begin
Parser := TParser.Create;
try
Parser.Parse(Content, IsBuiltIn, Langs);
finally
Parser.Free;
end;
end;
class procedure TSourceCodeLanguagesIO.Load(const Langs: TSourceCodeLanguages;
const FileName: string);
begin
if not TFile.Exists(FileName, False) then
Exit;
try
InternalLoad(
TFileIO.ReadAllText(FileName, TEncoding.UTF8, True), False, Langs
);
except
on E: EStreamError do
raise ESourceCodeLanguagesIO.Create(E);
on E: EIOUtils do
raise ESourceCodeLanguagesIO.Create(E);
else
raise;
end;
end;
class procedure TSourceCodeLanguagesIO.LoadFromResources(
const Langs: TSourceCodeLanguages; const ResName: string;
const ResType: PChar);
var
Content: string;
begin
Content := LoadResourceAsString(HInstance, ResName, ResType, etUTF8, True);
InternalLoad(Content, True, Langs);
end;
class procedure TSourceCodeLanguagesIO.Save(const Langs: TSourceCodeLanguages;
const FileName: string);
var
SB: TStringBuilder;
Language: TSourceCodeLanguage;
begin
SB := TStringBuilder.Create;
try
SB.AppendLine(Watermark);
SB.AppendLine;
for Language in Langs do
begin
SB.Append(Format('%s %s', [KwdLanguage, Language.ID.ToString]));
if Language.ID.ToString <> Language.FriendlyName then
SB.Append(' ' + Language.FriendlyName);
SB.AppendLine;
SB.AppendLine(Format(' %s %d', [KwdTabSize, Language.EditorTabSize]));
SB.AppendLine(Format(' %s %s', [KwdBrush, Language.HiliterBrushID]));
end;
TDirectory.CreateDirectory(TPath.GetDirectoryName(FileName));
try
TFileIO.WriteAllText(FileName, SB.ToString, TEncoding.UTF8, True);
except
on E: EStreamError do
raise ESourceCodeLanguagesIO.Create(E);
else
raise;
end;
finally
SB.Free;
end;
end;
{ TSourceCodeLanguagesIO.TParser }
constructor TSourceCodeLanguagesIO.TParser.Create;
begin
fLines := TIStringList.Create;
Init('');
end;
function TSourceCodeLanguagesIO.TParser.CurrentLine: string;
begin
if fLineIdx < fLines.Count then
Result := fLines[fLineIdx]
else
Result := EmptyStr;
end;
function TSourceCodeLanguagesIO.TParser.CurrentParameter: string;
var
Dummy: string;
begin
StrSplit(CurrentLine, ' ', Dummy, Result);
Result := StrTrim(Result);
end;
function TSourceCodeLanguagesIO.TParser.CurrentStatement: string;
var
Dummy: string;
begin
StrSplit(CurrentLine, ' ', Result, Dummy);
Result := StrTrim(Result);
end;
procedure TSourceCodeLanguagesIO.TParser.Init(const Source: string);
begin
fLineIdx := -1;
fLines.Clear;
fLines.Add(Source, EOL, True, True);
NextLine;
end;
procedure TSourceCodeLanguagesIO.TParser.NextLine;
begin
if fLineIdx = fLines.Count then
Exit;
Inc(fLineIdx);
while (fLineIdx < fLines.Count) and
((fLines[fLineIdx] = EmptyStr) or StrStartsStr('#', fLines[fLineIdx])) do
Inc(fLineIdx);
end;
procedure TSourceCodeLanguagesIO.TParser.Parse(const Content: string;
const IsBuiltIn: Boolean; const Langs: TSourceCodeLanguages);
var
LangList: TLangList;
Lang: TSourceCodeLanguage;
begin
Init(Content);
ParseWatermark;
// We read languages into a temporary list rather than directly into Langs so
// that Langs is not modified at all should a parsing error occur. Only if
// parsing is successful do we update Langs.
LangList := TLangList.Create;
try
while CurrentStatement <> EmptyStr do
begin
if not StrSameText(CurrentStatement, KwdLanguage) then
raise ESourceCodeLanguagesIO.Create(sMissingLangStatement);
ParseLanguages(LangList, IsBuiltIn);
NextLine;
end;
for Lang in LangList do
Langs.Update(Lang);
finally
LangList.Free;
end;
end;
procedure TSourceCodeLanguagesIO.TParser.ParseLanguage(
var Language: TSourceCodeLanguage);
var
TabSize: Integer;
TabRange: TRange;
begin
while StrMatchText(CurrentStatement, LanguageKwds) do
begin
if StrSameText(CurrentStatement, KwdTabSize) then
begin
if CurrentParameter = EmptyStr then
raise ESourceCodeLanguagesIO.Create(sMissingTabSize);
if not TryStrToInt(CurrentParameter, TabSize) then
raise ESourceCodeLanguagesIO.CreateFmt(sBadTabSize, [CurrentParameter]);
TabRange := TRange.Create(1, High(Byte));
if not TabRange.Contains(TabSize) then
raise ESourceCodeLanguagesIO.CreateFmt(sBadTabSize, [CurrentParameter]);
Language.EditorTabSize := Byte(TabSize);
end
else if StrSameText(CurrentStatement, KwdBrush) then
begin
if CurrentParameter = EmptyStr then
raise ESourceCodeLanguagesIO.Create(sMissingBrushID);
if not TSyntaxHiliterBrush.IsValidBrushID(CurrentParameter) then
raise ESourceCodeLanguagesIO.CreateFmt(sBadBrushID, [CurrentParameter]);
Language.HiliterBrushID := CurrentParameter;
end;
NextLine;
end;
end;
procedure TSourceCodeLanguagesIO.TParser.ParseLanguages(
const LangList: TLangList; const IsBuiltIn: Boolean);
var
LangIDs: IStringList;
LangIDStr: string;
LangID: TSourceCodeLanguageID;
LangFriendlyName: string;
Language: TSourceCodeLanguage;
begin
LangIDs := TIStringList.Create;
while StrSameText(CurrentStatement, KwdLanguage) do
begin
StrSplit(CurrentParameter, ' ', LangIDStr, LangFriendlyName);
LangIDStr := StrTrim(LangIDStr);
ValidateLanguageIdent(LangIDStr, LangIDs);
LangID := TSourceCodeLanguageID.Create(LangIDStr);
LangIDs.Add(LangIDStr);
LangFriendlyName := StrTrim(LangFriendlyName);
if LangFriendlyName = EmptyStr then
LangFriendlyName := LangIDStr;
NextLine;
Language := TSourceCodeLanguage.Create(LangID, LangFriendlyName, IsBuiltIn);
ParseLanguage(Language);
LangList.Add(Language);
end;
end;
procedure TSourceCodeLanguagesIO.TParser.ParseWatermark;
begin
if CurrentLine <> Watermark then
raise ESourceCodeLanguagesIO.Create(sBadWatermark);
NextLine;
end;
procedure TSourceCodeLanguagesIO.TParser.ValidateLanguageIdent(
const Ident: string; Existing: IStringList);
begin
if Ident = EmptyStr then
raise ESourceCodeLanguagesIO.CreateFmt(
sMissingID, [StrToUpper(KwdLanguage)]
);
if not TSourceCodeLanguageID.IsValidIDString(Ident) then
raise ESourceCodeLanguagesIO.CreateFmt(
sBadID, [Ident, StrToUpper(KwdLanguage)]
);
if Existing.Contains(Ident) then
raise ESourceCodeLanguagesIO.CreateFmt(
sDuplicateID, [Ident, StrToUpper(KwdLanguage)]
)
end;
end.
Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.