Skip to content

Commit

Permalink
Add ability to specify existing uses/symbols for interactive documents.
Browse files Browse the repository at this point in the history
Closes #54.
  • Loading branch information
alexrp committed Mar 25, 2023
1 parent 8d76a49 commit 78248c6
Show file tree
Hide file tree
Showing 28 changed files with 202 additions and 29 deletions.
67 changes: 67 additions & 0 deletions src/language/core/Semantics/Binding/InteractiveContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
namespace Vezel.Celerity.Language.Semantics.Binding;

public sealed partial class InteractiveContext
{
public static InteractiveContext Default { get; } =
new(ImmutableDictionary<string, ModulePath>.Empty, ImmutableDictionary<string, InteractiveSymbol>.Empty);

private readonly ImmutableDictionary<string, ModulePath> _uses;

private readonly ImmutableDictionary<string, InteractiveSymbol> _symbols;

private InteractiveContext(
ImmutableDictionary<string, ModulePath> uses, ImmutableDictionary<string, InteractiveSymbol> symbols)
{
_uses = uses;
_symbols = symbols;
}

public bool TryGetUse(string name, [MaybeNullWhen(false)] out ModulePath path)
{
Check.Argument(UseRegex().IsMatch(name), name);

return _uses.TryGetValue(name, out path);
}

public InteractiveContext AddUse(string name, ModulePath path)
{
Check.Argument(UseRegex().IsMatch(name), name);
Check.Null(path);

return new(_uses.Add(name, path), _symbols);
}

public InteractiveContext RemoveUse(string name)
{
Check.Argument(UseRegex().IsMatch(name), name);

return new(_uses.Remove(name), _symbols);
}

public bool TryGetSymbol(string name, [MaybeNullWhen(false)] out InteractiveSymbol symbol)
{
Check.Argument(SymbolRegex().IsMatch(name), name);

return _symbols.TryGetValue(name, out symbol);
}

public InteractiveContext AddSymbol(string name, bool mutable)
{
Check.Argument(SymbolRegex().IsMatch(name), name);

return new(_uses, _symbols.Add(name, new(name, mutable)));
}

public InteractiveContext RemoveSymbol(string name)
{
Check.Argument(SymbolRegex().IsMatch(name), name);

return new(_uses, _symbols.Remove(name));
}

[GeneratedRegex(@"^[A-Z][a-zA-Z0-9]*$", RegexOptions.Singleline | RegexOptions.CultureInvariant)]
private static partial Regex UseRegex();

[GeneratedRegex(@"^[a-z][_0-9a-z]*$", RegexOptions.Singleline | RegexOptions.CultureInvariant)]
private static partial Regex SymbolRegex();
}
25 changes: 25 additions & 0 deletions src/language/core/Semantics/Binding/InteractiveSymbol.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Vezel.Celerity.Language.Semantics.Tree;
using Vezel.Celerity.Language.Syntax.Tree;

namespace Vezel.Celerity.Language.Semantics.Binding;

public sealed class InteractiveSymbol : LocalSymbol
{
public override bool IsMutable => _mutable;

public override bool IsDiscard => false;

private readonly bool _mutable;

internal InteractiveSymbol(string name, bool mutable)
: base(name)
{
_mutable = mutable;
}

private protected override SyntaxToken GetToken(SemanticNode node)
{
// We never have bindings, so this should never be called.
throw new UnreachableException();
}
}
15 changes: 12 additions & 3 deletions src/language/core/Semantics/LanguageAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public void Dispose()

private readonly SyntaxTree _tree;

private readonly InteractiveContext _context;

private readonly ImmutableArray<Diagnostic>.Builder _diagnostics;

private readonly Dictionary<string, (List<UseDeclarationSemantics> Declarations, ModulePath? Path)> _uses =
Expand All @@ -43,9 +45,11 @@ public void Dispose()

private Scope _scope = new(null);

public AnalysisVisitor(SyntaxTree tree, ImmutableArray<Diagnostic>.Builder diagnostics)
public AnalysisVisitor(
SyntaxTree tree, InteractiveContext context, ImmutableArray<Diagnostic>.Builder diagnostics)
{
_tree = tree;
_context = context;
_diagnostics = diagnostics;
}

