Skip to content

Commit

Permalink
feat(binder): add new simplified binder
Browse files Browse the repository at this point in the history
  • Loading branch information
kthompson committed May 31, 2024
1 parent f6b6e77 commit a0c7eff
Show file tree
Hide file tree
Showing 12 changed files with 640 additions and 13 deletions.
128 changes: 128 additions & 0 deletions src/Panther/CodeAnalysis/Binder/Binder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Panther.CodeAnalysis.Syntax;
using Panther.CodeAnalysis.Text;

namespace Panther.CodeAnalysis.Binder;

public class Binder : SyntaxVisitor
{
private readonly DiagnosticBag _diagnostics;
private Symbol _symbolTable;

private Binder(Symbol symbolTable, DiagnosticBag diagnostics)
{
_diagnostics = diagnostics;
_symbolTable = symbolTable;
}

public static (ImmutableArray<Diagnostic> diagnostics, Symbol symbolTable) Bind(
ImmutableArray<SyntaxTree> syntaxTrees
) => Bind(Symbol.NewRoot(), syntaxTrees);

public static (ImmutableArray<Diagnostic> diagnostics, Symbol symbolTable) Bind(
Symbol symbolTable,
ImmutableArray<SyntaxTree> syntaxTrees
)
{
var diagnostics = new DiagnosticBag();

foreach (var tree in syntaxTrees)
{
var binder = new Binder(symbolTable, diagnostics);
binder.Visit(tree.Root);
}

return (diagnostics.ToImmutableArray(), symbolTable);
}

protected override void DefaultVisit(SyntaxNode node)
{
foreach (
var child in node.GetChildren().Where(child => child.Kind > SyntaxKind.ExpressionMarker)
)
Visit(child);
}

private void VisitContainer(SyntaxNode node, SymbolFlags symbolFlags, SyntaxToken identifier)
{
VisitContainer(node, symbolFlags, identifier.Text, identifier.Location);
}

private void VisitContainer(
SyntaxNode node,
SymbolFlags symbolFlags,
string name,
TextLocation location
)
{
var (scope, existing) = _symbolTable.DeclareSymbol(name, symbolFlags, location);
if (existing)
{
_diagnostics.ReportDuplicateSymbol(name, scope.Location, location);
}

var current = _symbolTable;

_symbolTable = scope;

this.DefaultVisit(node);

_symbolTable = current;
}

private void DeclareSymbol(SyntaxToken identifier, SymbolFlags symbolFlags)
{
var name = identifier.Text;
var location = identifier.Location;
var existing = _symbolTable.Lookup(name);
if (existing is not null)
{
_diagnostics.ReportDuplicateSymbol(name, existing.Location, location);
}

_symbolTable.DeclareSymbol(name, symbolFlags, location);
}

public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
{
VisitContainer(node, SymbolFlags.Namespace, node.Name.ToText(), node.Name.Location);
}

public override void VisitClassDeclaration(ClassDeclarationSyntax node)
{
VisitContainer(node, SymbolFlags.Class, node.Identifier);
}

public override void VisitObjectDeclaration(ObjectDeclarationSyntax node)
{
VisitContainer(node, SymbolFlags.Object, node.Identifier);
}

public override void VisitFunctionDeclaration(FunctionDeclarationSyntax node)
{
VisitContainer(node, SymbolFlags.Method, node.Identifier);
}

public override void VisitParameter(ParameterSyntax node)
{
var flags = _symbolTable.Flags.HasFlag(SymbolFlags.Class)
? SymbolFlags.Field
: SymbolFlags.Parameter;

DeclareSymbol(node.Identifier, flags);
}

public override void VisitVariableDeclarationStatement(VariableDeclarationStatementSyntax node)
{
var flags =
_symbolTable.Flags.HasFlag(SymbolFlags.Class)
|| _symbolTable.Flags.HasFlag(SymbolFlags.Object)
? SymbolFlags.Field
: SymbolFlags.Local;

// TODO: need a way to deal with block scope for variables (or maybe we just don't have block scope for now)
DeclareSymbol(node.IdentifierToken, flags);
}
}
67 changes: 67 additions & 0 deletions src/Panther/CodeAnalysis/Binder/Symbol.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.Collections;
using System.Collections.Generic;
using Panther.CodeAnalysis.Text;

