fix: an include in other letter case is no fault, as eu07 opens it

eu07 now finds files whatever the case of their names on Linux too
(MaSzyna-EU07/maszyna#362), so the starter no longer folds an include or a
scenery's .sbt to lower case to judge whether the simulator opens it: it
looks them up through CaseInsensitivePaths. The IncludeLetterCase fault and
its texts go, being a false alarm now, and the parser takes a plain yes or
no for each include.
This commit is contained in:
maj00r
2026-11-13 16:11:10 +01:00
parent 7f57160aae
commit 42f702a096
10 changed files with 29 additions and 55 deletions

View File

@@ -273,7 +273,7 @@ public class ScenerySyntaxTests
include koniec.scm include koniec.scm
"""; """;
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn, _ => new IncludeLookup(true)); var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn, _ => true);
Assert.Empty(scenery.Faults); Assert.Empty(scenery.Faults);
Assert.Equal("td/rozklad", Assert.Single(scenery.Trainsets).Name); Assert.Equal("td/rozklad", Assert.Single(scenery.Trainsets).Name);
@@ -326,7 +326,7 @@ public class ScenerySyntaxTests
endoptional endoptional
"""; """;
var faults = TestScenery.Parser().Parse("scenery/x.scn", scn, name => new IncludeLookup(name is "tory.scm" or "budynek.inc")).Faults; var faults = TestScenery.Parser().Parse("scenery/x.scn", scn, name => name is "tory.scm" or "budynek.inc").Faults;
Assert.Equal((SceneryFaultKind.MissingInclude, "x.scn", 2, null, "brak.scm"), Seen(Assert.Single(faults))); Assert.Equal((SceneryFaultKind.MissingInclude, "x.scn", 2, null, "brak.scm"), Seen(Assert.Single(faults)));
} }
@@ -347,9 +347,9 @@ public class ScenerySyntaxTests
} }
[Fact] [Fact]
public void An_include_on_disk_only_in_other_letter_case_is_what_linux_misses() public void An_include_on_disk_in_other_letter_case_is_no_fault()
{ {
// The simulator folds the name to lower case and opens it so. // The simulator finds the file whatever the case of its letters, on Linux too.
var files = new InMemoryFileSystem(caseSensitive: true) var files = new InMemoryFileSystem(caseSensitive: true)
.WithLegacyFile("game/scenery/braniewo_szeroki.scn", .WithLegacyFile("game/scenery/braniewo_szeroki.scn",
"include braniewo/base.ctr end\ninclude Braniewo/Tory.SCM end\n") "include braniewo/base.ctr end\ninclude Braniewo/Tory.SCM end\n")
@@ -357,21 +357,20 @@ public class ScenerySyntaxTests
.WithFile("game/scenery/braniewo/tory.scm", ""); .WithFile("game/scenery/braniewo/tory.scm", "");
var repository = TestScenery.Repository(files); var repository = TestScenery.Repository(files);
var faults = repository.Load("game/scenery/braniewo_szeroki.scn")!.Faults; Assert.Empty(repository.Load("game/scenery/braniewo_szeroki.scn")!.Faults);
Assert.Equal((SceneryFaultKind.IncludeLetterCase, "braniewo_szeroki.scn", 1, "Braniewo/Base.ctr", "braniewo/base.ctr"),
Seen(Assert.Single(faults)));
} }
[Theory] [Theory]
[InlineData(true)] [InlineData(null)]
[InlineData(false)] [InlineData("lk2_sandbox.sbt")]
public void A_missing_terrain_file_is_no_fault_while_the_scenery_has_its_own_sbt(bool sbt) [InlineData("Lk2_Sandbox.SBT")]
public void A_missing_terrain_file_is_no_fault_while_the_scenery_has_its_own_sbt(string? sbtOnDisk)
{ {
bool sbt = sbtOnDisk is not null;
var files = new InMemoryFileSystem(caseSensitive: true) var files = new InMemoryFileSystem(caseSensitive: true)
.WithLegacyFile("game/scenery/Lk2_Sandbox.scn", "include nmt/nmt100_mazowieckie_ter.scm end\nFirstInit\n"); .WithLegacyFile("game/scenery/Lk2_Sandbox.scn", "include nmt/nmt100_mazowieckie_ter.scm end\nFirstInit\n");
if (sbt) if (sbt)
files.WithFile("game/scenery/lk2_sandbox.sbt", "EU07"); files.WithFile("game/scenery/" + sbtOnDisk, "EU07");
var repository = TestScenery.Repository(files); var repository = TestScenery.Repository(files);
var scenery = repository.Load("game/scenery/Lk2_Sandbox.scn")!; var scenery = repository.Load("game/scenery/Lk2_Sandbox.scn")!;

View File

@@ -61,13 +61,6 @@ public enum SceneryFaultKind
/// <summary>An include whose file is not there; <see cref="SceneryFault.Token"/> names it.</summary> /// <summary>An include whose file is not there; <see cref="SceneryFault.Token"/> names it.</summary>
MissingInclude, MissingInclude,
/// <summary>
/// An include whose file is there only under a name in other letter case: the
/// simulator looks for it in lower case, which on Linux misses it. The
/// <see cref="SceneryFault.Subject"/> holds the name on disk.
/// </summary>
IncludeLetterCase,
/// <summary>An include with no <c>end</c>, which swallows what follows it.</summary> /// <summary>An include with no <c>end</c>, which swallows what follows it.</summary>
IncludeNotClosed, IncludeNotClosed,

View File

@@ -5,12 +5,6 @@ using System.Linq;
namespace StarterNG.Domain.Sceneries; namespace StarterNG.Domain.Sceneries;
/// <summary>
/// What the simulator makes of an include: whether it opens the file - and when
/// it does not, the file that is there under a name differing only in letter case.
/// </summary>
public readonly record struct IncludeLookup(bool Opens, string? OnDisk = null);
/// <summary> /// <summary>
/// Goes through a scenery's text for the parts the starter reads - trainsets, /// Goes through a scenery's text for the parts the starter reads - trainsets,
/// vehicles, includes - finding where each trainset and loose vehicle is, and /// vehicles, includes - finding where each trainset and loose vehicle is, and
@@ -35,17 +29,17 @@ public static class ScenerySyntax
/// <param name="text">The scenery's text, its includes put in place.</param> /// <param name="text">The scenery's text, its includes put in place.</param>
/// <param name="file">The scenery's file name, for faults in its own text.</param> /// <param name="file">The scenery's file name, for faults in its own text.</param>
/// <param name="findInclude"> /// <param name="opensInclude">
/// What the simulator makes of an include, named as the include names it; null /// Whether the simulator opens an include, named as the include names it; null
/// leaves includes unchecked. /// leaves includes unchecked.
/// </param> /// </param>
public static Result Scan(string text, string file, Func<string, IncludeLookup>? findInclude = null) public static Result Scan(string text, string file, Func<string, bool>? opensInclude = null)
{ {
string code = SceneryCode.Mask(text, out int unclosed); string code = SceneryCode.Mask(text, out int unclosed);
var tokens = SceneryCode.Tokens(code); var tokens = SceneryCode.Tokens(code);
var lines = new SceneryLines(text, file); var lines = new SceneryLines(text, file);
var simulator = new Reading(tokens, lines, findInclude, asSimulator: true); var simulator = new Reading(tokens, lines, opensInclude, asSimulator: true);
simulator.Run(); simulator.Run();
var author = new Reading(tokens, lines, null, asSimulator: false); var author = new Reading(tokens, lines, null, asSimulator: false);
author.Run(); author.Run();
@@ -116,7 +110,7 @@ public static class ScenerySyntax
} }
private sealed class Reading( private sealed class Reading(
List<SceneryCode.Token> tokens, SceneryLines lines, Func<string, IncludeLookup>? findInclude, bool asSimulator) List<SceneryCode.Token> tokens, SceneryLines lines, Func<string, bool>? opensInclude, bool asSimulator)
{ {
public readonly List<(int Start, int End, string Name)> Trainsets = new(); public readonly List<(int Start, int End, string Name)> Trainsets = new();
public readonly List<(int, int)> Loose = new(); public readonly List<(int, int)> Loose = new();
@@ -375,13 +369,8 @@ public static class ScenerySyntax
return stop < tokens.Count && tokens[stop].Is("end") ? stop : stop - 1; return stop < tokens.Count && tokens[stop].Is("end") ? stop : stop - 1;
} }
if (findInclude is not null && Plain(name) && findInclude(name) is { Opens: false } lookup) if (opensInclude is not null && Plain(name) && !opensInclude(name))
{ Fault(SceneryFaultKind.MissingInclude, token.Index, token: name);
if (lookup.OnDisk is { } onDisk)
Fault(SceneryFaultKind.IncludeLetterCase, token.Index, onDisk, name);
else
Fault(SceneryFaultKind.MissingInclude, token.Index, token: name);
}
// The simulator takes its parameters up to the word "end", written just so, // The simulator takes its parameters up to the word "end", written just so,
// whatever comes before it. // whatever comes before it.

View File

@@ -20,11 +20,11 @@ public sealed class SceneryParser
_random = random; _random = random;
} }
/// <param name="findInclude"> /// <param name="opensInclude">
/// What the simulator makes of an include, as the include names it; null leaves /// Whether the simulator opens an include, as the include names it; null leaves
/// includes unchecked. /// includes unchecked.
/// </param> /// </param>
public Scenery Parse(string path, string content, Func<string, IncludeLookup>? findInclude = null) public Scenery Parse(string path, string content, Func<string, bool>? opensInclude = null)
{ {
string original = content; string original = content;
@@ -38,7 +38,7 @@ public sealed class SceneryParser
// Read from the text with its optional includes blanked, which keeps every // Read from the text with its optional includes blanked, which keeps every
// place where it was, so a fault's line is the line in the file. // place where it was, so a fault's line is the line in the file.
var syntax = ScenerySyntax.Scan(content, System.IO.Path.GetFileName(path), findInclude); var syntax = ScenerySyntax.Scan(content, System.IO.Path.GetFileName(path), opensInclude);
var faults = syntax.Faults.ToList(); var faults = syntax.Faults.ToList();
var inlined = SceneryIncludes.Ranges(content); var inlined = SceneryIncludes.Ranges(content);

View File

@@ -80,10 +80,8 @@ public sealed class SceneryRepository : ISceneryRepository
// With its own .sbt the scenery's terrain comes from it, and the simulator // With its own .sbt the scenery's terrain comes from it, and the simulator
// passes its terrain includes over without opening them. // passes its terrain includes over without opening them.
bool binaryTerrain = Opens(Path.GetFileNameWithoutExtension(path) + ".sbt"); bool binaryTerrain = Opens(Path.GetFileNameWithoutExtension(path) + ".sbt");
IncludeLookup FindInclude(string name) => bool OpensInclude(string name) =>
Opens(name) || (binaryTerrain && SimulatorNames.Lower(name).Contains("_ter.scm")) Opens(name) || (binaryTerrain && SimulatorNames.Lower(name).Contains("_ter.scm"));
? new IncludeLookup(true)
: new IncludeLookup(false, ResolveInclude(name));
(string File, string Text)? ReadInclude(string name) (string File, string Text)? ReadInclude(string name)
{ {
@@ -100,7 +98,7 @@ public sealed class SceneryRepository : ISceneryRepository
string own = LegacyText.Decode(_files.ReadAllBytes(path), out var ownEncoding); string own = LegacyText.Decode(_files.ReadAllBytes(path), out var ownEncoding);
string text = SceneryIncludes.Inline(own, ReadInclude); string text = SceneryIncludes.Inline(own, ReadInclude);
var scenery = _parser.Parse(path, text, FindInclude); var scenery = _parser.Parse(path, text, OpensInclude);
scenery.HasCompanionTimetable = binaryTerrain; scenery.HasCompanionTimetable = binaryTerrain;
scenery.Sources = sources; scenery.Sources = sources;
scenery.TextEncoding = ownEncoding; scenery.TextEncoding = ownEncoding;
@@ -115,11 +113,11 @@ public sealed class SceneryRepository : ISceneryRepository
} }
/// <summary> /// <summary>
/// Whether the simulator opens a file of the scenery folder named so: by the /// Whether the simulator opens a file of the scenery folder named so: whatever the
/// name in lower case, which on Linux has to be how it is named on disk. /// case of its letters, on Linux too. A shade more lenient than eu07 there, which
/// folds only the letters A to Z and of two folders named alike takes the first.
/// </summary> /// </summary>
private bool Opens(string name) => private bool Opens(string name) => ResolveInclude(name) is not null;
_files.FileExists(Path.Combine(_paths.Scenery, SimulatorNames.Lower(name.Replace('\\', '/'))));
/// <summary> /// <summary>
/// An include's file as it is named on disk, from the scenery folder, whatever /// An include's file as it is named on disk, from the scenery folder, whatever

View File

@@ -742,7 +742,6 @@
<String key="SyntaxVehicleOutsideTrainset">Vozidlo {0} stojí mimo soupravu: chybí záhlaví trainset?</String> <String key="SyntaxVehicleOutsideTrainset">Vozidlo {0} stojí mimo soupravu: chybí záhlaví trainset?</String>
<String key="SyntaxNotANumber">{0}: „{1}“ místo čísla</String> <String key="SyntaxNotANumber">{0}: „{1}“ místo čísla</String>
<String key="SyntaxMissingInclude">Soubor {1} nenalezen (include)</String> <String key="SyntaxMissingInclude">Soubor {1} nenalezen (include)</String>
<String key="SyntaxIncludeLetterCase">Soubor {1} je na disku jako {0}: linuxový eu07 ho nenačte</String>
<String key="SyntaxIncludeNotClosed">include {1} bez end</String> <String key="SyntaxIncludeNotClosed">include {1} bez end</String>
<String key="SyntaxTrainsetUnreadable">Soupravu {0} se nepodařilo načíst</String> <String key="SyntaxTrainsetUnreadable">Soupravu {0} se nepodařilo načíst</String>
<String key="SyntaxVehicleUnreadable">Vozidlo {0} se nepodařilo načíst</String> <String key="SyntaxVehicleUnreadable">Vozidlo {0} se nepodařilo načíst</String>

View File

@@ -772,7 +772,6 @@
<String key="SyntaxVehicleOutsideTrainset">Vehicle {0} stands outside a trainset: header missing?</String> <String key="SyntaxVehicleOutsideTrainset">Vehicle {0} stands outside a trainset: header missing?</String>
<String key="SyntaxNotANumber">{0}: “{1}” instead of a number</String> <String key="SyntaxNotANumber">{0}: “{1}” instead of a number</String>
<String key="SyntaxMissingInclude">File {1} not found (include)</String> <String key="SyntaxMissingInclude">File {1} not found (include)</String>
<String key="SyntaxIncludeLetterCase">File {1} is on disk as {0}: the Linux eu07 will not load it</String>
<String key="SyntaxIncludeNotClosed">include {1} without end</String> <String key="SyntaxIncludeNotClosed">include {1} without end</String>
<String key="SyntaxTrainsetUnreadable">Trainset {0} could not be read</String> <String key="SyntaxTrainsetUnreadable">Trainset {0} could not be read</String>
<String key="SyntaxVehicleUnreadable">Vehicle {0} could not be read</String> <String key="SyntaxVehicleUnreadable">Vehicle {0} could not be read</String>

View File

@@ -742,7 +742,6 @@
<String key="SyntaxVehicleOutsideTrainset">A(z) {0} jármű szerelvényen kívül áll: hiányzik a trainset fejléc?</String> <String key="SyntaxVehicleOutsideTrainset">A(z) {0} jármű szerelvényen kívül áll: hiányzik a trainset fejléc?</String>
<String key="SyntaxNotANumber">{0}: „{1}” szám helyett</String> <String key="SyntaxNotANumber">{0}: „{1}” szám helyett</String>
<String key="SyntaxMissingInclude">A(z) {1} fájl nem található (include)</String> <String key="SyntaxMissingInclude">A(z) {1} fájl nem található (include)</String>
<String key="SyntaxIncludeLetterCase">A(z) {1} fájl a lemezen {0} néven van: a linuxos eu07 nem tölti be</String>
<String key="SyntaxIncludeNotClosed">include {1} end nélkül</String> <String key="SyntaxIncludeNotClosed">include {1} end nélkül</String>
<String key="SyntaxTrainsetUnreadable">A(z) {0} szerelvényt nem sikerült beolvasni</String> <String key="SyntaxTrainsetUnreadable">A(z) {0} szerelvényt nem sikerült beolvasni</String>
<String key="SyntaxVehicleUnreadable">A(z) {0} járművet nem sikerült beolvasni</String> <String key="SyntaxVehicleUnreadable">A(z) {0} járművet nem sikerült beolvasni</String>

View File

@@ -769,7 +769,6 @@
<String key="SyntaxVehicleOutsideTrainset">Pojazd {0} stoi poza składem: brak nagłówka trainset?</String> <String key="SyntaxVehicleOutsideTrainset">Pojazd {0} stoi poza składem: brak nagłówka trainset?</String>
<String key="SyntaxNotANumber">{0}: „{1}” zamiast liczby</String> <String key="SyntaxNotANumber">{0}: „{1}” zamiast liczby</String>
<String key="SyntaxMissingInclude">Brak pliku {1} (include)</String> <String key="SyntaxMissingInclude">Brak pliku {1} (include)</String>
<String key="SyntaxIncludeLetterCase">Plik {1} jest na dysku jako {0}: linuksowy eu07 go nie wczyta</String>
<String key="SyntaxIncludeNotClosed">include {1} bez end</String> <String key="SyntaxIncludeNotClosed">include {1} bez end</String>
<String key="SyntaxTrainsetUnreadable">Nie udało się odczytać składu {0}</String> <String key="SyntaxTrainsetUnreadable">Nie udało się odczytać składu {0}</String>
<String key="SyntaxVehicleUnreadable">Nie udało się odczytać pojazdu {0}</String> <String key="SyntaxVehicleUnreadable">Nie udało się odczytać pojazdu {0}</String>

View File

@@ -742,7 +742,6 @@
<String key="SyntaxVehicleOutsideTrainset">Транспорт {0} стоит вне состава: нет заголовка trainset?</String> <String key="SyntaxVehicleOutsideTrainset">Транспорт {0} стоит вне состава: нет заголовка trainset?</String>
<String key="SyntaxNotANumber">{0}: «{1}» вместо числа</String> <String key="SyntaxNotANumber">{0}: «{1}» вместо числа</String>
<String key="SyntaxMissingInclude">Файл {1} не найден (include)</String> <String key="SyntaxMissingInclude">Файл {1} не найден (include)</String>
<String key="SyntaxIncludeLetterCase">Файл {1} на диске назван {0}: eu07 на Linux его не загрузит</String>
<String key="SyntaxIncludeNotClosed">include {1} без end</String> <String key="SyntaxIncludeNotClosed">include {1} без end</String>
<String key="SyntaxTrainsetUnreadable">Не удалось прочитать состав {0}</String> <String key="SyntaxTrainsetUnreadable">Не удалось прочитать состав {0}</String>
<String key="SyntaxVehicleUnreadable">Не удалось прочитать единицу {0}</String> <String key="SyntaxVehicleUnreadable">Не удалось прочитать единицу {0}</String>