Implement script function

This commit is contained in:
Alexander Luzgarev 2020-07-16 11:40:53 +02:00
parent 7f0889f0d4
commit 2f04f86af3
8 changed files with 191 additions and 14 deletions

View File

@ -17,11 +17,49 @@ namespace Parser.Binding
{
var binder = new Binder();
var boundRoot = binder.BindRoot(syntaxTree.NullRoot);
var loweredStatement = Lowerer.Lower(boundRoot.File.Body);
var newRoot = Root(
boundRoot.Syntax,
File(boundRoot.File.Syntax, loweredStatement));
return new BoundProgram(newRoot, binder._diagnostics.ToImmutableArray());
var statements = ((BoundBlockStatement)boundRoot.File.Body).Statements;
var functionsBuilder = ImmutableDictionary.CreateBuilder<FunctionSymbol, BoundBlockStatement>();
var globalStatements = statements.Where(s => s.Kind != BoundNodeKind.FunctionDeclaration).ToArray();
var mainFunction = (FunctionSymbol?)null;
var scriptFunction = (FunctionSymbol?)null;
if (globalStatements.Length > 0)
{
// we have to gather all bound expression statements into a "script" function.
scriptFunction = new FunctionSymbol(
name: "%script",
parameters: ImmutableArray<ParameterSymbol>.Empty,
declaration: null);
var body = Block(globalStatements[0].Syntax, globalStatements);
var loweredBody = Lowerer.Lower(body);
functionsBuilder.Add(scriptFunction, loweredBody);
}
else
{
}
var functions = statements.OfType<BoundFunctionDeclaration>().ToArray();
var first = true;
foreach (var function in functions)
{
var functionSymbol = new FunctionSymbol(
name: function.Name,
parameters: function.InputDescription,
declaration: (FunctionDeclarationSyntaxNode)function.Syntax);
var loweredBody = Lowerer.Lower(function.Body);
functionsBuilder.Add(functionSymbol, loweredBody);
if (first && globalStatements.Length == 0)
{
// the first function in a file will become "main".
first = false;
mainFunction = functionSymbol;
}
}
return new BoundProgram(
binder._diagnostics.ToImmutableArray(),
mainFunction,
scriptFunction,
functionsBuilder.ToImmutable());
}
private BoundRoot BindRoot(RootSyntaxNode node)
@ -148,7 +186,58 @@ namespace Parser.Binding
private BoundFunctionDeclaration BindFunctionDeclaration(FunctionDeclarationSyntaxNode node)
{
throw new NotImplementedException();
var inputDescription = BindInputDescription(node.InputDescription);
var outputDescription = BindOutputDescription(node.OutputDescription);
var body = BindStatement(node.Body);
return new BoundFunctionDeclaration(node, node.Name.Text, inputDescription, outputDescription, body);
}
private ImmutableArray<ParameterSymbol> BindOutputDescription(FunctionOutputDescriptionSyntaxNode? node)
{
if (node is null)
{
return ImmutableArray<ParameterSymbol>.Empty;
}
var outputs = node.OutputList.Where(p => p.IsNode).Select(p => p.AsNode()!);
var builder = ImmutableArray.CreateBuilder<ParameterSymbol>();
foreach (var output in outputs)
{
if (output.Kind != TokenKind.IdentifierNameExpression)
{
throw new Exception($"Invalid function output kind {output.Kind}.");
}
builder.Add(BindParameterSymbol((IdentifierNameExpressionSyntaxNode)output));
}
return builder.ToImmutable();
}
private ImmutableArray<ParameterSymbol> BindInputDescription(FunctionInputDescriptionSyntaxNode? node)
{
if (node is null)
{
return ImmutableArray<ParameterSymbol>.Empty;
}
var parameters = node.ParameterList.Where(p => p.IsNode).Select(p => p.AsNode()!);
var builder = ImmutableArray.CreateBuilder<ParameterSymbol>();
foreach (var parameter in parameters)
{
if (parameter.Kind != TokenKind.IdentifierNameExpression)
{
throw new Exception($"Invalid function parameter kind {parameter.Kind}.");
}
builder.Add(BindParameterSymbol((IdentifierNameExpressionSyntaxNode)parameter));
}
return builder.ToImmutable();
}
private ParameterSymbol BindParameterSymbol(IdentifierNameExpressionSyntaxNode parameter)
{
return new ParameterSymbol(parameter.Text);
}
private BoundForStatement BindForStatement(ForStatementSyntaxNode node)

View File

