Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ and this project adheres _loosely_ to [Semantic Versioning](https://semver.org/s
* Bugfix: `DeltaDeserializerHandler` is made public. This handler enables deserializer to accept node id that appears both in received delta(s) and local nodes. In the context of delta protocol, this enables replacing a node in a model with a new node with the same id, which results in a valid model.
* `a.InsertBefore(b)` / `a.InsertAfter(b)` work correctly if both `a` and `b` are siblings.
### Changed
* Make formatting optional during persisting generated classes.
* Escape feature names that have the same name as an implementing type during generation.
* Don't generate current namespace as fully qualified name.
* Add `global::` prefix to `using` statements to support generating languages that contain `LionWeb` in their namespace.
* Generator escapes user language names conflicting with framework names.
* Use `IReferenceTarget` both as return and parameter type of `IDeserializerHandler.UnresolvableReferenceTarget()`.
* Factored out mutation code from generated classes into base class methods
Simplifies changes and reduces the amount of generated code
Expand Down
9 changes: 8 additions & 1 deletion build/LionWeb-CSharp-Build/Generate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@
var generalNodeLang = testLanguagesDefinitions.GeneralNodeLang;
var lowerCaseLang = testLanguagesDefinitions.LowerCaseLang;
var upperCaseLang = testLanguagesDefinitions.UpperCaseLang;
var namespaceStartsWithLionWebLang = testLanguagesDefinitions.NamespaceStartsWithLionWebLang;
var namespaceContainsLionWebLang = testLanguagesDefinitions.NamespaceContainsLionWebLang;
var languageWithLionWebNamedConcepts = testLanguagesDefinitions.LanguageWithLionWebNamedConcepts;
var featureSameNameAsContainingConcept = testLanguagesDefinitions.FeatureSameNameAsContainingConcept;

var lionWebVersionNamespace = "V" + lionWebVersion.VersionString.Replace('.', '_');
string prefix = $"LionWeb.Core.Test.Languages.Generated.{lionWebVersionNamespace}";
Expand All @@ -90,6 +94,10 @@
new(generalNodeLang, $"{prefix}.GeneralNodeLang"),
new(testLanguage, $"{prefix}.TestLanguage"),
new(nullableReferencesTestLanguage, $"{prefix}.NullableReferencesTestLang"),
new(namespaceStartsWithLionWebLang, $"LionWeb.Generated.{lionWebVersionNamespace}.namespaceStartsWithLionWebLang"),
new(namespaceContainsLionWebLang, $"io.LionWeb.Generated.{lionWebVersionNamespace}.namespaceStartsWithLionWebLang"),
new(languageWithLionWebNamedConcepts, $"{prefix}.LanguageWithLionWebNamedConcepts"),
new(featureSameNameAsContainingConcept, $"{prefix}.FeatureSameNameAsContainingConcept"),
// We don't really want these file in tests project, but update the version in Generator.
// However, it's not worth writing a separate code path for this one language (as we want to externalize it anyways).
// Step 3: Uncomment the line below
Expand Down Expand Up @@ -121,7 +129,6 @@
{
NamespaceMappings = testLanguagesDefinitions
.MixedLangs
.Except([l])
.Select(n => (n, $"{prefix}.Mixed.{n.Name}"))
.ToDictionary()
}
Expand Down
271 changes: 271 additions & 0 deletions build/LionWeb-CSharp-Build/TestLanguagesDefinitions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
using LionWeb.Core;
using LionWeb.Core.M2;
using LionWeb.Core.M3;
using LionWeb.Core.Utilities;
using System.Reflection;

// ReSharper disable SuggestVarOrType_SimpleTypes

Expand All @@ -38,6 +40,11 @@ public class TestLanguagesDefinitions
public Language GeneralNodeLang { get; private set; }
public Language? LowerCaseLang { get; private set; }
public Language? UpperCaseLang { get; private set; }
public Language NamespaceStartsWithLionWebLang { get; private set; }
public Language NamespaceContainsLionWebLang { get; private set; }
public Language LanguageWithLionWebNamedConcepts { get; private set; }
public Language FeatureSameNameAsContainingConcept { get; private set; }

public List<Language> MixedLangs { get; private set; } = [];

public TestLanguagesDefinitions(LionWebVersions lionWebVersion)
Expand All @@ -49,6 +56,10 @@ public TestLanguagesDefinitions(LionWebVersions lionWebVersion)
CreateMultiInheritLang();
CreateNamedLang();
CreateGeneralNodeLang();
CreateNamespaceStartsWithLionWebLang();
CreateNamespaceContainsLionWebLang();
CreateLanguageWithLionWebNamedConcepts();
CreateFeatureSameNameAsContainingConcept();

if (lionWebVersion.LionCore is ILionCoreLanguageWithStructuredDataType)
{
Expand Down Expand Up @@ -519,4 +530,264 @@ private void CreateUpperCaseLang()

UpperCaseLang = casingLang;
}

private void CreateNamespaceStartsWithLionWebLang()
{
var lwLang = new DynamicLanguage("id-NamespaceStartsWithLionWebLang-lang", _lionWebVersion)
{
Name = "NamespaceStartsWithLionWebLang", Key = "key-NamespaceStartsWithLionWebLang", Version = "1"
};

lwLang.Concept("id-concept", "key-concept", "MYConcept");

NamespaceStartsWithLionWebLang = lwLang;
}

private void CreateNamespaceContainsLionWebLang()
{
var lwLang = new DynamicLanguage("id-NamespaceContainsLionWebLang-lang", _lionWebVersion)
{
Name = "NamespaceContainsLionWebLang", Key = "key-NamespaceContainsLionWebLang", Version = "1"
};

lwLang.Concept("id-concept", "key-concept", "MYConcept");

NamespaceContainsLionWebLang = lwLang;
}

private void CreateLanguageWithLionWebNamedConcepts()
{
var lang = new DynamicLanguage("id-LanguageWithLionWebNamedConcepts-lang", _lionWebVersion)
{
Name = "LanguageWithLionWebNamedConcepts", Key = "key-LanguageWithLionWebNamedConcepts", Version = "1"
};

BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

HashSet<string> conceptNames =
[
// M3
"Language",
"EnumerationLiteral",
"Link",
"Containment",
"Reference",
"Property",
"Field",
"LanguageEntity",
"Classifier",
"Annotation",
"Concept",
"Interface",
"Datatype",
"Enumeration",
"PrimitiveType",
"StructuredDataType",

// M3 base
"LanguageBase",
"EnumerationLiteralBase",
"ContainmentBase",
"ReferenceBase",
"PropertyBase",
"FieldBase",
"AnnotationBase",
"ConceptBase",
"InterfaceBase",
"DatatypeBase",
"EnumerationBase",
"PrimitiveTypeBase",
"StructuredDataTypeBase",

// Framework
"Abstract",
"AbstractBaseNodeFactory",
"AddChildRaw",
"AddContainmentsRaw",
"AddInternal",
"AddOptionalMultipleContainment",
"AddOptionalMultipleReference",
"AddReferencesRaw",
"AddRequiredMultipleContainment",
"AddRequiredMultipleReference",
"AnnotatesLazy",
"AnnotationInstanceBase",
"ArgumentOutOfRangeException",
"Bool",
"Boolean",
// "boolean",
"Char",
"Character",
"CodeAnalysis",
"CollectAllSetFeatures",
"Collections",
"ConceptInstanceBase",
"Core",
"Decimal",
"DetachChild",
"Diagnostics",
"Enum",
"ExchangeChildRaw",
"ExchangeChildrenRaw",
"FeaturesLazy",
"FieldsLazy",
"Generic",
"GetAnnotation",
"GetConcept",
"GetContainmentOf",
"GetInternal",
"IEnumerable",
"IFieldValues",
"INamed",
"INamedWritable",
"INode",
"INodeFactory",
"IPartitionInstance",
"IReadOnlyList",
"IReadableNode",
"IStructuredDataTypeInstance",
"IWritableNode",
"ImplementsLazy",
"InsertChildRaw",
"InsertInternal",
"InsertOptionalMultipleContainment",
"InsertOptionalMultipleReference",
"InsertReferencesRaw",
"InsertRequiredMultipleContainment",
"InsertRequiredMultipleReference",
"Instance",
"Integer",
// "integer",
"InvalidValueException",
"Key",
"Lazy",
"LionCoreFeature",
"LionCoreFeatureKind",
"LionCoreLanguage",
"LionCoreMetaPointer",
"LionWeb",
"LionWebVersions",
"List",
"LiteralsLazy",
"M2",
"M3",
"Multiple",
"Name",
// "_name",
"NotNullWhenAttribute",
"Notification",
"Optional",
"Partition",
"Pipe",
"ReferenceEquals",
"RemoveChildRaw",
"RemoveInternal",
"RemoveOptionalMultipleContainment",
"RemoveOptionalMultipleReference",
"RemoveReferencesRaw",
"RemoveRequiredMultipleContainment",
"RemoveRequiredMultipleReference",
"RemoveSelfParentRaw",
"SetContainmentRaw",
"SetInternal",
"SetName",
"SetNameRaw",
"SetOptionalMultipleContainment",
"SetOptionalMultipleReference",
"SetOptionalSingleContainment",
"SetOptionalSingleReference",
"SetPropertyRaw",
"SetReferenceRaw",
"SetReferencesRaw",
"SetRequiredMultipleContainment",
"SetRequiredMultipleReference",
"SetRequiredReferenceTypeProperty",
"SetRequiredSingleContainment",
"SetRequiredSingleReference",
"SetRequiredValueTypeProperty",
"String",
"System",
"TryGetContainmentRaw",
"TryGetContainmentsRaw",
"TryGetName",
"TryGetPropertyRaw",
"TryGetReferenceRaw",
"TryGetReferencesRaw",
"Type",
"UnsetFeatureException",
"UnsetFieldException",
"UnsupportedClassifierException",
"UnsupportedEnumerationLiteralException",
"UnsupportedStructuredDataTypeException",
"Utilities",
"V2023_1",
"V2024_1",
"V2024_1_Compatible",
"V2025_1",
"V2025_1_Compatible",
"VersionSpecific",
"_builtIns",
"result",
// "v2023_1",
// "v2024_1",
// "v2024_1_Compatible",
// "v2025_1",
// "v2025_1_Compatible",
..typeof(ReadableNodeBase<>).GetMembers(bindingFlags)
.Concat(typeof(NodeBase).GetMembers(bindingFlags))
.OfType<MethodInfo>()
.Select(i => i.Name)
.Where(n => !n.Contains('.'))
.Where(IdUtils.IsValid)
];

foreach (var conceptName in conceptNames)
{
lang.Concept(conceptName);
}

LanguageWithLionWebNamedConcepts = lang;
}

private void CreateFeatureSameNameAsContainingConcept()
{
var lang = new DynamicLanguage("id-FeatureSameNameAsContainingConcept-lang", _lionWebVersion)
{
Name = "FeatureSameNameAsContainingConcept", Key = "key-FeatureSameNameAsContainingConcept", Version = "1"
};

var baseConceptA = lang.Concept("BaseConceptA").IsAbstract();
baseConceptA.Property("BaseConceptA");
lang.Concept("SubConceptA").Extending(baseConceptA);

var baseConceptB = lang.Concept("BaseConceptB").IsAbstract();
baseConceptB.Property("SubConceptB");
lang.Concept("SubConceptB").Extending(baseConceptB);

var iface = lang.Interface("Iface");
iface.Property("IfaceMethod");
var ifaceImplA = lang.Concept("IfaceImplA").Implementing(iface);
lang.Concept("IfaceImplB").Implementing(iface);
var ifaceMethodConcept = lang.Concept("IfaceMethod").Extending(ifaceImplA);

var baseConceptC = lang.Concept("BaseConceptC").IsAbstract();
baseConceptC.Property("BaseConceptD");

var baseConceptD = lang.Concept("BaseConceptD").IsAbstract();
baseConceptD.Property("BaseConceptE");

FeatureSameNameAsContainingConcept = lang;
}
}

