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] 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%** |