Skip to content

Commit

Permalink
Merge pull request #1 from Shimakaze-Kan/release_v1_1
Browse files Browse the repository at this point in the history
Release v1 1
  • Loading branch information
Shimakaze-Kan authored Aug 9, 2024
2 parents 75924a8 + 544a898 commit 4079c65
Show file tree
Hide file tree
Showing 9 changed files with 331 additions and 7 deletions.
16 changes: 16 additions & 0 deletions GNScript/AstNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -413,3 +413,19 @@ public FileExistsNode(AstNode path)
Path = path;
}
}

public class UserDefinedExtensionNode : AstNode
{
public AstNode Type { get; }
public AstNode RefBoxName { get; }
public AstNode FunctionName { get; }
public AstNode NumberOfParameters { get; }

public UserDefinedExtensionNode(AstNode type, AstNode refBoxName, AstNode functionName, AstNode numberOfParameters)
{
Type = type;
RefBoxName = refBoxName;
FunctionName = functionName;
NumberOfParameters = numberOfParameters;
}
}
128 changes: 124 additions & 4 deletions GNScript/Interpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class Interpreter
private Dictionary<FunctionDictionaryKey, FunctionNode> _functions = [];
private Dictionary<string, RefBoxNode> _refBoxDefinitions = [];
private Stack<CallReturnValue> _callReturnValue = [];
private Dictionary<UserDefinedExtensionKey, UserDefinedExtension> _userDefinedExtensions = [];
private int _scopeLevel = 0;
private bool _isForLoopParameterSection = false;

Expand Down Expand Up @@ -479,11 +480,19 @@ public ExecutionModel Visit(AstNode node)
}
}

