From ec486f6d550cb55458a97a24b902b80a246f3bcd Mon Sep 17 00:00:00 2001 From: Benjamin Nitschke Date: Sat, 29 Aug 2026 05:25:29 +0200 Subject: [PATCH 1/2] Add Phase 1 Language inline tests for Keyword, Limit, TypeKind, and operators. Keep BinaryOperator as an enum (16 members) and put symbol checks in BinaryOperatorTests.strict. Conversion tests parse the new method bodies and assert Tests.Count against C# constants. --- Language/BinaryOperatorTests.strict | 28 +++++++++ Language/Keyword.strict | 32 +++++++++- Language/Limit.strict | 19 +++++- Language/TypeKind.strict | 42 ++++++++++++- Language/UnaryOperator.strict | 6 +- .../StrictLanguageConversionTests.cs | 59 ++++++++++++++++++- 6 files changed, 180 insertions(+), 6 deletions(-) create mode 100644 Language/BinaryOperatorTests.strict diff --git a/Language/BinaryOperatorTests.strict b/Language/BinaryOperatorTests.strict new file mode 100644 index 00000000..79ea752e --- /dev/null +++ b/Language/BinaryOperatorTests.strict @@ -0,0 +1,28 @@ +has logger +Run + ArithmeticSymbols is "+" + ComparisonSymbols is "<" + LogicalSymbols is "and" + logger.Log(ArithmeticSymbols) +ArithmeticSymbols Text + BinaryOperator.Plus is "+" + BinaryOperator.Minus is "-" + BinaryOperator.Multiply is "*" + BinaryOperator.Divide is "/" + BinaryOperator.Power is "^" + BinaryOperator.Modulate is "%" + BinaryOperator.Plus +ComparisonSymbols Text + BinaryOperator.Smaller is "<" + BinaryOperator.Greater is ">" + BinaryOperator.SmallerOrEqual is "<=" + BinaryOperator.GreaterOrEqual is ">=" + BinaryOperator.Is is "is" + BinaryOperator.In is "in" + BinaryOperator.Smaller +LogicalSymbols Text + BinaryOperator.And is "and" + BinaryOperator.Or is "or" + BinaryOperator.Xor is "xor" + BinaryOperator.To is "to" + BinaryOperator.And \ No newline at end of file diff --git a/Language/Keyword.strict b/Language/Keyword.strict index 16e562da..2b078f2b 100644 --- a/Language/Keyword.strict +++ b/Language/Keyword.strict @@ -6,4 +6,34 @@ constant If = "if" constant Else = "else" constant For = "for" constant With = "with" -constant Return = "return" \ No newline at end of file +constant Return = "return" +IsKeyword(name Text) Boolean + IsKeyword("has") is true + IsKeyword("mutable") is true + IsKeyword("return") is true + IsKeyword("foo") is false + IsDeclarationKeyword(name) or IsControlKeyword(name) +IsDeclarationKeyword(name Text) Boolean + IsDeclarationKeyword("mutable") is true + IsDeclarationKeyword("if") is false + if name is Has + return true + if name is Constant + return true + if name is Let + return true + if name is MutableKeyword + return true + false +IsControlKeyword(name Text) Boolean + IsControlKeyword("if") is true + IsControlKeyword("has") is false + if name is If + return true + if name is Else + return true + if name is For + return true + if (name is With) or (name is Return) + return true + false \ No newline at end of file diff --git a/Language/Limit.strict b/Language/Limit.strict index d4a21612..a7af06fe 100644 --- a/Language/Limit.strict +++ b/Language/Limit.strict @@ -8,4 +8,21 @@ constant MultiLineCharacterCount = 100 constant MemberCount = 15 constant MemberCountForEnums = 40 constant NameMaxLimit = 50 -constant NameMinLimit = 2 \ No newline at end of file +constant NameMinLimit = 2 +HasExpectedMethodLimits Boolean + MethodLength is 13 + ParameterCount is 4 + MethodCount is 15 + true +HasExpectedFileLimits Boolean + LineCount is 256 + NestingLevel is 5 + CharacterCount is 120 + MultiLineCharacterCount is 100 + true +HasExpectedNameLimits Boolean + MemberCount is 15 + MemberCountForEnums is 40 + NameMaxLimit is 50 + NameMinLimit is 2 + true \ No newline at end of file diff --git a/Language/TypeKind.strict b/Language/TypeKind.strict index aa2a7a03..557f825a 100644 --- a/Language/TypeKind.strict +++ b/Language/TypeKind.strict @@ -9,4 +9,44 @@ constant KindError constant KindEnum constant KindIterator constant KindAny -constant KindUnknown \ No newline at end of file +constant KindUnknown +NameOf(kind Number) Text + NameOf(KindNone) is "None" + NameOf(KindList) is "List" + if kind < KindList + return NameOfScalar(kind) + NameOfComposite(kind) +NameOfScalar(kind Number) Text + NameOfScalar(KindBoolean) is "Boolean" + if kind is KindNone + return "None" + if kind is KindBoolean + return "Boolean" + if kind is KindNumber + return "Number" + if kind is KindText + return "Text" + if kind is KindCharacter + return "Character" + "Unknown" +NameOfComposite(kind Number) Text + NameOfComposite(KindError) is "Error" + if kind is KindList + return "List" + if kind is KindDictionary + return "Dictionary" + if kind is KindError + return "Error" + if kind is KindEnum + return "Enum" + if kind is KindIterator + return "Iterator" + NameOfAny(kind) +NameOfAny(kind Number) Text + NameOfAny(KindAny) is "Any" + NameOfAny(KindUnknown) is "Unknown" + if kind is KindAny + return "Any" + if kind is KindUnknown + return "Unknown" + "Unknown" \ No newline at end of file diff --git a/Language/UnaryOperator.strict b/Language/UnaryOperator.strict index 39a16cf8..206714ed 100644 --- a/Language/UnaryOperator.strict +++ b/Language/UnaryOperator.strict @@ -1 +1,5 @@ -constant Not = "not" \ No newline at end of file +constant Not = "not" +IsNot(name Text) Boolean + IsNot("not") is true + IsNot("and") is false + name is Not \ No newline at end of file diff --git a/Strict.Language.Tests/StrictLanguageConversionTests.cs b/Strict.Language.Tests/StrictLanguageConversionTests.cs index fcb5c17e..0b4aabf0 100644 --- a/Strict.Language.Tests/StrictLanguageConversionTests.cs +++ b/Strict.Language.Tests/StrictLanguageConversionTests.cs @@ -86,7 +86,6 @@ public void LoadLimitTypeFromLanguageDirectory() { using var limitType = CreateLanguageType(TestPackage.Instance, "Limit"); Assert.That(limitType.Members.Count, Is.EqualTo(11)); - Assert.That(limitType.IsEnum, Is.True); Assert.That(limitType.FindMember(nameof(Limit.MethodLength))!.InitialValue!.ToString(), Is.EqualTo(Limit.MethodLength.ToString())); Assert.That(limitType.FindMember(nameof(Limit.CharacterCount))!.InitialValue!.ToString(), @@ -99,6 +98,9 @@ public void LoadLimitTypeFromLanguageDirectory() Is.EqualTo(Limit.NestingLevel.ToString())); Assert.That(limitType.FindMember(nameof(Limit.ParameterCount))!.InitialValue!.ToString(), Is.EqualTo(Limit.ParameterCount.ToString())); + AssertInlineTests(limitType, "HasExpectedMethodLimits", 3); + AssertInlineTests(limitType, "HasExpectedFileLimits", 4); + AssertInlineTests(limitType, "HasExpectedNameLimits", 4); } private static Type CreateLanguageType(Package package, string typeName) => @@ -115,12 +117,18 @@ private static string GetExpressionsPath() => Path.Combine(Repositories.GetLocalDevelopmentPath(Repositories.StrictOrg, nameof(Strict)), "Expressions"); + private static void AssertInlineTests(Type type, string methodName, int testCount) + { + var method = type.Methods.First(candidate => candidate.Name == methodName); + method.GetBodyAndParseIfNeeded(); + Assert.That(method.Tests.Count, Is.EqualTo(testCount), methodName); + } + [Test] public void LoadKeywordTypeFromLanguageDirectory() { using var keywordType = CreateLanguageType(TestPackage.Instance, "Keyword"); Assert.That(keywordType.Members.Count, Is.EqualTo(9)); - Assert.That(keywordType.IsEnum, Is.True); Assert.That(keywordType.FindMember(nameof(Keyword.Constant))!.InitialValue!.ToString(), Is.EqualTo("\"" + Keyword.Constant + "\"")); Assert.That(keywordType.FindMember(nameof(Keyword.For))!.InitialValue!.ToString(), @@ -132,6 +140,53 @@ public void LoadKeywordTypeFromLanguageDirectory() Assert.That( keywordType.FindMember(nameof(Keyword.Mutable) + "Keyword")!.InitialValue!.ToString(), Is.EqualTo("\"" + Keyword.Mutable + "\"")); + AssertInlineTests(keywordType, "IsKeyword", 4); + AssertInlineTests(keywordType, "IsDeclarationKeyword", 2); + AssertInlineTests(keywordType, "IsControlKeyword", 2); + } + + [Test] + public void LoadTypeKindFromLanguageDirectory() + { + using var typeKind = CreateLanguageType(TestPackage.Instance, "TypeKind"); + Assert.That(typeKind.Members.Count, Is.EqualTo(12)); + Assert.That(typeKind.FindMember("KindNone")!.InitialValue!.ToString(), Is.EqualTo("0")); + Assert.That(typeKind.FindMember("KindBoolean")!.InitialValue!.ToString(), + Is.EqualTo(((int)TypeKind.Boolean).ToString())); + Assert.That(typeKind.FindMember("KindUnknown")!.InitialValue!.ToString(), + Is.EqualTo(((int)TypeKind.Unknown).ToString())); + AssertInlineTests(typeKind, "NameOf", 2); + AssertInlineTests(typeKind, "NameOfScalar", 1); + AssertInlineTests(typeKind, "NameOfComposite", 1); + AssertInlineTests(typeKind, "NameOfAny", 2); + } + + [Test] + public void LoadUnaryOperatorFromLanguageDirectory() + { + using var unaryOperator = CreateLanguageType(TestPackage.Instance, "UnaryOperator"); + Assert.That(unaryOperator.Members.Count, Is.EqualTo(1)); + Assert.That(unaryOperator.FindMember(nameof(UnaryOperator.Not))!.InitialValue!.ToString(), + Is.EqualTo("\"" + UnaryOperator.Not + "\"")); + AssertInlineTests(unaryOperator, "IsNot", 2); + } + + [Test] + public void LoadBinaryOperatorFromLanguageDirectory() + { + using var package = new Package(TestPackage.Instance, "BinaryOperatorCheck"); + using var binaryOperator = CreateLanguageType(package, "BinaryOperator"); + Assert.That(binaryOperator.Members.Count, Is.EqualTo(16)); + Assert.That(binaryOperator.IsEnum, Is.True); + Assert.That(binaryOperator.FindMember(nameof(BinaryOperator.Plus))!.InitialValue!.ToString(), + Is.EqualTo("\"" + BinaryOperator.Plus + "\"")); + Assert.That(binaryOperator.FindMember(nameof(BinaryOperator.Is))!.InitialValue!.ToString(), + Is.EqualTo("\"" + BinaryOperator.Is + "\"")); + using var tests = CreateLanguageType(package, "BinaryOperatorTests"); + AssertInlineTests(tests, "Run", 3); + AssertInlineTests(tests, "ArithmeticSymbols", 6); + AssertInlineTests(tests, "ComparisonSymbols", 6); + AssertInlineTests(tests, "LogicalSymbols", 4); } [Test] From 710bdfce3ed3cda4eca1ed02deb1be4a4f6eb433 Mon Sep 17 00:00:00 2001 From: Benjamin Nitschke Date: Sat, 29 Aug 2026 05:26:42 +0200 Subject: [PATCH 2/2] Update Phase 1 conversion plan for Keyword/Limit/TypeKind/operator inline tests. --- strict-conversion-plan.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/strict-conversion-plan.md b/strict-conversion-plan.md index 224e99a8..594a49aa 100644 --- a/strict-conversion-plan.md +++ b/strict-conversion-plan.md @@ -116,11 +116,11 @@ not an auto-numbered enum value. This is the same principle as C#'s naming restr | Priority | C# File | Description | Strict equivalent plan | Status | |----------|---------|-------------|------------------------|--------| -| 1 | `Keyword.cs` | String constants for keywords | `Language/Keyword.strict` | ✅ 100% | -| 2 | `BinaryOperator.cs` | 16 operator string constants | `Language/BinaryOperator.strict` | ✅ 100% | -| 3 | `UnaryOperator.cs` | 1 unary operator constant | `Language/UnaryOperator.strict` | ✅ 100% | -| 4 | `TypeKind.cs` | Enum: None/Boolean/Number/etc. | `Language/TypeKind.strict` | ✅ 100% | -| 5 | `Limit.cs` | Size limit constants | `Language/Limit.strict` | ✅ 100% | +| 1 | `Keyword.cs` | String constants for keywords | `Language/Keyword.strict` | ✅ 100% + `IsKeyword` inline tests | +| 2 | `BinaryOperator.cs` | 16 operator string constants | `Language/BinaryOperator.strict` + `BinaryOperatorTests.strict` | ✅ 100% + inline symbol tests | +| 3 | `UnaryOperator.cs` | 1 unary operator constant | `Language/UnaryOperator.strict` | ✅ 100% + `IsNot` inline tests | +| 4 | `TypeKind.cs` | Enum: None/Boolean/Number/etc. | `Language/TypeKind.strict` | ✅ 100% + `NameOf` inline tests | +| 5 | `Limit.cs` | Size limit constants | `Language/Limit.strict` | ✅ 100% + C# value inline tests | | 6 | `TypeLines.cs` | Raw lines of a type file | `Language/TypeLines.strict` | ✅ 100% | | 7 | `NamedType.cs` | Name + Type pair | `Language/NamedType.strict` | ✅ 70% | | 8 | `NumberExtensions.cs` | Simple number helpers | Methods on Number | 🚧 Deferred | @@ -149,9 +149,9 @@ This means `has name Text` fails if a `Name` type exists — use a name that eit - Uses a name with no matching type: `has typeName Text`, `has elementName Text` **Summary of what's done vs what's next:** -- ✅ **5 pure-constant types done** (Phase 1a) — Limit, Keyword, TypeKind, UnaryOperator, BinaryOperator -- ✅ **Language package `.strict` files** — TypeLines, NamedType, Parameter, Member, Variable, Expression, ConcreteExpression, ExpressionParser, TypeParser, TypeFinder, MethodParser, Context, Package, Type, Body, Parser + constants. Root `Method.strict` is data-only (`Name`/`Type`/`Parameters`); parsing lives in `MethodParser.strict`. -- ✅ **Object-model cleanup** — Language types use `Name`/`Type` (not legacy `elementName`/`typeName`/`expressionText`). Guarded by `StrictLanguageConversionTests` (11 tests). +- ✅ **5 pure-constant types done** (Phase 1a) — Limit, Keyword, TypeKind, UnaryOperator, BinaryOperator now have inline `is` tests. `StrictLanguageConversionTests` parses those method bodies and asserts `Tests.Count` plus C# constant/ordinal parity. BinaryOperator stays an enum (16 members > `MemberCount` 15), so symbol tests live in `BinaryOperatorTests.strict`. Keyword/Limit/TypeKind/UnaryOperator gained small helper methods (`IsKeyword`, limit checks, `NameOf`, `IsNot`) and are therefore no longer classified as enums. +- ✅ **Language package `.strict` files** — TypeLines, NamedType, Parameter, Member, Variable, Expression, ConcreteExpression, ExpressionParser, TypeParser, TypeFinder, MethodParser, Context, Package, Type, Body, Parser, BinaryOperatorTests + constants. Root `Method.strict` is data-only (`Name`/`Type`/`Parameters`); parsing lives in `MethodParser.strict`. +- ✅ **Object-model cleanup** — Language types use `Name`/`Type` (not legacy `elementName`/`typeName`/`expressionText`). Guarded by `StrictLanguageConversionTests` (14 tests). - ✅ **Type.strict** — real member/method line parse under **HighLevelRuntime** (inline tests green). `Members`/`Methods` + `MethodParser.Parse` for headers/params/body span. - ✅ **MethodParser.strict** — `Parse` / `ParseBody` / parameter extraction; avoids `IndexOf("(")` via `OpenParen`/`CloseParen` constants + character scan. - ✅ **Parser.Run** — reads a real file via `File(path).ReadLines` under the **VM** (Path CLI args work after VM `File.from` Path fix). Logs path + ok when non-empty. @@ -180,8 +180,8 @@ This means `has name Text` fails if a `Name` type exists — use a name that eit | Metric | Target | Actual | % | |--------|--------|--------|---| -| `.strict` files created | 23 | 23 | 100% | -| Test methods written | 335 | 36 | 11% | +| `.strict` files created | 23 | 21 | 91% | +| Test methods written | 335 | 50 | 15% | | C# files replaced | 32 | 0 | 0% | --- @@ -508,7 +508,7 @@ This is the execution engine — the capstone of the self-hosting effort. | Phase | Project | C# Files | Target `.strict` Files | Actual `.strict` Files | Tests Written | C# % Done | |-------|---------|----------|------------------------|------------------------|---------------|-----------| | 0 | Base Types (verification) | 0 | 0 (already `.strict`) | 2 (BaseTypesTest) | 1 | 0% | -| 1 | `Strict.Language` | 32 | 22 | 20 (Limit, Keyword, TypeKind, UnaryOperator, BinaryOperator, TypeLines, NamedType, Parameter, Member, Variable, Expression, ConcreteExpression, ExpressionParser, TypeParser, TypeFinder, Method, Context, Package, Type, Body) | 28 | 27% | +| 1 | `Strict.Language` | 32 | 22 | 21 (same as before + BinaryOperatorTests; Keyword/Limit/TypeKind/UnaryOperator now have inline tests) | 50 | 27% | | 2 | `Strict.Expressions` | 29 | 29 | **32** (AST + Parser + NumberChars + demo) | ~140 + 6 C# | **~40%** | | 3 | `Strict.Validators` | 3 | 3 | **6** | ~4 C# + inline | **~40%** | | 4 | `Strict.TestRunner` | 1 | 1 | **7** | ~4 C# + inline | **~40%** |