internal static class LanguageExtensions
{
internal static DynamicConcept Concept(this DynamicLanguage language, string name) =>
language.Concept($"id-{name}", $"key-{name}", name);

internal static DynamicInterface Interface(this DynamicLanguage language, string name) =>
language.Interface($"id-{name}", $"key-{name}", name);

internal static DynamicProperty Property(this DynamicClassifier classifier, string name) =>
classifier.Property($"id-{name}", $"key-{name}", name).OfType(classifier.GetLanguage().LionWebVersion.BuiltIns.String);
}
16 changes: 9 additions & 7 deletions src/LionWeb.Generator/GeneratorFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public void Persist(string path) =>
Persist(path, Generate());

/// Stores the output of <paramref name="compilationUnit"/> to the file at <paramref name="path"/>.
public void Persist(string path, CompilationUnitSyntax compilationUnit)
public void Persist(string path, CompilationUnitSyntax compilationUnit, bool formatted = true)
{
var workspace = new AdhocWorkspace();
var options = workspace.Options
Expand All @@ -87,7 +87,9 @@ public void Persist(string path, CompilationUnitSyntax compilationUnit)
.WithChangedOption(CSharpFormattingOptions.WrappingKeepStatementsOnSingleLine, value: false)
.WithChangedOption(CSharpFormattingOptions.NewLineForMembersInAnonymousTypes, value: true)
.WithChangedOption(CSharpFormattingOptions.NewLineForMembersInObjectInit, value: true);
var formattedCompilationUnit = (CompilationUnitSyntax)Formatter.Format(compilationUnit, workspace, options);
var formattedCompilationUnit = formatted
? (CompilationUnitSyntax)Formatter.Format(compilationUnit, workspace, options)
: compilationUnit;

using var streamWriter = new StreamWriter(path, false);
streamWriter.Write(formattedCompilationUnit.GetText().ToString().ReplaceLineEndings());
Expand All @@ -101,19 +103,19 @@ public void Persist(string path, CompilationUnitSyntax compilationUnit)

/// Compiles the output of <see cref="Generate"/> and returns all diagnostic messages.
public ImmutableArray<Diagnostic> Compile() =>
Compile(Generate());
Compile([Generate()]);

/// Compiles <paramref name="compilationUnit"/> and returns all diagnostic messages.
public ImmutableArray<Diagnostic> Compile(CompilationUnitSyntax compilationUnit)
/// Compiles <paramref name="compilationUnits"/> and returns all diagnostic messages.
public ImmutableArray<Diagnostic> Compile(IEnumerable<CompilationUnitSyntax> compilationUnits)
{
var tree = SyntaxTree(compilationUnit);
var trees = compilationUnits.Select(cu => SyntaxTree(cu));
var refApis = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic)
.Select(a => MetadataReference.CreateFromFile(a.Location))
.Append(MetadataReference.CreateFromFile(typeof(Stack<>).Assembly.Location))
.Append(MetadataReference.CreateFromFile(typeof(ISet<>).Assembly.Location))
.Append(MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location));
var compilation = CSharpCompilation.Create("foo", [tree], refApis);
var compilation = CSharpCompilation.Create("foo", trees, refApis);
var diagnostics = compilation.GetDiagnostics();
return diagnostics;
}
Expand Down
2 changes: 1 addition & 1 deletion src/LionWeb.Generator/Impl/DefinitionGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ private UsingDirectiveSyntax[] CollectUsings() =>
.Prepend(_builtIns.GetType().Namespace)
.Distinct()
.Order()
.Select(n => UsingDirective(ParseName(n)))
.Select(n => UsingDirective(ParseName($"global::{n}")))
.Concat(PrimitiveTypesAsUsings())
.ToArray();

Expand Down
Loading
Loading