if (nodeModel.IsArray() && ArrayNode.Extensions.Contains(extensionNode.ExtensionName))
var arrayExtensions = EnumHelpers.GetEnumNamesLowercase<ArrayExtension>();
if (nodeModel.IsArray())
{
var originalArrayValue = (List<object>)nodeModel;
var arrayValue = originalArrayValue.DeepCopy(); // GN Script array is not reference type by language convention

if (_userDefinedExtensions.TryGetValue(new(extensionNode.ExtensionName.ToLower(), extensionNode.Arguments.Count + 1), out var userDefinedExtension)
&& userDefinedExtension.Type == "array")
{
return ExecuteUserDefinedExtension(extensionNode, arrayValue, userDefinedExtension);
}
ExceptionsHelper.FailIfFalse(arrayExtensions.Contains(extensionNode.ExtensionName), "Extension not found");

if (EnumHelpers.EqualsIgnoreCase(extensionNode.ExtensionName, ArrayExtension.Length))
{
return arrayValue.Count;
Expand Down Expand Up @@ -574,9 +583,17 @@ public ExecutionModel Visit(AstNode node)
}

var stringExtensions = EnumHelpers.GetEnumNamesLowercase<StringExtension>();
if (nodeModel.IsString() && stringExtensions.Contains(extensionNode.ExtensionName))
if (nodeModel.IsString())
{
var stringValue = (string)nodeModel;

if (_userDefinedExtensions.TryGetValue(new(extensionNode.ExtensionName.ToLower(), extensionNode.Arguments.Count + 1), out var userDefinedExtension)
&& userDefinedExtension.Type == "string")
{
return ExecuteUserDefinedExtension(extensionNode, stringValue, userDefinedExtension);
}
ExceptionsHelper.FailIfFalse(stringExtensions.Contains(extensionNode.ExtensionName), "Extension not found");

if (EnumHelpers.EqualsIgnoreCase(extensionNode.ExtensionName, StringExtension.ToLower))
{
return stringValue.ToLower();
Expand Down Expand Up @@ -639,8 +656,15 @@ public ExecutionModel Visit(AstNode node)
}

var refBoxExtensions = EnumHelpers.GetEnumNamesLowercase<BoxExtension>();
if (nodeModel.IsRefBox() && refBoxExtensions.Contains(extensionNode.ExtensionName))
if (nodeModel.IsRefBox())
{
if (_userDefinedExtensions.TryGetValue(new(extensionNode.ExtensionName.ToLower(), extensionNode.Arguments.Count + 1), out var userDefinedExtension)
&& userDefinedExtension.Type == "refbox")
{
return ExecuteUserDefinedExtension(extensionNode, nodeModel.Value, userDefinedExtension);
}
ExceptionsHelper.FailIfFalse(refBoxExtensions.Contains(extensionNode.ExtensionName), "Extension not found");

if (EnumHelpers.EqualsIgnoreCase(extensionNode.ExtensionName, BoxExtension.IsInstanceOf))
{
ExceptionsHelper.FailIfTrue(extensionNode.Arguments.Count != 1, "Expected 1 arguments");
Expand Down Expand Up @@ -734,9 +758,16 @@ public ExecutionModel Visit(AstNode node)
}

var intExtensions = EnumHelpers.GetEnumNamesLowercase<IntExtension>();
if (nodeModel.IsInt() && intExtensions.Contains(extensionNode.ExtensionName))
if (nodeModel.IsInt())
{
var intValue = (int)nodeModel;
if (_userDefinedExtensions.TryGetValue(new(extensionNode.ExtensionName.ToLower(), extensionNode.Arguments.Count + 1), out var userDefinedExtension)
&& userDefinedExtension.Type == "int")
{
return ExecuteUserDefinedExtension(extensionNode, intValue, userDefinedExtension);
}
ExceptionsHelper.FailIfFalse(intExtensions.Contains(extensionNode.ExtensionName), "Extension not found");

if (EnumHelpers.EqualsIgnoreCase(extensionNode.ExtensionName, IntExtension.ToString))
{
return intValue.ToString();
Expand Down Expand Up @@ -1060,10 +1091,87 @@ public ExecutionModel Visit(AstNode node)
var exists = File.Exists((string)pathModel);
return exists ? 1 : 0;
}
else if (node is UserDefinedExtensionNode userDefinedExtensionNode)
{
var typeModel = Visit(userDefinedExtensionNode.Type);
var refboxNameModel = Visit(userDefinedExtensionNode.RefBoxName);
var functionNameModel = Visit(userDefinedExtensionNode.FunctionName);
var numOfParametersModel = Visit(userDefinedExtensionNode.NumberOfParameters);

ExceptionsHelper.FailIfFalse(typeModel.IsString(), "Expected type");
ExceptionsHelper.FailIfFalse(refboxNameModel.IsString(), "Expected an refbox name");
ExceptionsHelper.FailIfFalse(functionNameModel.IsString(), "Expected an function name");
ExceptionsHelper.FailIfFalse(numOfParametersModel.IsInt(), "Expected number of function parameters");

var type = ((string)typeModel).ToLower();
var refboxName = (string)refboxNameModel;
var functionName = (string)functionNameModel;
var numOfParameters = (int)numOfParametersModel;

ExceptionsHelper.FailIfTrue(numOfParameters == 0, "Expected at least one parameter in referenced function");

string[] types = ["string", "int", "refbox", "array"];
ExceptionsHelper.FailIfFalse(types.Contains(type), "Invalid type");

ExceptionsHelper.FailIfFalse(_refBoxDefinitions.ContainsKey(refboxName), "Refbox not found");
var refbox = _refBoxDefinitions[refboxName];

ExceptionsHelper.FailIfFalse(refbox.IsConst, "Expected const refbox");
ExceptionsHelper.FailIfTrue(refbox.IsAbstract, "Refbox cannot be abstract");
var function = refbox.Functions.FirstOrDefault(f => f.Element.Name == functionName && f.Element.Parameters.Count == numOfParameters);
ExceptionsHelper.FailIfTrue(function is null, "Function not found");
ExceptionsHelper.FailIfTrue(function.Modifier == AccessModifier.Guarded, "Function has to be Exposed");

var functionBody = function.Element.Body;
_userDefinedExtensions[new(functionName.ToLower(), function.Element.Parameters.Count)] = new(type, function.Element.Parameters, functionBody);

return ExecutionModel.Empty;
}

throw new Exception("AST node error");
}

private ExecutionModel ExecuteUserDefinedExtension(ExtensionAccessNode extensionNode, object value, UserDefinedExtension userDefinedExtension)
{
ExceptionsHelper.FailIfFalse(extensionNode.Arguments.Count == userDefinedExtension.FunctionParameterNames.Count - 1,
$"Expected {userDefinedExtension.FunctionParameterNames.Count - 1} parameters");

var callScopeLevel = _scopeLevel;
_scopeLevel++;

// pass value before extension as first parameter
_variables.SetVariable(userDefinedExtension.FunctionParameterNames[0], value, _scopeLevel, true);

for (int i = 0; i < extensionNode.Arguments.Count; i++)
{
_variables.SetVariable(userDefinedExtension.FunctionParameterNames[i + 1], Visit(extensionNode.Arguments[i]).Value, _scopeLevel, true);
}

var body = userDefinedExtension.FunctionBody;
while (body != null)
{
Visit(body);
body = body.Next;

if (_callReturnValue.Any())
{
_variables.ClearScope(callScopeLevel + 1);
_scopeLevel = callScopeLevel;
var callReturnValue = _callReturnValue.Pop();

if (callReturnValue.IsVoid)
{
return ExecutionModel.Empty;
}

var returnValue = callReturnValue.ReturnValue;
return ExecutionModel.FromObject(returnValue);
}
}

throw new Exception("No return statement in extension referenced function");
}

private RefBoxInstanceNode CreateAnonymousRefBoxInstance(Dictionary<FunctionVariableDictionaryKey, RefBoxElement>? callReturnValue, List<string> anonymousRefBoxDefinitionNames)
{
anonymousRefBoxDefinitionNames.Add($"anonymous_{Guid.NewGuid()}");
Expand Down Expand Up @@ -1114,6 +1222,17 @@ public void Dump()
if (_refBoxDefinitions.Count == 0)
sb.AppendLine(" No ref boxes to display.");

sb.AppendLine();

sb.AppendLine("[User defined extensions]");
foreach (var key in _userDefinedExtensions.Keys)
{
sb.AppendLine($" {key.ExtensionName} : (1){(key.NumberOfParameters > 1 ? $" + ({key.NumberOfParameters - 1})" : "")}");
}

if (_userDefinedExtensions.Count == 0)
sb.AppendLine(" No extensions to display.");

Console.WriteLine(sb);
}

Expand Down Expand Up @@ -1144,6 +1263,7 @@ public void SetRuntimeState(InterpreterRuntimeState interpreterRuntimeState)
_refBoxDefinitions = interpreterRuntimeState.RefBoxDefinitions;
_scopeLevel = interpreterRuntimeState.ScopeLevel;
_variables = interpreterRuntimeState.Variables;
_userDefinedExtensions = interpreterRuntimeState.UserDefinedExtensions;
}

private void DeclareFunctionParameters(FunctionCallNode functionCallNode, FunctionNode function)
Expand Down
2 changes: 2 additions & 0 deletions GNScript/Lexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ private Token ReadIdentifierOrKeyword()
return new Token(TokenType.ReadWholeFile, value);
case "fileExists":
return new Token(TokenType.FileExists, value);
case "createExtension":
return new Token(TokenType.UserDefinedExtension, value);
case "const":
return new Token(TokenType.Const, value);
case "wuwei":
Expand Down
2 changes: 2 additions & 0 deletions GNScript/Models/InterpreterRuntimeState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public class InterpreterRuntimeState
public Dictionary<FunctionDictionaryKey, FunctionNode> Functions { get; set; }
public Dictionary<string, RefBoxNode> RefBoxDefinitions { get; set; }
public Stack<CallReturnValue> CallReturnValues { get; set; }
public Dictionary<UserDefinedExtensionKey, UserDefinedExtension> UserDefinedExtensions { get; set; }
public int ScopeLevel { get; set; }
public bool IsForLoopParameterSection { get; set; }

Expand All @@ -25,6 +26,7 @@ public void Combine(InterpreterRuntimeState state)
Functions.CombineDictionaries(state.Functions);
RefBoxDefinitions.CombineDictionaries(state.RefBoxDefinitions);
CallReturnValues = new(CallReturnValues.Concat(state.CallReturnValues.Reverse()));
UserDefinedExtensions.CombineDictionaries(state.UserDefinedExtensions);
ScopeLevel = state.ScopeLevel;
IsForLoopParameterSection = state.IsForLoopParameterSection;
}
Expand Down
2 changes: 2 additions & 0 deletions GNScript/Models/UserDefinedExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
namespace GNScript.Models;
public record UserDefinedExtension(string Type, List<string> FunctionParameterNames, AstNode FunctionBody);
28 changes: 28 additions & 0 deletions GNScript/Models/UserDefinedExtensionKey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace GNScript.Models;
public class UserDefinedExtensionKey
{
public string ExtensionName { get; }
public int NumberOfParameters { get; }

public UserDefinedExtensionKey(string extensionName, int numberOfParameters)
{
ExtensionName = extensionName;
NumberOfParameters = numberOfParameters;
}

public override bool Equals(object? obj)
{
if (obj == null)
return false;

if (obj is UserDefinedExtensionKey other == false)
return false;

return other.ExtensionName == ExtensionName && other.NumberOfParameters == NumberOfParameters;
}

public override int GetHashCode()
{
return HashCode.Combine(ExtensionName, NumberOfParameters);
}
}
61 changes: 58 additions & 3 deletions GNScript/Parser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace GNScript;
using GNScript.Helpers;
using System.IO;

namespace GNScript;
public class Parser
{
private readonly List<Token> _tokens;
Expand Down Expand Up @@ -318,12 +321,27 @@ private AstNode ParseFunctionCall()

private AstNode ParseExpression()
{
return ParseComparison();
return ParseLogicalOperator();
}

private AstNode ParseLogicalOperator()
{
var left = ParseComparison();

while (_position < _tokens.Count && (_tokens[_position].Type == TokenType.AndOperator || _tokens[_position].Type == TokenType.OrOperator))
{
var operatorToken = _tokens[_position];
_position++;
var right = ParseComparison();
left = new BinaryOperationNode(left, operatorToken, right);
}

return left;
}

private AstNode ParseComparison()
{
var comparsionOperators = new[] { TokenType.GreaterThan, TokenType.LessThan, TokenType.Equal, TokenType.NotEqual, TokenType.GreaterThanOrEqual, TokenType.LessThanOrEqual, TokenType.AndOperator, TokenType.OrOperator };
var comparsionOperators = new[] { TokenType.GreaterThan, TokenType.LessThan, TokenType.Equal, TokenType.NotEqual, TokenType.GreaterThanOrEqual, TokenType.LessThanOrEqual };
var left = ParseTerm();

while (_position < _tokens.Count && comparsionOperators.Contains(_tokens[_position].Type))
Expand Down Expand Up @@ -447,6 +465,11 @@ private AstNode ParsePrimary()
}
_position++;

if (_tokens[_position].Type == TokenType.Colon) // there can be extension after function call
{
return ParseExtensionAccess(expression);
}

AstNode result = expression;
while (_tokens[_position].Type == TokenType.Dot) // we can call a function for anonymous refbox instance
{
Expand Down Expand Up @@ -595,6 +618,38 @@ private AstNode ParsePrimary()
return ParseExtensionAccess(node);
}

return node;
}
case TokenType.UserDefinedExtension:
{
_position++; // createExtension
if (_tokens[_position].Type != TokenType.LeftParen)
{
throw new Exception("Expected (");
}
_position++; // (

var parameters = new List<AstNode>();
while (_tokens[_position].Type != TokenType.RightParen)
{
parameters.Add(ParseExpression());

if (_tokens[_position].Type == TokenType.Comma)
_position++; // comma
}

ExceptionsHelper.FailIfFalse(parameters.Count == 4, "Expected 4 parameters");

// Ensure we are at a right parenthesis after parsing arguments
if (_tokens[_position].Type != TokenType.RightParen)
{
throw new Exception("Missing closing parenthesis after function call arguments");
}

_position++; // )

var node = new UserDefinedExtensionNode(parameters[0], parameters[1], parameters[2], parameters[3]);

return node;
}
default:
Expand Down
1 change: 1 addition & 0 deletions GNScript/Token.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public enum TokenType
ReadWholeFile,
FileExists,
Const,
UserDefinedExtension,

#region Access Modifiers
Guarded,
Expand Down
Loading

0 comments on commit 4079c65

Please sign in to comment.