Expand Down Expand Up @@ -148,6 +152,7 @@ private ScopeContext<T> PushScope<T>()
{
0 => (null, null),
1 when _uses.TryGetValue(comps[0], out var tup) => (tup.Declarations[^1], tup.Path),
1 when _context.TryGetUse(comps[0], out var p) => (null, p),
_ => (null, new(comps)),
};
}
Expand Down Expand Up @@ -964,6 +969,9 @@ public override IdentifierExpressionSemantics VisitIdentifierExpression(Identifi
{
sym = _scope.ResolveSymbol(ident.Text);

if (sym == null && _context.TryGetSymbol(ident.Text, out var sym2))
sym = sym2;

if (sym == null)
Error(
ident.Span,
Expand Down Expand Up @@ -1234,9 +1242,10 @@ public override SetPatternSemantics VisitSetPattern(SetPatternSyntax node)

private readonly AnalysisVisitor _visitor;

public LanguageAnalyzer(SyntaxTree tree, ImmutableArray<Diagnostic>.Builder diagnostics)
public LanguageAnalyzer(
SyntaxTree tree, InteractiveContext context, ImmutableArray<Diagnostic>.Builder diagnostics)
{
_visitor = new(tree, diagnostics);
_visitor = new(tree, context, diagnostics);
}

public DocumentSemantics Analyze()
Expand Down
7 changes: 5 additions & 2 deletions src/language/core/Semantics/SemanticTree.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Vezel.Celerity.Language.Diagnostics;
using Vezel.Celerity.Language.Semantics.Binding;
using Vezel.Celerity.Language.Semantics.Tree;
using Vezel.Celerity.Language.Syntax;
using Vezel.Celerity.Language.Syntax.Tree;

namespace Vezel.Celerity.Language.Semantics;

Expand All @@ -21,12 +23,13 @@ private SemanticTree(SyntaxTree syntax, DocumentSemantics root, ImmutableArray<D
root.SetParent(this);
}

public static SemanticTree Analyze(SyntaxTree syntax)
public static SemanticTree Analyze(SyntaxTree syntax, InteractiveContext? context = null)
{
Check.Null(syntax);
Check.Argument((syntax.Root, context) is (InteractiveDocumentSyntax, _) or (_, null), context);

var diags = ImmutableArray.CreateBuilder<Diagnostic>(0);
var root = new LanguageAnalyzer(syntax, diags).Analyze();
var root = new LanguageAnalyzer(syntax, context ?? InteractiveContext.Default, diags).Analyze();

diags.Sort(static (x, y) => x.Span.CompareTo(y.Span));

Expand Down
20 changes: 17 additions & 3 deletions src/tests/QualityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,28 @@
public sealed partial class QualityTests : CelerityTests
{
private static Task TestAsync(
SyntaxMode mode,
string contents, LintPass pass, [CallerFilePath] string file = "", [CallerMemberName] string name = "")
{
return TestAsync(default(InteractiveContext), contents, pass, file, name);
}

private static Task TestAsync(
Func<InteractiveContext, InteractiveContext> contextSetup,
string contents,
LintPass pass,
[CallerFilePath] string file = "",
[CallerMemberName] string name = "")
{
var syntax = SyntaxTree.Parse(new StringSourceText($"{name}.cel", contents), mode, discardText: true);
var semantics = SemanticTree.Analyze(syntax);
return TestAsync(contextSetup(InteractiveContext.Default), contents, pass, file, name);
}

private static Task TestAsync(InteractiveContext? context, string contents, LintPass pass, string file, string name)
{
var syntax = SyntaxTree.Parse(
new StringSourceText($"{name}.cel", contents),
context != null ? SyntaxMode.Interactive : SyntaxMode.Module,
discardText: true);
var semantics = SemanticTree.Analyze(syntax, context);
var analysis = LintAnalysis.Create(semantics, new[] { pass }, LintConfiguration.Default);

return VerifyDiagnosticsAsync(
Expand Down
23 changes: 20 additions & 3 deletions src/tests/SemanticTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,27 @@
public sealed partial class SemanticTests : CelerityTests
{
private static Task TestAsync(
SyntaxMode mode, string contents, [CallerFilePath] string file = "", [CallerMemberName] string name = "")
string contents, [CallerFilePath] string file = "", [CallerMemberName] string name = "")
{
var syntax = SyntaxTree.Parse(new StringSourceText($"{name}.cel", contents), mode, discardText: true);
var semantics = SemanticTree.Analyze(syntax);
return TestAsync(default(InteractiveContext), contents, file, name);
}

private static Task TestAsync(
Func<InteractiveContext, InteractiveContext> contextSetup,
string contents,
[CallerFilePath] string file = "",
[CallerMemberName] string name = "")
{
return TestAsync(contextSetup(InteractiveContext.Default), contents, file, name);
}

private static Task TestAsync(InteractiveContext? context, string contents, string file, string name)
{
var syntax = SyntaxTree.Parse(
new StringSourceText($"{name}.cel", contents),
context != null ? SyntaxMode.Interactive : SyntaxMode.Module,
discardText: true);
var semantics = SemanticTree.Analyze(syntax, context);

return VerifyDiagnosticsAsync(syntax.Diagnostics.Concat(semantics.Diagnostics), file, name);
}
Expand Down
1 change: 0 additions & 1 deletion src/tests/quality/explicitly_undocumented_module.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ public sealed partial class QualityTests
public Task explicitly_undocumented_module()
{
return TestAsync(
SyntaxMode.Module,
"""
@doc false
mod {
Expand Down
1 change: 0 additions & 1 deletion src/tests/quality/lint_severity_attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ public sealed partial class QualityTests
public Task lint_severity_attributes()
{
return TestAsync(
SyntaxMode.Module,
"""
mod {
fn code() {
Expand Down
1 change: 0 additions & 1 deletion src/tests/quality/tests_without_assert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ public sealed partial class QualityTests
public Task tests_without_assert()
{
return TestAsync(
SyntaxMode.Module,
"""
mod {
test foo {
Expand Down
1 change: 0 additions & 1 deletion src/tests/quality/undocumented_declarations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ public sealed partial class QualityTests
public Task undocumented_declarations()
{
return TestAsync(
SyntaxMode.Module,
"""
@doc "my fancy module"
mod {
Expand Down
1 change: 0 additions & 1 deletion src/tests/quality/undocumented_module.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ public sealed partial class QualityTests
public Task undocumented_module()
{
return TestAsync(
SyntaxMode.Module,
"""
mod {
}
Expand Down
1 change: 0 additions & 1 deletion src/tests/quality/unreachable_code.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ public sealed partial class QualityTests
public Task unreachable_code()
{
return TestAsync(
SyntaxMode.Module,
"""
mod {
err fn main() {
Expand Down
16 changes: 16 additions & 0 deletions src/tests/quality/unreachable_code_submission.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
public sealed partial class QualityTests
{
[Fact]
public Task unreachable_code_submission()
{
return TestAsync(
ctx => ctx,
"""
{
ret nil;
42;
};
""",
UnreachableCodePass.Instance);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Warning[unreachable-code]: Code is unreachable
--> unreachable_code_submission.cel (3,5)-(3,8)
1 | {
2 | ret nil;
3 | 42;
: ^^^
4 | };
1 change: 0 additions & 1 deletion src/tests/quality/unused_local_symbols.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ public sealed partial class QualityTests
public Task unused_local_symbols()
{
return TestAsync(
SyntaxMode.Module,
"""
mod {
const unused_const = 1;
Expand Down
1 change: 0 additions & 1 deletion src/tests/quality/uppercase_base_indicators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ public sealed partial class QualityTests
public Task uppercase_base_indicators()
{
return TestAsync(
SyntaxMode.Module,
"""
mod {
fn foo() {
Expand Down
1 change: 0 additions & 1 deletion src/tests/semantics/assignment_targets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ public sealed partial class SemanticTests
public Task assignment_targets()
{
return TestAsync(
SyntaxMode.Module,
"""
mod {
fn assign(arg) {
Expand Down
18 changes: 18 additions & 0 deletions src/tests/semantics/contextual_submissions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
public sealed partial class SemanticTests
{
[Fact]
public Task contextual_submissions()
{
return TestAsync(
ctx => ctx
.AddSymbol("mutable", mutable: true)
.AddSymbol("immutable", mutable: false),
"""
mutable = 1;
immutable = 2;
let mut immutable = 3;
immutable = 4;
nonexistent = 5;
""");
}
}
15 changes: 15 additions & 0 deletions src/tests/semantics/contextual_submissions.diags.verified.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Error[E0033]: Assignment to immutable symbol 'immutable'
--> contextual_submissions.cel (2,1)-(2,10)
1 | mutable = 1;
2 | immutable = 2;
: ^^^^^^^^^
3 | let mut immutable = 3;
4 | immutable = 4;
5 | nonexistent = 5;
Error[E0030]: Unknown symbol name 'nonexistent'
--> contextual_submissions.cel (5,1)-(5,12)
2 | immutable = 2;
3 | let mut immutable = 3;
4 | immutable = 4;
5 | nonexistent = 5;
: ^^^^^^^^^^^
1 change: 0 additions & 1 deletion src/tests/semantics/duplicate_fields.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ public sealed partial class SemanticTests
public Task duplicate_fields()
{
return TestAsync(
SyntaxMode.Module,
"""
mod {
fn foo() {
Expand Down
1 change: 0 additions & 1 deletion src/tests/semantics/duplicate_symbols.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ public sealed partial class SemanticTests
public Task duplicate_symbols()
{
return TestAsync(
SyntaxMode.Module,
"""
mod {
use Aaa = Bbb;
Expand Down
1 change: 0 additions & 1 deletion src/tests/semantics/error_handling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ public sealed partial class SemanticTests
public Task error_handling()
{
return TestAsync(
SyntaxMode.Module,
"""
mod {
fn infallible() {
Expand Down
1 change: 0 additions & 1 deletion src/tests/semantics/let_bindings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ public sealed partial class SemanticTests
public Task let_bindings()
{
return TestAsync(
SyntaxMode.Module,
"""
mod {
fn bindings(arg) {
Expand Down
Loading

0 comments on commit 78248c6

Please sign in to comment.