Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding local functions #59

Closed
wants to merge 11 commits into from
4 changes: 2 additions & 2 deletions src/Minsk.Tests/CodeAnalysis/EvaluationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ public void Evaluator_Variables_Can_Shadow_Functions()
private static void AssertValue(string text, object expectedValue)
{
var syntaxTree = SyntaxTree.Parse(text);
var compilation = new Compilation(syntaxTree);
var compilation = new Compilation(syntaxTree, new CompilationOptions(SourceCodeKind.Script));
var variables = new Dictionary<VariableSymbol, object>();
var result = compilation.Evaluate(variables);

Expand All @@ -375,7 +375,7 @@ private void AssertDiagnostics(string text, string diagnosticText)
{
var annotatedText = AnnotatedText.Parse(text);
var syntaxTree = SyntaxTree.Parse(annotatedText.Text);
var compilation = new Compilation(syntaxTree);
var compilation = new Compilation(syntaxTree, new CompilationOptions(SourceCodeKind.Script));
var result = compilation.Evaluate(new Dictionary<VariableSymbol, object>());

var expectedDiagnostics = AnnotatedText.UnindentLines(diagnosticText);
Expand Down
5 changes: 2 additions & 3 deletions src/Minsk.Tests/CodeAnalysis/Syntax/ParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,8 @@ private static ExpressionSyntax ParseExpression(string text)
{
var syntaxTree = SyntaxTree.Parse(text);
var root = syntaxTree.Root;
var member = Assert.Single(root.Members);
var globalStatement = Assert.IsType<GlobalStatementSyntax>(member);
return Assert.IsType<ExpressionStatementSyntax>(globalStatement.Statement).Expression;
var statement = Assert.Single(root.Statements);
return Assert.IsType<ExpressionStatementSyntax>(statement).Expression;
}

public static IEnumerable<object[]> GetBinaryOperatorPairsData()
Expand Down
129 changes: 57 additions & 72 deletions src/Minsk/CodeAnalysis/Binding/Binder.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Diagnostics;
using Minsk.CodeAnalysis.Lowering;
using Minsk.CodeAnalysis.Symbols;
using Minsk.CodeAnalysis.Syntax;
Expand All @@ -12,16 +12,20 @@ namespace Minsk.CodeAnalysis.Binding
internal sealed class Binder
{
private readonly DiagnosticBag _diagnostics = new DiagnosticBag();
private readonly List<(FunctionSymbol function, BoundBlockStatement body)> _functionBodies = new List<(FunctionSymbol function, BoundBlockStatement body)>();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chose a list of tuples instead of a dictionary here because this collection is never actually used for lookup, it's only ever used for enumeration (which a list is more efficient at).

private readonly FunctionSymbol _function;
private readonly CompilationOptions _options;

private Stack<(BoundLabel BreakLabel, BoundLabel ContinueLabel)> _loopStack = new Stack<(BoundLabel BreakLabel, BoundLabel ContinueLabel)>();
private int _labelCounter;
private BoundScope _scope;

public Binder(BoundScope parent, FunctionSymbol function)
public Binder(BoundScope parent, FunctionSymbol function, CompilationOptions options)
{
_scope = new BoundScope(parent);
_function = function;
_options = options;

_scope = new BoundScope(parent);

if (function != null)
{
Expand All @@ -30,62 +34,38 @@ public Binder(BoundScope parent, FunctionSymbol function)
}
}

public static BoundGlobalScope BindGlobalScope(BoundGlobalScope previous, CompilationUnitSyntax syntax)
public static BoundGlobalScope BindGlobalScope(BoundGlobalScope previous, CompilationUnitSyntax syntax, CompilationOptions options)
{
var parentScope = CreateParentScope(previous);
var binder = new Binder(parentScope, function: null);

foreach (var function in syntax.Members.OfType<FunctionDeclarationSyntax>())
binder.BindFunctionDeclaration(function);

var statements = ImmutableArray.CreateBuilder<BoundStatement>();
var parentScope = previous == null ? CreateRootScope() : previous.Scope;
var binder = new Binder(parentScope, function: null, options);

foreach (var globalStatement in syntax.Members.OfType<GlobalStatementSyntax>())
{
var statement = binder.BindStatement(globalStatement.Statement);
statements.Add(statement);
}
var statements = binder.BindStatements(syntax.Statements);

var functions = binder._scope.GetDeclaredFunctions();
var variables = binder._scope.GetDeclaredVariables();
var diagnostics = binder.Diagnostics.ToImmutableArray();
var functionBodies = binder._functionBodies.ToImmutableArray();

if (previous != null)
{
diagnostics = diagnostics.InsertRange(0, previous.Diagnostics);
functionBodies = functionBodies.InsertRange(0, previous.FunctionBodies);
}

return new BoundGlobalScope(previous, diagnostics, functions, variables, statements.ToImmutable());
return new BoundGlobalScope(previous, binder._scope, functionBodies, diagnostics, statements);
}

public static BoundProgram BindProgram(BoundGlobalScope globalScope)
public static LoweredProgram LowerProgram(BoundGlobalScope globalScope)
{
var parentScope = CreateParentScope(globalScope);

var functionBodies = ImmutableDictionary.CreateBuilder<FunctionSymbol, BoundBlockStatement>();
var diagnostics = ImmutableArray.CreateBuilder<Diagnostic>();

var scope = globalScope;
var loweredFunctionBodies = ImmutableDictionary.CreateBuilder<FunctionSymbol, BoundBlockStatement>();

while (scope != null)
{
foreach (var function in scope.Functions)
{
var binder = new Binder(parentScope, function);
var body = binder.BindStatement(function.Declaration.Body);
var loweredBody = Lowerer.Lower(body);
functionBodies.Add(function, loweredBody);

diagnostics.AddRange(binder.Diagnostics);
}

scope = scope.Previous;
}
foreach (var (function, body) in globalScope.FunctionBodies)
loweredFunctionBodies.Add(function, Lowerer.Lower(body));

var statement = Lowerer.Lower(new BoundBlockStatement(globalScope.Statements));
var loweredStatement = Lowerer.Lower(new BoundBlockStatement(globalScope.Statements));

return new BoundProgram(diagnostics.ToImmutable(), functionBodies.ToImmutable(), statement);
return new LoweredProgram(globalScope.Diagnostics, loweredFunctionBodies.ToImmutable(), loweredStatement);
}

private void BindFunctionDeclaration(FunctionDeclarationSyntax syntax)
private FunctionSymbol DeclareFunction(FunctionDeclarationSyntax syntax)
{
var parameters = ImmutableArray.CreateBuilder<ParameterSymbol>();

Expand Down Expand Up @@ -114,34 +94,28 @@ private void BindFunctionDeclaration(FunctionDeclarationSyntax syntax)
var function = new FunctionSymbol(syntax.Identifier.Text, parameters.ToImmutable(), type, syntax);
if (!_scope.TryDeclareFunction(function))
_diagnostics.ReportSymbolAlreadyDeclared(syntax.Identifier.Span, function.Name);

return function;
}

private static BoundScope CreateParentScope(BoundGlobalScope previous)
private BoundStatement BindFunctionDeclaration(FunctionDeclarationSyntax syntax)
{
var stack = new Stack<BoundGlobalScope>();
while (previous != null)
{
stack.Push(previous);
previous = previous.Previous;
}
if (_function != null && _options.SourceCodeKind != SourceCodeKind.Script)
_diagnostics.XXX_ReportLocalFunctionsAreOnlySupportedInScriptMode(syntax.Identifier.Span);

var parent = CreateRootScope();
var result = _scope.TryLookupFunction(syntax.Identifier.Text, out var function);
Debug.Assert(result);

while (stack.Count > 0)
{
previous = stack.Pop();
var scope = new BoundScope(parent);
var binder = new Binder(_scope, function, _options);

foreach (var f in previous.Functions)
scope.TryDeclareFunction(f);
var body = binder.BindBlockStatement(function.Declaration.Body);
_functionBodies.Add((function, body));

foreach (var v in previous.Variables)
scope.TryDeclareVariable(v);
_diagnostics.AddRange(binder.Diagnostics);
_functionBodies.AddRange(binder._functionBodies);

parent = scope;
}

return parent;
// Function declaration is a no-op.
return new BoundBlockStatement(ImmutableArray<BoundStatement>.Empty);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, we could create some sort of a no-op bound node.

}

private static BoundScope CreateRootScope()
Expand All @@ -165,6 +139,8 @@ private BoundStatement BindStatement(StatementSyntax syntax)
{
switch (syntax.Kind)
{
case SyntaxKind.FunctionDeclaration:
return BindFunctionDeclaration((FunctionDeclarationSyntax)syntax);
case SyntaxKind.BlockStatement:
return BindBlockStatement((BlockStatementSyntax)syntax);
case SyntaxKind.VariableDeclaration:
Expand All @@ -188,20 +164,31 @@ private BoundStatement BindStatement(StatementSyntax syntax)
}
}

private BoundStatement BindBlockStatement(BlockStatementSyntax syntax)
private BoundBlockStatement BindBlockStatement(BlockStatementSyntax syntax)
{
var statements = ImmutableArray.CreateBuilder<BoundStatement>();
_scope = new BoundScope(_scope);

foreach (var statementSyntax in syntax.Statements)
var statements = BindStatements(syntax.Statements);

_scope = _scope.Parent;

return new BoundBlockStatement(statements);
}

private ImmutableArray<BoundStatement> BindStatements(ImmutableArray<StatementSyntax> syntax)
{
var statements = ImmutableArray.CreateBuilder<BoundStatement>();

foreach (var functionDeclarationSyntax in syntax.OfType<FunctionDeclarationSyntax>())
DeclareFunction(functionDeclarationSyntax);

foreach (var statementSyntax in syntax)
{
var statement = BindStatement(statementSyntax);
statements.Add(statement);
}

_scope = _scope.Parent;

return new BoundBlockStatement(statements.ToImmutable());
return statements.ToImmutable();
}

private BoundStatement BindVariableDeclaration(VariableDeclarationSyntax syntax)
Expand Down Expand Up @@ -507,9 +494,7 @@ private VariableSymbol BindVariable(SyntaxToken identifier, bool isReadOnly, Typ
{
var name = identifier.Text ?? "?";
var declare = !identifier.IsMissing;
var variable = _function == null
? (VariableSymbol) new GlobalVariableSymbol(name, isReadOnly, type)
: new LocalVariableSymbol(name, isReadOnly, type);
var variable = new VariableSymbol(name, isReadOnly, type);

if (declare && !_scope.TryDeclareVariable(variable))
_diagnostics.ReportSymbolAlreadyDeclared(identifier.Span, name);
Expand Down
10 changes: 5 additions & 5 deletions src/Minsk/CodeAnalysis/Binding/BoundGlobalScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ namespace Minsk.CodeAnalysis.Binding
{
internal sealed class BoundGlobalScope
{
public BoundGlobalScope(BoundGlobalScope previous, ImmutableArray<Diagnostic> diagnostics, ImmutableArray<FunctionSymbol> functions, ImmutableArray<VariableSymbol> variables, ImmutableArray<BoundStatement> statements)
public BoundGlobalScope(BoundGlobalScope previous, BoundScope scope, ImmutableArray<(FunctionSymbol function, BoundBlockStatement body)> functionBodies, ImmutableArray<Diagnostic> diagnostics, ImmutableArray<BoundStatement> statements)
{
Previous = previous;
Scope = scope;
FunctionBodies = functionBodies;
Diagnostics = diagnostics;
Functions = functions;
Variables = variables;
Statements = statements;
}

public BoundGlobalScope Previous { get; }
public BoundScope Scope { get; }
public ImmutableArray<(FunctionSymbol function, BoundBlockStatement body)> FunctionBodies { get; }
public ImmutableArray<Diagnostic> Diagnostics { get; }
public ImmutableArray<FunctionSymbol> Functions { get; }
public ImmutableArray<VariableSymbol> Variables { get; }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is useful anymore since it's accessible from the scope (and noone actually used these properties anyway after the refactoring I did).

public ImmutableArray<BoundStatement> Statements { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

namespace Minsk.CodeAnalysis.Binding
{
internal sealed class BoundProgram
internal sealed class LoweredProgram
{
public BoundProgram(ImmutableArray<Diagnostic> diagnostics, ImmutableDictionary<FunctionSymbol, BoundBlockStatement> functions, BoundBlockStatement statement)
public LoweredProgram(ImmutableArray<Diagnostic> diagnostics, ImmutableDictionary<FunctionSymbol, BoundBlockStatement> functions, BoundBlockStatement statement)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let the bikeshedding begin. 😄

{
Diagnostics = diagnostics;
Functions = functions;
Expand Down
34 changes: 18 additions & 16 deletions src/Minsk/CodeAnalysis/Compilation.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading;
using Minsk.CodeAnalysis.Binding;
using Minsk.CodeAnalysis.Lowering;
using Minsk.CodeAnalysis.Symbols;
using Minsk.CodeAnalysis.Syntax;
using Minsk.Utilities;

namespace Minsk.CodeAnalysis
{
public sealed class Compilation
{
private BoundGlobalScope _globalScope;
private readonly CompilationOptions _options;

public Compilation(SyntaxTree syntaxTree)
: this(null, syntaxTree)
public Compilation(SyntaxTree syntaxTree, CompilationOptions options)
: this(null, syntaxTree, options)
{
}

private Compilation(Compilation previous, SyntaxTree syntaxTree)
private Compilation(Compilation previous, SyntaxTree syntaxTree, CompilationOptions options)
{
Previous = previous;
SyntaxTree = syntaxTree;
_options = options;
}

public Compilation Previous { get; }
Expand All @@ -35,7 +36,7 @@ internal BoundGlobalScope GlobalScope
{
if (_globalScope == null)
{
var globalScope = Binder.BindGlobalScope(Previous?.GlobalScope, SyntaxTree.Root);
var globalScope = Binder.BindGlobalScope(Previous?.GlobalScope, SyntaxTree.Root, _options);
Interlocked.CompareExchange(ref _globalScope, globalScope, null);
}

Expand All @@ -45,7 +46,7 @@ internal BoundGlobalScope GlobalScope

public Compilation ContinueWith(SyntaxTree syntaxTree)
{
return new Compilation(this, syntaxTree);
return new Compilation(this, syntaxTree, _options);
}

public EvaluationResult Evaluate(Dictionary<VariableSymbol, object> variables)
Expand All @@ -54,7 +55,7 @@ public EvaluationResult Evaluate(Dictionary<VariableSymbol, object> variables)
if (diagnostics.Any())
return new EvaluationResult(diagnostics, null);

var program = Binder.BindProgram(GlobalScope);
var program = Binder.LowerProgram(GlobalScope);
if (program.Diagnostics.Any())
return new EvaluationResult(program.Diagnostics.ToImmutableArray(), null);

Expand All @@ -65,22 +66,23 @@ public EvaluationResult Evaluate(Dictionary<VariableSymbol, object> variables)

public void EmitTree(TextWriter writer)
{
var program = Binder.BindProgram(GlobalScope);
var program = Binder.LowerProgram(GlobalScope);

if (program.Statement.Statements.Any())
{
program.Statement.WriteTo(writer);
}
else

foreach (var (function, body) in program.Functions)
{
foreach (var functionBody in program.Functions)
if (GlobalScope.Previous != null &&
GlobalScope.Previous.FunctionBodies.Any(functionBody => functionBody.function == function))
{
if (!GlobalScope.Functions.Contains(functionBody.Key))
continue;

functionBody.Key.WriteTo(writer);
functionBody.Value.WriteTo(writer);
continue;
}

function.WriteTo(writer);
body.WriteTo(writer);
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions src/Minsk/CodeAnalysis/CompilationOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Minsk.CodeAnalysis
{
public sealed class CompilationOptions
{
public CompilationOptions(SourceCodeKind sourceCodeKind)
{
SourceCodeKind = sourceCodeKind;
}

public SourceCodeKind SourceCodeKind { get; }
}
}
Loading