namespace Panther.CodeAnalysis.Binder;

public class Symbol(string name, SymbolFlags flags, TextLocation location, Symbol? parent)
: IEnumerable<Symbol>
{
private Dictionary<string, Symbol>? _symbols;
private List<Symbol>? _symbolList;

public static Symbol NewRoot() => new("", SymbolFlags.None, TextLocation.None, null);

public string Name => name;
public SymbolFlags Flags => flags;
public TextLocation Location => location;
public Symbol? Parent => parent;

public string FullName
{
get
{
var parentName = Parent?.FullName;
return string.IsNullOrEmpty(parentName) ? Name : $"{parentName}.{Name}";
}
}


public (Symbol, bool existing) DeclareClass(string name, TextLocation location) =>
DeclareSymbol(name, SymbolFlags.Class, location);

public (Symbol, bool existing) DeclareField(string name, TextLocation location) =>
DeclareSymbol(name, SymbolFlags.Field, location);

public (Symbol, bool existing) DeclareMethod(string name, TextLocation location) =>
DeclareSymbol(name, SymbolFlags.Method, location);

public (Symbol, bool existing) DeclareSymbol(string name, SymbolFlags flags, TextLocation location)
{
_symbols ??= new();
_symbolList ??= new();
var symbol = new Symbol(name, flags, location, this);
var existing = !_symbols.TryAdd(name, symbol);

if(!existing) _symbolList.Add(symbol);

return (existing ? _symbols[name] : symbol, existing);
}

public Symbol? Lookup(string name, bool includeParents = true) =>
_symbols?.GetValueOrDefault(name) ?? this.Parent?.Lookup(name, includeParents);

public IEnumerator<Symbol> GetEnumerator()
{
if(_symbolList == null)
yield break;

foreach (var symbol in _symbolList)
yield return symbol;
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
24 changes: 24 additions & 0 deletions src/Panther/CodeAnalysis/Binder/SymbolFlags.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

namespace Panther.CodeAnalysis.Binder;

[Flags]
public enum SymbolFlags
{
None = 0,

// Symbol type
Namespace = 1,
Object = 1 << 1,
Class = 1 << 2,
Method = 1 << 3,
Field = 1 << 4,
Property = 1 << 5,
Parameter = 1 << 6,
Local = 1 << 7,

// Symbol Access
Private = None, // may not use this but should eventually be the default access level
Protected = 1 << 8,
Public = 1 << 9,
}
18 changes: 9 additions & 9 deletions src/Panther/CodeAnalysis/Compilation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,23 +98,23 @@ public static Compilation CreateScript(
params SyntaxTree[] syntaxTrees
) => new Compilation(references, isScript: true, previous, syntaxTrees);

public IEnumerable<Symbol> GetSymbols()
public IEnumerable<Binder.Symbol> GetSymbols()
{
var compilation = this;
var symbolNames = new HashSet<string>();

while (compilation != null)
{
foreach (
var type in compilation.RootSymbol.Types.Where(type => symbolNames.Add(type.Name))
)
var (_, symbolTable) = Binder.Binder.Bind(compilation.SyntaxTrees);

foreach (var type in symbolTable.Where(type => symbolNames.Add(type.FullName)))
{
yield return type;

foreach (var member in type.Members)
{
yield return member;
}
//
// foreach (var member in type.Members)
// {
// yield return member;
// }
}

compilation = compilation.Previous;
Expand Down
10 changes: 10 additions & 0 deletions src/Panther/CodeAnalysis/DiagnosticBag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -289,4 +289,14 @@ public void ReportGenericTypeNotSupported(TextLocation location, string typeName

public void ReportNoThisInScope(TextLocation location, string scopeName) =>
Report(location, $"`this` keyword not valid in {scopeName} scope");

public void ReportDuplicateSymbol(
string name,
TextLocation existing,
TextLocation newIdentifier
)
{
Report(newIdentifier, $"A symbol with the name '{name}' already exists in this scope");
Report(existing, $"A symbol with the name '{name}' already exists in this scope");
}
}
38 changes: 38 additions & 0 deletions src/Panther/CodeAnalysis/Symbols/SymbolPrinter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,44 @@ public static void WriteTo(this Type symbol, TextWriter writer)
}
}

public static void WriteTo(this Binder.Symbol symbol, TextWriter writer)
{
if (symbol.Flags.HasFlag(Binder.SymbolFlags.Method))
{
writer.WriteKeyword("def ");
writer.WriteIdentifier(symbol.Name);
writer.WritePunctuation("(");
using var enumerator = symbol.GetEnumerator();
if (enumerator.MoveNext())
{
enumerator.Current.WriteTo(writer);

while (enumerator.MoveNext())
{
writer.WritePunctuation(", ");
enumerator.Current.WriteTo(writer);
}
}
writer.WritePunctuation(")");
}
else if (
symbol.Flags.HasFlag(Binder.SymbolFlags.Field)
|| symbol.Flags.HasFlag(Binder.SymbolFlags.Local)
)
{
writer.WriteKeyword("val ");
writer.WriteIdentifier(symbol.Name);
}
else if (symbol.Flags.HasFlag(Binder.SymbolFlags.Parameter))
{
writer.WriteIdentifier(symbol.Name);
}
else
{
throw new NotImplementedException();
}
}

public static void WriteTo(this Symbol symbol, TextWriter writer)
{
switch (symbol)
Expand Down
2 changes: 2 additions & 0 deletions src/Panther/CodeAnalysis/Syntax/SyntaxKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ public enum SyntaxKind
CloseBracketToken,

// Expressions
ExpressionMarker, // marker for SyntaxKinds that are more complicated than tokens and keywords

ArrayCreationExpression,
AssignmentExpression,
BinaryExpression,
Expand Down
14 changes: 10 additions & 4 deletions src/Panther/Repl/PantherRepl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
using Mono.Cecil;
using Panther.CodeAnalysis;
using Panther.CodeAnalysis.Authoring;
using Panther.CodeAnalysis.Symbols;
using Panther.CodeAnalysis.Syntax;
using Panther.CodeAnalysis.Text;
using Panther.IO;
using SymbolFlags = Panther.CodeAnalysis.Binder.SymbolFlags;

namespace Panther.Repl;

Expand Down Expand Up @@ -150,7 +152,11 @@ private void MetaDumpSymbols()
if (_previous == null)
return;

foreach (var symbol in _previous.GetSymbols())
foreach (
var symbol in _previous
.GetSymbols()
.Where(sym => !sym.Flags.HasFlag(SymbolFlags.Parameter))
)
{
symbol.WriteTo(Console.Out);
Console.WriteLine();
Expand All @@ -166,16 +172,16 @@ private void MetaDumpFunction(string functionName)

var function = _previous
.GetSymbols()
.Where(m => m.IsMethod)
.FirstOrDefault(func => func.Name == functionName);
.Where(m => m.Flags.HasFlag(SymbolFlags.Method))
.FirstOrDefault(func => func.FullName == functionName);

if (function == null)
{
WriteError($"Function {functionName} not found.");
return;
}

_previous.EmitTree(function, Console.Out);
// _previous.EmitTree(function, Console.Out);
}

protected override bool IsCompleteSubmission(string text)
Expand Down
Loading

0 comments on commit a0c7eff

Please sign in to comment.