@ -5,16 +5,33 @@ namespace Parser.Binding
{
public class BoundProgram
{
public BoundProgram(BoundRoot nullRoot, ImmutableArray<Diagnostic> diagnostics)
public BoundProgram(
ImmutableArray<Diagnostic> diagnostics,
FunctionSymbol? mainFunction,
FunctionSymbol? scriptFunction,
ImmutableDictionary<FunctionSymbol, BoundBlockStatement> functions)
{
NullRoot = nullRoot;
Diagnostics = diagnostics;
MainFunction = mainFunction;
ScriptFunction = scriptFunction;
Functions = functions;
}
public ImmutableArray<Diagnostic> Diagnostics { get; }
public BoundRoot NullRoot { get; }
/// <summary>
/// A "main" function (first in a file without any global statements).
/// </summary>
public FunctionSymbol? MainFunction { get; }
public BoundFile Root => NullRoot.File;
/// <summary>
/// A "script" function (generated from all global statements in a file if there are any).
/// </summary>
public FunctionSymbol? ScriptFunction { get; }
/// <summary>
/// So-called "local" functions.
/// </summary>
public ImmutableDictionary<FunctionSymbol, BoundBlockStatement> Functions { get; }
}
}

View File

@ -141,12 +141,21 @@ namespace Parser.Binding
public class BoundFunctionDeclaration : BoundStatement
{
public BoundFunctionDeclaration(SyntaxNode syntax)
public BoundFunctionDeclaration(SyntaxNode syntax, string name, ImmutableArray<ParameterSymbol> inputDescription, ImmutableArray<ParameterSymbol> outputDescription, BoundStatement body)
: base(syntax)
{
Name = name;
InputDescription = inputDescription;
OutputDescription = outputDescription;
Body = body;
}
public override BoundNodeKind Kind => BoundNodeKind.FunctionDeclaration;
public string Name { get; }
public ImmutableArray<ParameterSymbol> InputDescription { get; }
public ImmutableArray<ParameterSymbol> OutputDescription { get; }
public BoundStatement Body { get; }
}
public class BoundGotoStatement : BoundStatement

View File

@ -0,0 +1,23 @@
using System.Collections.Immutable;
namespace Parser.Binding
{
public class FunctionSymbol
{
public FunctionSymbol(
string name,
ImmutableArray<ParameterSymbol> parameters,
FunctionDeclarationSyntaxNode? declaration)
{
Name = name;
Parameters = parameters;
Declaration = declaration;
}
public string Name { get; }
public ImmutableArray<ParameterSymbol> Parameters { get; }
public FunctionDeclarationSyntaxNode? Declaration { get; }
}
}

View File

@ -0,0 +1,12 @@
namespace Parser.Binding
{
public class ParameterSymbol
{
public ParameterSymbol(string name)
{
Name = name;
}
public string Name { get; }
}
}

View File

@ -26,8 +26,30 @@ namespace Parser
internal EvaluationResult Evaluate()
{
var result = EvaluateFile(_program.Root);
return new EvaluationResult(result, _diagnostics.ToImmutableArray());
if (_program.MainFunction is { } mainFunction)
{
if (mainFunction.Parameters.Length > 0)
{
_diagnostics.ReportNotEnoughInputs(
new TextSpan(mainFunction.Declaration.Position, mainFunction.Declaration.Position + mainFunction.Declaration.FullWidth),
mainFunction.Name);
return new EvaluationResult(null, _diagnostics.ToImmutableArray());
}
else
{
var result = EvaluateBlockStatement(_program.Functions[mainFunction]);
return new EvaluationResult(result, _diagnostics.ToImmutableArray());
}
}
else if (_program.ScriptFunction is { } scriptFunction)
{
var result = EvaluateBlockStatement(_program.Functions[scriptFunction]);
return new EvaluationResult(result, _diagnostics.ToImmutableArray());
}
else
{
return new EvaluationResult(null, _diagnostics.ToImmutableArray());
}
}
private MObject? EvaluateFile(BoundFile root)

View File

@ -38,6 +38,11 @@ namespace Parser.Internal
Report(span, "Unexpected end of file.");
}
internal void ReportNotEnoughInputs(TextSpan span, string functionName)
{
Report(span, $"Not enough inputs for function '{functionName}'.");
}
internal void ReportUnexpectedCharacterWhileParsingNumber(TextSpan span, char c)
{
Report(span, $"Unexpected character '{c}' while parsing a number.");

View File

@ -175,7 +175,7 @@ namespace Parser.Internal
if (CurrentToken.Kind == TokenKind.TildeToken)
{
var notToken = EatToken();
builder.Add(notToken);
builder.Add(Factory.IdentifierNameExpressionSyntax(notToken));
}
else
{