using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BetterTerminal.Scripts.Language; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("BetterTerminal")] [assembly: InternalsVisibleTo("STFO")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("STFO.Language")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+09dcc6a36661551cc3e378f1ce513b747a2397d4")] [assembly: AssemblyProduct("STFO.Language")] [assembly: AssemblyTitle("STFO.Language")] [assembly: AssemblyVersion("1.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace BetterTerminal.Scripts.Runtime { internal enum StfoValueType { None, Number, Boolean, Text, List, Map } internal readonly record struct StfoValue(StfoValueType Type, object? Raw) { internal static StfoValue None => new StfoValue(StfoValueType.None, null); internal double Number => (double)Raw; internal bool Boolean => (bool)Raw; internal string Text => (string)Raw; internal IReadOnlyList List => (IReadOnlyList)Raw; internal IReadOnlyDictionary Map => (IReadOnlyDictionary)Raw; internal int EstimatedBytes => Type switch { StfoValueType.None => 0, StfoValueType.Number => 8, StfoValueType.Boolean => 1, StfoValueType.Text => Text.Length * 2, StfoValueType.List => List.Sum((StfoValue item) => item.EstimatedBytes + IntPtr.Size), StfoValueType.Map => Map.Sum>((KeyValuePair item) => item.Key.Length * 2 + item.Value.EstimatedBytes + IntPtr.Size), _ => 0, }; internal static StfoValue FromNumber(double value) { return new StfoValue(StfoValueType.Number, value); } internal static StfoValue FromBoolean(bool value) { return new StfoValue(StfoValueType.Boolean, value); } internal static StfoValue FromText(string value) { return new StfoValue(StfoValueType.Text, value); } internal static StfoValue FromList(IReadOnlyList value) { return new StfoValue(StfoValueType.List, value); } internal static StfoValue FromMap(IReadOnlyDictionary value) { return new StfoValue(StfoValueType.Map, value); } public override string ToString() { return Type switch { StfoValueType.None => "NONE", StfoValueType.Number => Number.ToString("0.###############", CultureInfo.InvariantCulture), StfoValueType.Boolean => Boolean ? "TRUE" : "FALSE", StfoValueType.Text => Text, StfoValueType.List => "[" + string.Join(", ", List.Select((StfoValue item) => item.ToString())) + "]", StfoValueType.Map => "{" + string.Join(", ", Map.Select, string>((KeyValuePair item) => $"{item.Key}: {item.Value}")) + "}", _ => string.Empty, }; } [CompilerGenerated] private bool PrintMembers(StringBuilder builder) { builder.Append("Type = "); builder.Append(Type.ToString()); builder.Append(", Raw = "); builder.Append(Raw); return true; } } internal sealed record StfoExecutionResult(IReadOnlyList PrintedLines, StfoValue Output, IReadOnlyList Diagnostics, int Instructions, int EstimatedMemoryBytes, StfoInputRequest? InputRequest) { internal bool IsWaitingForInput => (object)InputRequest != null; internal bool Succeeded { get { if (!IsWaitingForInput) { return Diagnostics.All((StfoDiagnostic item) => item.Severity != StfoDiagnosticSeverity.Error); } return false; } } [CompilerGenerated] private bool PrintMembers(StringBuilder builder) { RuntimeHelpers.EnsureSufficientExecutionStack(); builder.Append("PrintedLines = "); builder.Append(PrintedLines); builder.Append(", Output = "); builder.Append(Output.ToString()); builder.Append(", Diagnostics = "); builder.Append(Diagnostics); builder.Append(", Instructions = "); builder.Append(Instructions.ToString()); builder.Append(", EstimatedMemoryBytes = "); builder.Append(EstimatedMemoryBytes.ToString()); builder.Append(", InputRequest = "); builder.Append(InputRequest); return true; } } internal enum StfoInputKind { Text, Number, Boolean, Option } internal sealed record StfoInputRequest(StfoInputKind Kind, string Prompt, IReadOnlyList? Options = null); internal sealed class StfoVirtualMachine { private readonly StfoPolicy _policy; private readonly IStfoWorkspace _workspace; private readonly bool _allowFileWrites; private readonly Dictionary _variables = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Stack> _localScopes = new Stack>(); private readonly Dictionary _functions = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly HashSet _executedImports = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly List _printedLines = new List(); private readonly List _diagnostics = new List(); private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); private StfoValue _output = StfoValue.None; private int _instructions; private int _estimatedMemoryBytes; private bool _stopped; private int _callDepth; private readonly Queue _inputValues; private StfoInputRequest? _inputRequest; internal StfoVirtualMachine(StfoPolicy policy, IStfoWorkspace workspace, bool allowFileWrites, IEnumerable? inputValues = null) { _policy = policy; _workspace = workspace; _allowFileWrites = allowFileWrites; _inputValues = new Queue(inputValues ?? Array.Empty()); } internal StfoExecutionResult Execute(string entryPath, StfoEntryPointKind entryPointKind, StfoValue? pipelineInput = null) { string fullPath = Path.GetFullPath(entryPath); string file = Relative(fullPath); string source = File.ReadAllText(fullPath); StfoLexResult stfoLexResult = new StfoLexer(file, source).Lex(); StfoEntryPoint entryPoint = StfoManifest.Parse(file, stfoLexResult.Tokens).GetEntryPoint(entryPointKind); if ((object)entryPoint == null) { ReportFatal((entryPointKind == StfoEntryPointKind.Program) ? StfoDiagnosticCode.MissingProgramEntryPoint : StfoDiagnosticCode.MissingPipelineEntryPoint, "SCRIPT DOES NOT DECLARE @" + entryPointKind.ToString().ToLowerInvariant() + ".", file, new StfoToken(StfoTokenKind.EndOfFile, string.Empty, 1, 1, 0)); } else { ExecuteFile(fullPath); if (!_stopped) { IReadOnlyList argumentValues = ((entryPointKind != StfoEntryPointKind.Pipeline) ? ((IReadOnlyList)Array.Empty()) : ((IReadOnlyList)new StfoValue[1] { pipelineInput ?? StfoValue.None })); StfoValue output = InvokeFunction(entryPoint.FunctionName, argumentValues, file, entryPoint.Token); if (output.Type != StfoValueType.None) { _output = output; } } } return new StfoExecutionResult(_printedLines, _output, _diagnostics, _instructions, _estimatedMemoryBytes, _inputRequest); } private void ExecuteFile(string path) { if (_stopped || !_executedImports.Add(path)) { return; } string text = Relative(path); try { string source = File.ReadAllText(path); StfoLexResult stfoLexResult = new StfoLexer(text, source).Lex(); _diagnostics.AddRange(stfoLexResult.Diagnostics); if (stfoLexResult.Diagnostics.Any((StfoDiagnostic item) => item.Severity == StfoDiagnosticSeverity.Error)) { _stopped = true; return; } StfoProgramSyntax stfoProgramSyntax = new StfoParser(text, stfoLexResult.Tokens).Parse(); _diagnostics.AddRange(stfoProgramSyntax.Diagnostics); if (stfoProgramSyntax.Diagnostics.Any((StfoDiagnostic item) => item.Severity == StfoDiagnosticSeverity.Error)) { _stopped = true; return; } foreach (StfoFunctionStatementSyntax item in stfoProgramSyntax.Statements.OfType()) { _functions[item.Name] = new StfoFunctionBinding(item, path); } foreach (StfoStatementSyntax statement in stfoProgramSyntax.Statements) { if (_stopped || !ConsumeInstruction(statement.Token, text)) { break; } ExecuteStatement(statement, path, text); } } catch (Exception ex) { ReportFatal(StfoDiagnosticCode.IoError, "EXECUTION FAILED: " + ex.Message, text, new StfoToken(StfoTokenKind.EndOfFile, string.Empty, 1, 1, 0)); } } private void ExecuteStatement(StfoStatementSyntax statement, string currentPath, string currentFile) { string scriptPath; string error; if (!(statement is StfoImportStatementSyntax stfoImportStatementSyntax)) { if (!(statement is StfoAssignmentStatementSyntax stfoAssignmentStatementSyntax)) { if (!(statement is StfoPrintStatementSyntax stfoPrintStatementSyntax)) { if (!(statement is StfoOutputStatementSyntax stfoOutputStatementSyntax)) { if (!(statement is StfoExpressionStatementSyntax stfoExpressionStatementSyntax)) { if (!(statement is StfoIfStatementSyntax statement2)) { if (statement is StfoFunctionStatementSyntax) { return; } if (!(statement is StfoReturnStatementSyntax stfoReturnStatementSyntax)) { if (!(statement is StfoForStatementSyntax statement3)) { if (statement is StfoWhileStatementSyntax statement4) { ExecuteWhile(statement4, currentPath, currentFile); } } else { ExecuteFor(statement3, currentPath, currentFile); } } else { if (_callDepth != 0) { throw new StfoReturnSignal(Evaluate(stfoReturnStatementSyntax.Expression, currentFile)); } ReportFatal(StfoDiagnosticCode.ReturnOutsideFunction, "RETURN USED OUTSIDE FUNCTION.", currentFile, stfoReturnStatementSyntax.Token); } } else { ExecuteIf(statement2, currentPath, currentFile); } } else { Evaluate(stfoExpressionStatementSyntax.Expression, currentFile); } } else { _output = Evaluate(stfoOutputStatementSyntax.Expression, currentFile); UpdateMemory(stfoOutputStatementSyntax.Token, currentFile); } } else { _printedLines.Add(Evaluate(stfoPrintStatementSyntax.Expression, currentFile).ToString()); UpdateMemory(stfoPrintStatementSyntax.Token, currentFile); } } else { SetVariable(stfoAssignmentStatementSyntax.Name, Evaluate(stfoAssignmentStatementSyntax.Expression, currentFile), stfoAssignmentStatementSyntax.Token, currentFile); } } else if (!_workspace.TryResolveScript(stfoImportStatementSyntax.Path, out scriptPath, out error, Path.GetDirectoryName(currentPath))) { ReportFatal(StfoDiagnosticCode.ImportOutsideWorkspace, error, currentFile, stfoImportStatementSyntax.Token); } else { ExecuteFile(scriptPath); } } private void ExecuteFor(StfoForStatementSyntax statement, string currentPath, string currentFile) { StfoValue stfoValue = Evaluate(statement.Collection, currentFile); if (stfoValue.Type != StfoValueType.List) { ReportFatal(StfoDiagnosticCode.InvalidOperation, "FOR EXPECTS A LIST.", currentFile, statement.Token); return; } foreach (StfoValue item in stfoValue.List) { if (_stopped || !ConsumeInstruction(statement.Token, currentFile)) { break; } SetVariable(statement.Variable, item, statement.Token, currentFile); foreach (StfoStatementSyntax item2 in statement.Body) { if (_stopped || !ConsumeInstruction(item2.Token, currentFile)) { return; } ExecuteStatement(item2, currentPath, currentFile); } } } private void ExecuteWhile(StfoWhileStatementSyntax statement, string currentPath, string currentFile) { while (!_stopped && ConsumeInstruction(statement.Token, currentFile)) { StfoValue stfoValue = Evaluate(statement.Condition, currentFile); if (stfoValue.Type != StfoValueType.Boolean) { ReportFatal(StfoDiagnosticCode.InvalidOperation, "WHILE CONDITION MUST BE BOOLEAN.", currentFile, statement.Token); break; } if (!stfoValue.Boolean) { break; } foreach (StfoStatementSyntax item in statement.Body) { if (_stopped || !ConsumeInstruction(item.Token, currentFile)) { return; } ExecuteStatement(item, currentPath, currentFile); } } } private void ExecuteIf(StfoIfStatementSyntax statement, string currentPath, string currentFile) { StfoValue stfoValue = Evaluate(statement.Condition, currentFile); if (stfoValue.Type != StfoValueType.Boolean) { ReportFatal(StfoDiagnosticCode.InvalidOperation, "IF CONDITION MUST BE BOOLEAN.", currentFile, statement.Token); return; } foreach (StfoStatementSyntax item in stfoValue.Boolean ? statement.ThenStatements : statement.ElseStatements) { if (_stopped || !ConsumeInstruction(item.Token, currentFile)) { break; } ExecuteStatement(item, currentPath, currentFile); } } private StfoValue Evaluate(StfoExpressionSyntax expression, string file) { if (_stopped || !ConsumeInstruction(expression.Token, file)) { return StfoValue.None; } if (!(expression is StfoLiteralExpressionSyntax stfoLiteralExpressionSyntax)) { if (!(expression is StfoNameExpressionSyntax expression2)) { if (!(expression is StfoUnaryExpressionSyntax expression3)) { if (!(expression is StfoBinaryExpressionSyntax expression4)) { if (!(expression is StfoCallExpressionSyntax call)) { if (expression is StfoListExpressionSyntax expression5) { return EvaluateList(expression5, file); } return StfoValue.None; } return EvaluateCall(call, file); } return EvaluateBinary(expression4, file); } return EvaluateUnary(expression3, file); } return GetVariable(expression2, file); } return FromLiteral(stfoLiteralExpressionSyntax.Value); } private StfoValue EvaluateList(StfoListExpressionSyntax expression, string file) { if (expression.Items.Count > _policy.MaximumCollectionSize) { ReportFatal(StfoDiagnosticCode.CollectionLimit, "MAXIMUM COLLECTION SIZE EXCEEDED.", file, expression.Token); return StfoValue.None; } List list = new List(expression.Items.Count); foreach (StfoExpressionSyntax item in expression.Items) { list.Add(Evaluate(item, file)); } return StfoValue.FromList(list); } private StfoValue EvaluateCall(StfoCallExpressionSyntax call, string file) { List list = new List(call.Arguments.Count); foreach (StfoExpressionSyntax argument in call.Arguments) { list.Add(Evaluate(argument, file)); } if (_stopped) { return StfoValue.None; } return InvokeFunction(call.Name, list, file, call.Token); } private StfoValue InvokeFunction(string functionName, IReadOnlyList argumentValues, string file, StfoToken callToken) { if (TryInvokeBuiltin(functionName, argumentValues, file, callToken, out var result)) { return result; } if (!_functions.TryGetValue(functionName, out StfoFunctionBinding value)) { ReportFatal(StfoDiagnosticCode.UnknownFunction, "UNKNOWN FUNCTION: " + functionName, file, callToken); return StfoValue.None; } StfoFunctionStatementSyntax syntax = value.Syntax; if (argumentValues.Count != syntax.Parameters.Count) { ReportFatal(StfoDiagnosticCode.ArgumentCountMismatch, $"FUNCTION {syntax.Name} EXPECTS {syntax.Parameters.Count} ARGUMENTS, RECEIVED {argumentValues.Count}.", file, callToken); return StfoValue.None; } if (_callDepth >= _policy.MaximumCallDepth) { ReportFatal(StfoDiagnosticCode.CallDepthLimit, "MAXIMUM CALL DEPTH EXCEEDED.", file, callToken); return StfoValue.None; } Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < syntax.Parameters.Count; i++) { dictionary[syntax.Parameters[i]] = argumentValues[i]; } _localScopes.Push(dictionary); _callDepth++; try { foreach (StfoStatementSyntax item in syntax.Body) { if (!_stopped && ConsumeInstruction(item.Token, file)) { ExecuteStatement(item, value.SourcePath, Relative(value.SourcePath)); continue; } break; } } catch (StfoReturnSignal stfoReturnSignal) { return stfoReturnSignal.Value; } finally { _callDepth--; _localScopes.Pop(); } return StfoValue.None; } private bool TryInvokeBuiltin(string functionName, IReadOnlyList arguments, string file, StfoToken token, out StfoValue result) { result = StfoValue.None; if (!Enum.TryParse(functionName.Replace("_", string.Empty), ignoreCase: true, out var result2)) { return false; } switch (result2) { case StfoBuiltinFunction.Read: { if (!RequireArguments(arguments, 1, result2, file, token)) { return true; } if (arguments[0].Type != StfoValueType.Text) { result = InvalidBuiltinType(result2, file, token); return true; } if (!TryResolveRuntimeFile(arguments[0].Text, file, out string filePath, out string error)) { ReportFatal(StfoDiagnosticCode.ImportOutsideWorkspace, error, file, token); return true; } if (!File.Exists(filePath)) { ReportFatal(StfoDiagnosticCode.IoError, "FILE NOT FOUND.", file, token); return true; } string text = File.ReadAllText(filePath); if (text.Length > _policy.MaximumStringLength) { ReportFatal(StfoDiagnosticCode.StringLimit, "FILE CONTENT EXCEEDS MAXIMUM STRING LENGTH.", file, token); return true; } result = StfoValue.FromText(text); return true; } case StfoBuiltinFunction.Write: { if (!RequireArguments(arguments, 2, result2, file, token)) { return true; } if (arguments[0].Type != StfoValueType.Text || arguments[1].Type != StfoValueType.Text) { result = InvalidBuiltinType(result2, file, token); return true; } if (!_allowFileWrites) { ReportFatal(StfoDiagnosticCode.FileWriteDisabled, "SCRIPT FILE WRITES ARE DISABLED.", file, token); return true; } if (!TryResolveRuntimeFile(arguments[0].Text, file, out string filePath2, out string error2)) { ReportFatal(StfoDiagnosticCode.ImportOutsideWorkspace, error2, file, token); return true; } string directoryName = Path.GetDirectoryName(filePath2); if (!string.IsNullOrEmpty(directoryName)) { Directory.CreateDirectory(directoryName); } File.WriteAllText(filePath2, arguments[1].Text); result = StfoValue.FromBoolean(value: true); return true; } case StfoBuiltinFunction.Input: case StfoBuiltinFunction.InputNumber: case StfoBuiltinFunction.InputBool: { if (!RequireArguments(arguments, 1, result2, file, token)) { return true; } if (arguments[0].Type != StfoValueType.Text) { result = InvalidBuiltinType(result2, file, token); return true; } StfoInputKind stfoInputKind = result2 switch { StfoBuiltinFunction.InputNumber => StfoInputKind.Number, StfoBuiltinFunction.InputBool => StfoInputKind.Boolean, _ => StfoInputKind.Text, }; if (_inputValues.Count == 0) { _inputRequest = new StfoInputRequest(stfoInputKind, arguments[0].Text); _stopped = true; return true; } string text2 = _inputValues.Dequeue(); result = stfoInputKind switch { StfoInputKind.Number => StfoValue.FromNumber(double.Parse(text2, CultureInfo.InvariantCulture)), StfoInputKind.Boolean => StfoValue.FromBoolean(ParseBooleanInput(text2)), _ => StfoValue.FromText(text2), }; return true; } case StfoBuiltinFunction.Option: { if (!RequireArguments(arguments, 2, result2, file, token)) { return true; } if (arguments[0].Type != StfoValueType.Text || arguments[1].Type != StfoValueType.List || arguments[1].List.Any((StfoValue item) => item.Type != StfoValueType.Text)) { result = InvalidBuiltinType(result2, file, token); return true; } string[] array = arguments[1].List.Select((StfoValue item) => item.Text).ToArray(); if (array.Length == 0) { result = InvalidBuiltinType(result2, file, token); return true; } if (_inputValues.Count == 0) { _inputRequest = new StfoInputRequest(StfoInputKind.Option, arguments[0].Text, array); _stopped = true; return true; } result = StfoValue.FromText(_inputValues.Dequeue()); return true; } case StfoBuiltinFunction.Count: if (!RequireArguments(arguments, 1, result2, file, token)) { return true; } result = arguments[0].Type switch { StfoValueType.List => StfoValue.FromNumber(arguments[0].List.Count), StfoValueType.Text => StfoValue.FromNumber(arguments[0].Text.Length), _ => InvalidBuiltinType(result2, file, token), }; return true; case StfoBuiltinFunction.Take: { if (!RequireArguments(arguments, 2, result2, file, token)) { return true; } if (arguments[0].Type != StfoValueType.List || arguments[1].Type != StfoValueType.Number) { result = InvalidBuiltinType(result2, file, token); return true; } int count = Math.Max(0, (int)arguments[1].Number); result = StfoValue.FromList(arguments[0].List.Take(count).ToArray()); return true; } case StfoBuiltinFunction.Where: { if (!RequireArguments(arguments, 3, result2, file, token)) { return true; } if (arguments[0].Type != StfoValueType.List || arguments[1].Type != StfoValueType.Text) { result = InvalidBuiltinType(result2, file, token); return true; } string field = arguments[1].Text; string wanted = arguments[2].ToString(); result = StfoValue.FromList(arguments[0].List.Where((StfoValue item) => item.Type == StfoValueType.Map && item.Map.TryGetValue(field, out var value) && value.ToString().Contains(wanted, StringComparison.OrdinalIgnoreCase)).ToArray()); return true; } case StfoBuiltinFunction.Sort: { if (!RequireArguments(arguments, 2, result2, file, token)) { return true; } if (arguments[0].Type != StfoValueType.List || arguments[1].Type != StfoValueType.Text) { result = InvalidBuiltinType(result2, file, token); return true; } string sortField = arguments[1].Text; result = StfoValue.FromList(arguments[0].List.OrderBy((StfoValue item) => (item.Type != StfoValueType.Map || !item.Map.TryGetValue(sortField, out var value)) ? string.Empty : value.ToString(), StringComparer.OrdinalIgnoreCase).ToArray()); return true; } default: return false; } } private bool RequireArguments(IReadOnlyList arguments, int expected, StfoBuiltinFunction function, string file, StfoToken token) { if (arguments.Count == expected) { return true; } ReportFatal(StfoDiagnosticCode.ArgumentCountMismatch, $"{function.ToString().ToUpperInvariant()} EXPECTS {expected} ARGUMENTS.", file, token); return false; } private StfoValue InvalidBuiltinType(StfoBuiltinFunction function, string file, StfoToken token) { ReportFatal(StfoDiagnosticCode.InvalidOperation, "INVALID ARGUMENT TYPE FOR " + function.ToString().ToUpperInvariant() + ".", file, token); return StfoValue.None; } private StfoValue EvaluateUnary(StfoUnaryExpressionSyntax expression, string file) { StfoValue result = Evaluate(expression.Operand, file); if (result.Type != StfoValueType.Number) { ReportFatal(StfoDiagnosticCode.InvalidOperation, "UNARY OPERATOR EXPECTS NUMBER.", file, expression.Token); return StfoValue.None; } if (expression.Operator != StfoTokenKind.Minus) { return result; } return StfoValue.FromNumber(0.0 - result.Number); } private StfoValue EvaluateBinary(StfoBinaryExpressionSyntax expression, string file) { StfoValue stfoValue = Evaluate(expression.Left, file); StfoValue stfoValue2 = Evaluate(expression.Right, file); if (_stopped) { return StfoValue.None; } if (expression.Operator == StfoTokenKind.Plus && (stfoValue.Type == StfoValueType.Text || stfoValue2.Type == StfoValueType.Text)) { string text = stfoValue.ToString() + stfoValue2; if (text.Length > _policy.MaximumStringLength) { ReportFatal(StfoDiagnosticCode.StringLimit, "MAXIMUM STRING LENGTH EXCEEDED.", file, expression.Token); return StfoValue.None; } return StfoValue.FromText(text); } StfoTokenKind stfoTokenKind = expression.Operator; if ((uint)(stfoTokenKind - 19) <= 1u) { bool flag = stfoValue.Type == stfoValue2.Type && object.Equals(stfoValue.Raw, stfoValue2.Raw); return StfoValue.FromBoolean((expression.Operator == StfoTokenKind.EqualEqual) ? flag : (!flag)); } if (stfoValue.Type != StfoValueType.Number || stfoValue2.Type != StfoValueType.Number) { ReportFatal(StfoDiagnosticCode.InvalidOperation, "BINARY OPERATOR EXPECTS NUMBERS.", file, expression.Token); return StfoValue.None; } if (expression.Operator == StfoTokenKind.Slash && stfoValue2.Number == 0.0) { ReportFatal(StfoDiagnosticCode.DivisionByZero, "DIVISION BY ZERO.", file, expression.Token); return StfoValue.None; } return expression.Operator switch { StfoTokenKind.Plus => StfoValue.FromNumber(stfoValue.Number + stfoValue2.Number), StfoTokenKind.Minus => StfoValue.FromNumber(stfoValue.Number - stfoValue2.Number), StfoTokenKind.Star => StfoValue.FromNumber(stfoValue.Number * stfoValue2.Number), StfoTokenKind.Slash => StfoValue.FromNumber(stfoValue.Number / stfoValue2.Number), StfoTokenKind.Less => StfoValue.FromBoolean(stfoValue.Number < stfoValue2.Number), StfoTokenKind.LessOrEqual => StfoValue.FromBoolean(stfoValue.Number <= stfoValue2.Number), StfoTokenKind.Greater => StfoValue.FromBoolean(stfoValue.Number > stfoValue2.Number), StfoTokenKind.GreaterOrEqual => StfoValue.FromBoolean(stfoValue.Number >= stfoValue2.Number), _ => StfoValue.None, }; } private StfoValue GetVariable(StfoNameExpressionSyntax expression, string file) { if (_localScopes.Count > 0 && _localScopes.Peek().TryGetValue(expression.Name, out var value)) { return value; } if (_variables.TryGetValue(expression.Name, out var value2)) { return value2; } ReportFatal(StfoDiagnosticCode.UndefinedVariable, "UNDEFINED VARIABLE: " + expression.Name, file, expression.Token); return StfoValue.None; } private void SetVariable(string name, StfoValue value, StfoToken token, string file) { if (_localScopes.Count > 0) { _localScopes.Peek()[name] = value; } else { _variables[name] = value; } UpdateMemory(token, file); } private bool ConsumeInstruction(StfoToken token, string file) { _instructions++; if (_instructions > _policy.MaximumInstructions) { ReportFatal(StfoDiagnosticCode.InstructionLimit, "MAXIMUM INSTRUCTION COUNT EXCEEDED.", file, token); return false; } if (_stopwatch.Elapsed.TotalSeconds > (double)_policy.MaximumExecutionSeconds) { ReportFatal(StfoDiagnosticCode.TimeLimit, "MAXIMUM EXECUTION TIME EXCEEDED.", file, token); return false; } return true; } private void UpdateMemory(StfoToken token, string file) { _estimatedMemoryBytes = _variables.Sum>((KeyValuePair item) => item.Key.Length * 2 + item.Value.EstimatedBytes) + _localScopes.Sum((Dictionary scope) => scope.Sum((KeyValuePair item) => item.Key.Length * 2 + item.Value.EstimatedBytes)) + _printedLines.Sum((string line) => line.Length * 2) + _output.EstimatedBytes; if (_estimatedMemoryBytes > (long)_policy.MaximumMemoryKB * 1024L) { ReportFatal(StfoDiagnosticCode.MemoryLimit, "MAXIMUM SCRIPT MEMORY EXCEEDED.", file, token); } } private void ReportFatal(StfoDiagnosticCode code, string message, string file, StfoToken token) { _diagnostics.Add(new StfoDiagnostic(code, StfoDiagnosticSeverity.Error, message, new StfoSourceLocation(file, token.Line, token.Column, Math.Max(1, token.Text.Length)))); _stopped = true; } private static StfoValue FromLiteral(object? value) { if (value != null) { if (!(value is double value2)) { if (!(value is bool value3)) { if (value is string value4) { return StfoValue.FromText(value4); } return StfoValue.None; } return StfoValue.FromBoolean(value3); } return StfoValue.FromNumber(value2); } return StfoValue.None; } private string Relative(string path) { return Path.GetRelativePath(_workspace.RootDirectory, path).Replace(Path.DirectorySeparatorChar, '/'); } private bool TryResolveRuntimeFile(string requestedPath, string currentFile, out string filePath, out string error) { string fullPath = Path.GetFullPath(Path.Combine(_workspace.RootDirectory, currentFile)); return _workspace.TryResolveFile(requestedPath, out filePath, out error, Path.GetDirectoryName(fullPath)); } private static bool ParseBooleanInput(string input) { if (!input.Equals("TRUE", StringComparison.OrdinalIgnoreCase) && !input.Equals("ON", StringComparison.OrdinalIgnoreCase) && !input.Equals("YES", StringComparison.OrdinalIgnoreCase)) { return input.Equals("SI", StringComparison.OrdinalIgnoreCase); } return true; } } internal sealed class StfoReturnSignal : Exception { internal StfoValue Value { get; } internal StfoReturnSignal(StfoValue value) { Value = value; } } internal enum StfoBuiltinFunction { Read, Write, Input, InputNumber, InputBool, Option, Count, Take, Where, Sort } internal sealed record StfoFunctionBinding(StfoFunctionStatementSyntax Syntax, string SourcePath); } namespace BetterTerminal.Scripts.Language { internal interface IStfoWorkspace { string RootDirectory { get; } bool TryResolveScript(string scriptName, out string scriptPath, out string error, string? relativeToDirectory = null); bool TryResolveFile(string requestedPath, out string filePath, out string error, string? relativeToDirectory = null); } internal sealed record StfoAnalysisResult(IReadOnlyList Diagnostics, IReadOnlyList Files) { internal bool HasErrors => Diagnostics.Any((StfoDiagnostic item) => item.Severity == StfoDiagnosticSeverity.Error); [CompilerGenerated] private bool PrintMembers(StringBuilder builder) { RuntimeHelpers.EnsureSufficientExecutionStack(); builder.Append("Diagnostics = "); builder.Append(Diagnostics); builder.Append(", Files = "); builder.Append(Files); return true; } } internal sealed class StfoAnalyzer { private readonly StfoPolicy _policy; private readonly IStfoWorkspace _workspace; private readonly List _diagnostics = new List(); private readonly HashSet _visited = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly HashSet _active = new HashSet(StringComparer.OrdinalIgnoreCase); internal StfoAnalyzer(StfoPolicy policy, IStfoWorkspace workspace) { _policy = policy; _workspace = workspace; } internal StfoAnalysisResult Analyze(string entryPath) { AnalyzeFile(Path.GetFullPath(entryPath), 0); return new StfoAnalysisResult(_diagnostics.OrderBy((StfoDiagnostic item) => item.Location.File, StringComparer.OrdinalIgnoreCase).ThenBy((StfoDiagnostic item) => item.Location.Line).ThenBy((StfoDiagnostic item) => item.Location.Column) .ToArray(), _visited.OrderBy((string path) => path, StringComparer.OrdinalIgnoreCase).ToArray()); } private void AnalyzeFile(string path, int depth) { string text = Path.GetRelativePath(_workspace.RootDirectory, path).Replace(Path.DirectorySeparatorChar, '/'); if (depth > _policy.MaximumImportDepth) { Add(StfoDiagnosticCode.ImportDepthExceeded, StfoDiagnosticSeverity.Error, $"IMPORT DEPTH EXCEEDS {_policy.MaximumImportDepth}.", text, 1, 1, 1); } else if (_active.Contains(path)) { Add(StfoDiagnosticCode.CircularImport, StfoDiagnosticSeverity.Error, "CIRCULAR IMPORT DETECTED.", text, 1, 1, 1); } else { if (!_visited.Add(path)) { return; } if (File.Exists(path)) { try { long num = (long)_policy.MaximumSourceKB * 1024L; if (new FileInfo(path).Length > num) { Add(StfoDiagnosticCode.SourceTooLarge, StfoDiagnosticSeverity.Error, $"SOURCE EXCEEDS {_policy.MaximumSourceKB} KB.", text, 1, 1, 1); } else { string source = File.ReadAllText(path); StfoLexResult stfoLexResult = new StfoLexer(text, source).Lex(); _diagnostics.AddRange(stfoLexResult.Diagnostics); if (!stfoLexResult.Diagnostics.Any((StfoDiagnostic item) => item.Severity == StfoDiagnosticSeverity.Error)) { StfoProgramSyntax stfoProgramSyntax = new StfoParser(text, stfoLexResult.Tokens).Parse(); _diagnostics.AddRange(stfoProgramSyntax.Diagnostics); StfoManifest stfoManifest = StfoManifest.Parse(text, stfoLexResult.Tokens); _diagnostics.AddRange(stfoManifest.Diagnostics); ValidateManifest(stfoManifest, stfoProgramSyntax, text); AnalyzeProgramSafety(stfoProgramSyntax, text); } _active.Add(path); AnalyzeDirectives(stfoLexResult.Tokens, text); AnalyzeImports(stfoLexResult.Tokens, path, text, depth); _active.Remove(path); } return; } catch (Exception ex) { Add(StfoDiagnosticCode.IoError, StfoDiagnosticSeverity.Error, "CANNOT READ SCRIPT: " + ex.Message, text, 1, 1, 1); return; } } Add(StfoDiagnosticCode.ImportNotFound, StfoDiagnosticSeverity.Error, "SCRIPT FILE NOT FOUND.", text, 1, 1, 1); } } private void AnalyzeDirectives(IReadOnlyList tokens, string file) { for (int i = 0; i + 2 < tokens.Count; i++) { if (tokens[i].Kind != StfoTokenKind.At) { continue; } StfoToken stfoToken = tokens[i + 1]; StfoToken stfoToken2 = tokens[i + 2]; if (stfoToken.Kind == StfoTokenKind.Identifier && Enum.TryParse(stfoToken.Text, ignoreCase: true, out var _)) { continue; } if (stfoToken.Kind == StfoTokenKind.Identifier && stfoToken.Text.Equals("STFO", StringComparison.OrdinalIgnoreCase) && stfoToken2.Kind == StfoTokenKind.Number) { if (stfoToken2.Text != "1") { Add(StfoDiagnosticCode.UnsupportedLanguageVersion, StfoDiagnosticSeverity.Error, "UNSUPPORTED STFO VERSION " + stfoToken2.Text + ". USE VERSION 1.", file, stfoToken2.Line, stfoToken2.Column, stfoToken2.Text.Length); } continue; } if (stfoToken.Kind != StfoTokenKind.Identifier || stfoToken2.Kind != StfoTokenKind.Number || !TryParseLimit(stfoToken.Text, out var kind) || !int.TryParse(stfoToken2.Text, out var result2)) { Add(StfoDiagnosticCode.InvalidDirective, StfoDiagnosticSeverity.Error, "INVALID LIMIT DIRECTIVE.", file, stfoToken.Line, stfoToken.Column, Math.Max(1, stfoToken.Text.Length)); continue; } int maximum = _policy.GetMaximum(kind); if (result2 > maximum) { Add(StfoDiagnosticCode.LimitExceedsPolicy, StfoDiagnosticSeverity.Error, $"REQUESTED {kind.ToString().ToUpperInvariant()} {result2} EXCEEDS {maximum}.", file, stfoToken2.Line, stfoToken2.Column, stfoToken2.Text.Length); } } } private void AnalyzeImports(IReadOnlyList tokens, string currentPath, string currentFile, int depth) { for (int i = 0; i + 1 < tokens.Count; i++) { StfoToken stfoToken = tokens[i]; if (stfoToken.Kind != StfoTokenKind.Identifier || !stfoToken.Text.Equals("IMPORT", StringComparison.OrdinalIgnoreCase)) { continue; } StfoToken stfoToken2 = tokens[i + 1]; if (stfoToken2.Kind == StfoTokenKind.String) { string text = Unquote(stfoToken2.Text); if (!_workspace.TryResolveScript(text, out string scriptPath, out string error, Path.GetDirectoryName(currentPath))) { Add(StfoDiagnosticCode.ImportOutsideWorkspace, StfoDiagnosticSeverity.Error, error, currentFile, stfoToken2.Line, stfoToken2.Column, stfoToken2.Text.Length); } else if (!File.Exists(scriptPath)) { Add(StfoDiagnosticCode.ImportNotFound, StfoDiagnosticSeverity.Error, "IMPORT NOT FOUND: " + text, currentFile, stfoToken2.Line, stfoToken2.Column, stfoToken2.Text.Length); } else { AnalyzeFile(scriptPath, depth + 1); } } } } private static bool TryParseLimit(string text, out StfoLimitKind kind) { string text2 = text.Replace("_", string.Empty); if (text2.Equals("TIMEOUT", StringComparison.OrdinalIgnoreCase)) { text2 = "TimeoutSeconds"; } else if (text2.Equals("MEMORY", StringComparison.OrdinalIgnoreCase)) { text2 = "MemoryKB"; } else if (text2.Equals("CALLDEPTH", StringComparison.OrdinalIgnoreCase)) { text2 = "CallDepth"; } else if (text2.Equals("IMPORTDEPTH", StringComparison.OrdinalIgnoreCase)) { text2 = "ImportDepth"; } else if (text2.Equals("SOURCE", StringComparison.OrdinalIgnoreCase)) { text2 = "SourceKB"; } return Enum.TryParse(text2, ignoreCase: true, out kind); } private void AnalyzeProgramSafety(StfoProgramSyntax program, string file) { foreach (StfoFunctionStatementSyntax item in program.Statements.OfType()) { bool num = ContainsCall(item.Body, item.Name); bool flag = ContainsConditional(item.Body); if (num && !flag) { Add(StfoDiagnosticCode.PossibleUnboundedRecursion, StfoDiagnosticSeverity.Warning, "FUNCTION " + item.Name + " CALLS ITSELF WITHOUT A DETECTABLE IF GUARD.", file, item.Token.Line, item.Token.Column, item.Token.Text.Length); } } } private void ValidateManifest(StfoManifest manifest, StfoProgramSyntax program, string file) { StfoEntryPointKind[] values = Enum.GetValues(); for (int i = 0; i < values.Length; i++) { StfoEntryPointKind stfoEntryPointKind = values[i]; StfoEntryPoint entryPoint = manifest.GetEntryPoint(stfoEntryPointKind); if ((object)entryPoint == null) { continue; } StfoFunctionStatementSyntax stfoFunctionStatementSyntax = program.Statements.OfType().FirstOrDefault((StfoFunctionStatementSyntax item) => item.Name.Equals(entryPoint.FunctionName, StringComparison.OrdinalIgnoreCase)); if ((object)stfoFunctionStatementSyntax == null) { Add(StfoDiagnosticCode.EntryPointNotFound, StfoDiagnosticSeverity.Error, "ENTRY FUNCTION NOT FOUND: " + entryPoint.FunctionName, file, entryPoint.Token.Line, entryPoint.Token.Column, entryPoint.Token.Text.Length); continue; } int num = ((stfoEntryPointKind != StfoEntryPointKind.Program) ? 1 : 0); if (stfoFunctionStatementSyntax.Parameters.Count != num) { Add(StfoDiagnosticCode.InvalidEntryPointSignature, StfoDiagnosticSeverity.Error, $"{stfoEntryPointKind.ToString().ToUpperInvariant()} ENTRY FUNCTION MUST HAVE {num} PARAMETER(S).", file, stfoFunctionStatementSyntax.Token.Line, stfoFunctionStatementSyntax.Token.Column, stfoFunctionStatementSyntax.Token.Text.Length); } } } private static bool ContainsConditional(IEnumerable statements) { return statements.Any(delegate(StfoStatementSyntax statement) { if (statement is StfoIfStatementSyntax) { return true; } if (statement is StfoForStatementSyntax stfoForStatementSyntax) { return ContainsConditional(stfoForStatementSyntax.Body); } return statement is StfoWhileStatementSyntax stfoWhileStatementSyntax && ContainsConditional(stfoWhileStatementSyntax.Body); }); } private static bool ContainsCall(IEnumerable statements, string functionName) { return statements.Any(delegate(StfoStatementSyntax statement) { if (statement is StfoAssignmentStatementSyntax stfoAssignmentStatementSyntax) { return ContainsCall(stfoAssignmentStatementSyntax.Expression, functionName); } if (statement is StfoPrintStatementSyntax stfoPrintStatementSyntax) { return ContainsCall(stfoPrintStatementSyntax.Expression, functionName); } if (statement is StfoOutputStatementSyntax stfoOutputStatementSyntax) { return ContainsCall(stfoOutputStatementSyntax.Expression, functionName); } if (statement is StfoExpressionStatementSyntax stfoExpressionStatementSyntax) { return ContainsCall(stfoExpressionStatementSyntax.Expression, functionName); } if (statement is StfoReturnStatementSyntax stfoReturnStatementSyntax) { return ContainsCall(stfoReturnStatementSyntax.Expression, functionName); } if (statement is StfoIfStatementSyntax stfoIfStatementSyntax) { return ContainsCall(stfoIfStatementSyntax.Condition, functionName) || ContainsCall(stfoIfStatementSyntax.ThenStatements, functionName) || ContainsCall(stfoIfStatementSyntax.ElseStatements, functionName); } if (statement is StfoForStatementSyntax stfoForStatementSyntax) { return ContainsCall(stfoForStatementSyntax.Collection, functionName) || ContainsCall(stfoForStatementSyntax.Body, functionName); } return statement is StfoWhileStatementSyntax stfoWhileStatementSyntax && (ContainsCall(stfoWhileStatementSyntax.Condition, functionName) || ContainsCall(stfoWhileStatementSyntax.Body, functionName)); }); } private static bool ContainsCall(StfoExpressionSyntax expression, string functionName) { if (!(expression is StfoCallExpressionSyntax stfoCallExpressionSyntax)) { if (!(expression is StfoUnaryExpressionSyntax stfoUnaryExpressionSyntax)) { if (!(expression is StfoBinaryExpressionSyntax stfoBinaryExpressionSyntax)) { if (expression is StfoListExpressionSyntax stfoListExpressionSyntax) { return stfoListExpressionSyntax.Items.Any((StfoExpressionSyntax item) => ContainsCall(item, functionName)); } return false; } return ContainsCall(stfoBinaryExpressionSyntax.Left, functionName) || ContainsCall(stfoBinaryExpressionSyntax.Right, functionName); } return ContainsCall(stfoUnaryExpressionSyntax.Operand, functionName); } return stfoCallExpressionSyntax.Name.Equals(functionName, StringComparison.OrdinalIgnoreCase) || stfoCallExpressionSyntax.Arguments.Any((StfoExpressionSyntax argument) => ContainsCall(argument, functionName)); } private static string Unquote(string value) { if (value.Length < 2) { return value; } return value.Substring(1, value.Length - 1 - 1); } private void Add(StfoDiagnosticCode code, StfoDiagnosticSeverity severity, string message, string file, int line, int column, int length) { _diagnostics.Add(new StfoDiagnostic(code, severity, message, new StfoSourceLocation(file, line, column, length))); } } internal enum StfoDiagnosticSeverity { Info, Warning, Error } internal enum StfoDiagnosticCode { InvalidCharacter = 1001, UnterminatedString = 1002, InvalidNumber = 1003, SourceTooLarge = 1004, InvalidDirective = 2001, LimitExceedsPolicy = 2002, UnsupportedLanguageVersion = 2003, SyntaxError = 2101, UndefinedVariable = 2102, InvalidOperation = 2103, DivisionByZero = 2104, PossibleUnboundedRecursion = 2201, DuplicateEntryPoint = 2301, EntryPointNotFound = 2302, InvalidEntryPointSignature = 2303, MissingProgramEntryPoint = 2304, MissingPipelineEntryPoint = 2305, InstructionLimit = 5001, MemoryLimit = 5002, TimeLimit = 5003, CallDepthLimit = 5004, UnknownFunction = 5005, ArgumentCountMismatch = 5006, ReturnOutsideFunction = 5007, CollectionLimit = 5008, StringLimit = 5009, FileWriteDisabled = 5010, ImportNotFound = 3001, ImportOutsideWorkspace = 3002, CircularImport = 3003, ImportDepthExceeded = 3004, IoError = 4001 } internal sealed record StfoSourceLocation(string File, int Line, int Column, int Length); internal sealed record StfoDiagnostic(StfoDiagnosticCode Code, StfoDiagnosticSeverity Severity, string Message, StfoSourceLocation Location) { internal string Format() { return $"{Severity.ToString().ToUpperInvariant()} STFO{Code:D4} {Location.File}:{Location.Line}:{Location.Column} " + Message; } } internal sealed class StfoLexer { private readonly string _file; private readonly string _source; private readonly List _tokens = new List(); private readonly List _diagnostics = new List(); private int _offset; private int _line = 1; private int _column = 1; private bool IsAtEnd => _offset >= _source.Length; private char Current { get { if (!IsAtEnd) { return _source[_offset]; } return '\0'; } } internal StfoLexer(string file, string source) { _file = file; _source = source; } internal StfoLexResult Lex() { while (!IsAtEnd) { char current = Current; if ((current == '\t' || current == '\r' || current == ' ') ? true : false) { Advance(); continue; } switch (current) { case '#': SkipComment(); continue; case '\n': AddSingle(StfoTokenKind.NewLine); _line++; _column = 1; continue; } if (IsIdentifierStart(current)) { ReadIdentifier(); } else if (char.IsDigit(current)) { ReadNumber(); } else if ((current == '"' || current == '\'') ? true : false) { ReadString(current); } else { ReadSymbol(); } } _tokens.Add(new StfoToken(StfoTokenKind.EndOfFile, string.Empty, _line, _column, _offset)); return new StfoLexResult(_tokens, _diagnostics); } private void ReadIdentifier() { int offset = _offset; int line = _line; int column = _column; while (!IsAtEnd && IsIdentifierPart(Current)) { Advance(); } AddToken(StfoTokenKind.Identifier, offset, line, column); } private void ReadNumber() { int offset = _offset; int line = _line; int column = _column; while (!IsAtEnd && char.IsDigit(Current)) { Advance(); } if (!IsAtEnd && Current == '.' && char.IsDigit(Peek(1))) { Advance(); while (!IsAtEnd && char.IsDigit(Current)) { Advance(); } } string source = _source; int num = offset; string text = source.Substring(num, _offset - num); if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var _)) { AddDiagnostic(StfoDiagnosticCode.InvalidNumber, StfoDiagnosticSeverity.Error, "INVALID NUMBER '" + text + "'.", line, column, text.Length); } _tokens.Add(new StfoToken(StfoTokenKind.Number, text, line, column, offset)); } private void ReadString(char quote) { int offset = _offset; int line = _line; int column = _column; Advance(); bool flag = false; while (!IsAtEnd && Current != '\n') { if (Current == '\\' && Peek(1) != 0) { Advance(); Advance(); continue; } if (Current == quote) { Advance(); flag = true; break; } Advance(); } if (!flag) { AddDiagnostic(StfoDiagnosticCode.UnterminatedString, StfoDiagnosticSeverity.Error, "UNTERMINATED STRING.", line, column, Math.Max(1, _offset - offset)); } AddToken(StfoTokenKind.String, offset, line, column); } private void ReadSymbol() { int line = _line; int column = _column; StfoTokenKind? stfoTokenKind; switch (Current) { case '@': stfoTokenKind = StfoTokenKind.At; break; case '(': stfoTokenKind = StfoTokenKind.LeftParenthesis; break; case ')': stfoTokenKind = StfoTokenKind.RightParenthesis; break; case '[': stfoTokenKind = StfoTokenKind.LeftBracket; break; case ']': stfoTokenKind = StfoTokenKind.RightBracket; break; case ',': stfoTokenKind = StfoTokenKind.Comma; break; case ':': stfoTokenKind = StfoTokenKind.Colon; break; case '.': stfoTokenKind = StfoTokenKind.Dot; break; case '|': stfoTokenKind = StfoTokenKind.Pipe; break; case '+': stfoTokenKind = StfoTokenKind.Plus; break; case '-': stfoTokenKind = StfoTokenKind.Minus; break; case '*': stfoTokenKind = StfoTokenKind.Star; break; case '/': stfoTokenKind = StfoTokenKind.Slash; break; case '=': stfoTokenKind = ((Peek(1) != '=') ? new StfoTokenKind?(StfoTokenKind.Assign) : new StfoTokenKind?(StfoTokenKind.EqualEqual)); break; case '!': if (Peek(1) == '=') { stfoTokenKind = StfoTokenKind.NotEqual; break; } goto default; case '<': stfoTokenKind = ((Peek(1) != '=') ? new StfoTokenKind?(StfoTokenKind.Less) : new StfoTokenKind?(StfoTokenKind.LessOrEqual)); break; case '>': stfoTokenKind = ((Peek(1) != '=') ? new StfoTokenKind?(StfoTokenKind.Greater) : new StfoTokenKind?(StfoTokenKind.GreaterOrEqual)); break; default: stfoTokenKind = null; break; } StfoTokenKind? stfoTokenKind2 = stfoTokenKind; if (!stfoTokenKind2.HasValue) { AddDiagnostic(StfoDiagnosticCode.InvalidCharacter, StfoDiagnosticSeverity.Error, $"INVALID CHARACTER '{Current}'.", line, column, 1); Advance(); return; } bool flag; switch (stfoTokenKind2) { case StfoTokenKind.EqualEqual: case StfoTokenKind.NotEqual: case StfoTokenKind.LessOrEqual: case StfoTokenKind.GreaterOrEqual: flag = true; break; default: flag = false; break; } int num = ((!flag) ? 1 : 2); int offset = _offset; for (int i = 0; i < num; i++) { Advance(); } _tokens.Add(new StfoToken(stfoTokenKind2.Value, _source.Substring(offset, num), line, column, offset)); } private void AddSingle(StfoTokenKind kind) { int offset = _offset; int line = _line; int column = _column; Advance(); _tokens.Add(new StfoToken(kind, _source[offset].ToString(), line, column, offset)); } private void AddToken(StfoTokenKind kind, int start, int line, int column) { _tokens.Add(new StfoToken(kind, _source.Substring(start, _offset - start), line, column, start)); } private void SkipComment() { while (!IsAtEnd && Current != '\n') { Advance(); } } private void AddDiagnostic(StfoDiagnosticCode code, StfoDiagnosticSeverity severity, string message, int line, int column, int length) { _diagnostics.Add(new StfoDiagnostic(code, severity, message, new StfoSourceLocation(_file, line, column, length))); } private char Peek(int distance) { if (_offset + distance < _source.Length) { return _source[_offset + distance]; } return '\0'; } private void Advance() { _offset++; _column++; } private static bool IsIdentifierStart(char value) { if (!char.IsLetter(value)) { return value == '_'; } return true; } private static bool IsIdentifierPart(char value) { if (!char.IsLetterOrDigit(value)) { return value == '_'; } return true; } } internal enum StfoEntryPointKind { Program, Pipeline } internal sealed record StfoEntryPoint(StfoEntryPointKind Kind, string FunctionName, StfoToken Token); internal sealed record StfoManifest(StfoEntryPoint? Program, StfoEntryPoint? Pipeline, IReadOnlyList Diagnostics) { internal StfoEntryPoint? GetEntryPoint(StfoEntryPointKind kind) { return kind switch { StfoEntryPointKind.Program => Program, StfoEntryPointKind.Pipeline => Pipeline, _ => throw new ArgumentOutOfRangeException("kind"), }; } internal static StfoManifest Parse(string file, IReadOnlyList tokens) { StfoEntryPoint stfoEntryPoint = null; StfoEntryPoint stfoEntryPoint2 = null; List list = new List(); for (int i = 0; i + 2 < tokens.Count; i++) { if (tokens[i].Kind == StfoTokenKind.At && tokens[i + 1].Kind == StfoTokenKind.Identifier && tokens[i + 2].Kind == StfoTokenKind.Identifier && Enum.TryParse(tokens[i + 1].Text, ignoreCase: true, out var result)) { StfoEntryPoint stfoEntryPoint3 = new StfoEntryPoint(result, tokens[i + 2].Text, tokens[i + 1]); if (result switch { StfoEntryPointKind.Program => stfoEntryPoint, StfoEntryPointKind.Pipeline => stfoEntryPoint2, _ => null, } != null) { list.Add(new StfoDiagnostic(StfoDiagnosticCode.DuplicateEntryPoint, StfoDiagnosticSeverity.Error, "DUPLICATE " + result.ToString().ToUpperInvariant() + " ENTRY POINT.", new StfoSourceLocation(file, tokens[i + 1].Line, tokens[i + 1].Column, tokens[i + 1].Text.Length))); } else if (result == StfoEntryPointKind.Program) { stfoEntryPoint = stfoEntryPoint3; } else { stfoEntryPoint2 = stfoEntryPoint3; } } } return new StfoManifest(stfoEntryPoint, stfoEntryPoint2, list); } } internal sealed class StfoParser { private readonly string _file; private readonly IReadOnlyList _tokens; private readonly List _diagnostics = new List(); private int _position; private StfoToken Current => Peek(0); internal StfoParser(string file, IReadOnlyList tokens) { _file = file; _tokens = tokens; } internal StfoProgramSyntax Parse() { return new StfoProgramSyntax(ParseStatementList(), _diagnostics); } private IReadOnlyList ParseStatementList(params string[] stopKeywords) { List list = new List(); while (Current.Kind != StfoTokenKind.EndOfFile) { SkipNewLines(); if (Current.Kind == StfoTokenKind.EndOfFile || stopKeywords.Any(IsKeyword)) { break; } if (Current.Kind == StfoTokenKind.At) { SkipLine(); continue; } StfoStatementSyntax stfoStatementSyntax = ParseStatement(); if ((object)stfoStatementSyntax != null) { list.Add(stfoStatementSyntax); } StfoTokenKind kind = Current.Kind; if ((uint)kind > 1u) { Report(Current, "EXPECTED END OF LINE."); SkipLine(); } } return list; } private StfoStatementSyntax? ParseStatement() { if (IsKeyword("IMPORT")) { return ParseImport(); } if (IsKeyword("PRINT")) { return ParseCommandExpression(isOutput: false); } if (IsKeyword("OUTPUT")) { return ParseCommandExpression(isOutput: true); } if (IsKeyword("IF")) { return ParseIf(); } if (IsKeyword("FUNCTION")) { return ParseFunction(); } if (IsKeyword("RETURN")) { return ParseReturn(); } if (IsKeyword("FOR")) { return ParseFor(); } if (IsKeyword("WHILE")) { return ParseWhile(); } if (Current.Kind == StfoTokenKind.Identifier && Peek(1).Kind == StfoTokenKind.Assign) { StfoToken stfoToken = NextToken(); NextToken(); return new StfoAssignmentStatementSyntax(Expression: ParseExpression(), Token: stfoToken, Name: stfoToken.Text); } StfoExpressionSyntax stfoExpressionSyntax = ParseExpression(); return new StfoExpressionStatementSyntax(stfoExpressionSyntax.Token, stfoExpressionSyntax); } private StfoStatementSyntax ParseIf() { StfoToken stfoToken = NextToken(); StfoExpressionSyntax condition = ParseExpression(); RequireBlockLineEnd(); IReadOnlyList thenStatements = ParseStatementList("ELSE", "END"); IReadOnlyList elseStatements = Array.Empty(); if (IsKeyword("ELSE")) { NextToken(); RequireBlockLineEnd(); elseStatements = ParseStatementList("END"); } RequireEnd(stfoToken); return new StfoIfStatementSyntax(stfoToken, condition, thenStatements, elseStatements); } private StfoStatementSyntax ParseFunction() { StfoToken stfoToken = NextToken(); if (Current.Kind != StfoTokenKind.Identifier) { Report(Current, "FUNCTION EXPECTS A NAME."); SkipLine(); return new StfoFunctionStatementSyntax(stfoToken, "INVALID", Array.Empty(), Array.Empty()); } string text = NextToken().Text; List list = new List(); if (!Match(StfoTokenKind.LeftParenthesis)) { Report(Current, "FUNCTION EXPECTS '('."); } do { StfoTokenKind kind = Current.Kind; if (((uint)kind <= 1u || kind == StfoTokenKind.RightParenthesis) ? true : false) { break; } if (Current.Kind != StfoTokenKind.Identifier) { Report(Current, "EXPECTED PARAMETER NAME."); break; } list.Add(NextToken().Text); } while (Match(StfoTokenKind.Comma)); if (!Match(StfoTokenKind.RightParenthesis)) { Report(Current, "FUNCTION EXPECTS ')'."); } RequireBlockLineEnd(); IReadOnlyList body = ParseStatementList("END"); RequireEnd(stfoToken); return new StfoFunctionStatementSyntax(stfoToken, text, list, body); } private StfoStatementSyntax ParseReturn() { return new StfoReturnStatementSyntax(NextToken(), ParseExpression()); } private StfoStatementSyntax ParseFor() { StfoToken stfoToken = NextToken(); if (Current.Kind != StfoTokenKind.Identifier) { Report(Current, "FOR EXPECTS A VARIABLE NAME."); SkipLine(); return new StfoForStatementSyntax(stfoToken, "INVALID", new StfoLiteralExpressionSyntax(stfoToken, null), Array.Empty()); } string text = NextToken().Text; if (!IsKeyword("IN")) { Report(Current, "FOR EXPECTS IN."); } else { NextToken(); } StfoExpressionSyntax collection = ParseExpression(); RequireBlockLineEnd(); IReadOnlyList body = ParseStatementList("END"); RequireEnd(stfoToken); return new StfoForStatementSyntax(stfoToken, text, collection, body); } private StfoStatementSyntax ParseWhile() { StfoToken stfoToken = NextToken(); StfoExpressionSyntax condition = ParseExpression(); RequireBlockLineEnd(); IReadOnlyList body = ParseStatementList("END"); RequireEnd(stfoToken); return new StfoWhileStatementSyntax(stfoToken, condition, body); } private StfoStatementSyntax? ParseImport() { StfoToken token = NextToken(); if (Current.Kind != StfoTokenKind.String) { Report(Current, "IMPORT EXPECTS A STRING PATH."); return null; } return new StfoImportStatementSyntax(token, UnescapeString(NextToken().Text)); } private StfoStatementSyntax ParseCommandExpression(bool isOutput) { StfoToken token = NextToken(); bool num = Match(StfoTokenKind.LeftParenthesis); StfoExpressionSyntax expression = ParseExpression(); if (num && !Match(StfoTokenKind.RightParenthesis)) { Report(Current, "EXPECTED ')'."); } if (!isOutput) { return new StfoPrintStatementSyntax(token, expression); } return new StfoOutputStatementSyntax(token, expression); } private StfoExpressionSyntax ParseExpression(int parentPrecedence = 0) { int unaryPrecedence = GetUnaryPrecedence(Current.Kind); StfoExpressionSyntax stfoExpressionSyntax; if (unaryPrecedence > 0) { StfoToken stfoToken = NextToken(); stfoExpressionSyntax = new StfoUnaryExpressionSyntax(Operand: ParseExpression(unaryPrecedence), Token: stfoToken, Operator: stfoToken.Kind); } else { stfoExpressionSyntax = ParsePrimaryExpression(); } while (true) { int binaryPrecedence = GetBinaryPrecedence(Current.Kind); if (binaryPrecedence == 0 || binaryPrecedence <= parentPrecedence) { break; } StfoToken stfoToken2 = NextToken(); StfoExpressionSyntax right = ParseExpression(binaryPrecedence); stfoExpressionSyntax = new StfoBinaryExpressionSyntax(stfoToken2, stfoExpressionSyntax, stfoToken2.Kind, right); } return stfoExpressionSyntax; } private StfoExpressionSyntax ParsePrimaryExpression() { if (Current.Kind == StfoTokenKind.LeftBracket) { return ParseListExpression(); } if (Match(StfoTokenKind.LeftParenthesis)) { StfoExpressionSyntax result = ParseExpression(); if (!Match(StfoTokenKind.RightParenthesis)) { Report(Current, "EXPECTED ')'."); } return result; } StfoToken stfoToken = NextToken(); if (stfoToken.Kind == StfoTokenKind.Number) { double.TryParse(stfoToken.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2); return new StfoLiteralExpressionSyntax(stfoToken, result2); } if (stfoToken.Kind == StfoTokenKind.String) { return new StfoLiteralExpressionSyntax(stfoToken, UnescapeString(stfoToken.Text)); } if (stfoToken.Kind == StfoTokenKind.Identifier) { if (stfoToken.Text.Equals("TRUE", StringComparison.OrdinalIgnoreCase)) { return new StfoLiteralExpressionSyntax(stfoToken, true); } if (stfoToken.Text.Equals("FALSE", StringComparison.OrdinalIgnoreCase)) { return new StfoLiteralExpressionSyntax(stfoToken, false); } if (stfoToken.Text.Equals("NONE", StringComparison.OrdinalIgnoreCase)) { return new StfoLiteralExpressionSyntax(stfoToken, null); } if (Match(StfoTokenKind.LeftParenthesis)) { List list = new List(); do { StfoTokenKind kind = Current.Kind; if (((uint)kind <= 1u || kind == StfoTokenKind.RightParenthesis) ? true : false) { break; } list.Add(ParseExpression()); } while (Match(StfoTokenKind.Comma)); if (!Match(StfoTokenKind.RightParenthesis)) { Report(Current, "EXPECTED ')' AFTER ARGUMENTS."); } return new StfoCallExpressionSyntax(stfoToken, stfoToken.Text, list); } return new StfoNameExpressionSyntax(stfoToken, stfoToken.Text); } Report(stfoToken, $"EXPECTED EXPRESSION, FOUND {stfoToken.Kind}."); return new StfoLiteralExpressionSyntax(stfoToken, null); } private StfoExpressionSyntax ParseListExpression() { StfoToken token = NextToken(); List list = new List(); do { StfoTokenKind kind = Current.Kind; if (((uint)kind <= 1u || kind == StfoTokenKind.RightBracket) ? true : false) { break; } list.Add(ParseExpression()); } while (Match(StfoTokenKind.Comma)); if (!Match(StfoTokenKind.RightBracket)) { Report(Current, "EXPECTED ']'."); } return new StfoListExpressionSyntax(token, list); } private static int GetUnaryPrecedence(StfoTokenKind kind) { if ((uint)(kind - 15) <= 1u) { return 6; } return 0; } private static int GetBinaryPrecedence(StfoTokenKind kind) { switch (kind) { case StfoTokenKind.Star: case StfoTokenKind.Slash: return 5; case StfoTokenKind.Plus: case StfoTokenKind.Minus: return 4; case StfoTokenKind.Less: case StfoTokenKind.LessOrEqual: case StfoTokenKind.Greater: case StfoTokenKind.GreaterOrEqual: return 3; case StfoTokenKind.EqualEqual: case StfoTokenKind.NotEqual: return 2; default: return 0; } } private bool IsKeyword(string keyword) { if (Current.Kind == StfoTokenKind.Identifier) { return Current.Text.Equals(keyword, StringComparison.OrdinalIgnoreCase); } return false; } private bool Match(StfoTokenKind kind) { if (Current.Kind != kind) { return false; } NextToken(); return true; } private void SkipNewLines() { while (Current.Kind == StfoTokenKind.NewLine) { NextToken(); } } private void SkipLine() { while (true) { StfoTokenKind kind = Current.Kind; if ((uint)kind > 1u) { NextToken(); continue; } break; } } private void RequireBlockLineEnd() { if (Current.Kind == StfoTokenKind.NewLine) { NextToken(); } else if (Current.Kind != StfoTokenKind.EndOfFile) { Report(Current, "EXPECTED END OF LINE BEFORE BLOCK."); SkipLine(); if (Current.Kind == StfoTokenKind.NewLine) { NextToken(); } } } private void RequireEnd(StfoToken openingToken) { SkipNewLines(); if (!IsKeyword("END")) { Report(Current, $"EXPECTED END FOR BLOCK STARTED AT LINE {openingToken.Line}."); } else { NextToken(); } } private StfoToken NextToken() { StfoToken current = Current; if (_position < _tokens.Count - 1) { _position++; } return current; } private StfoToken Peek(int offset) { int index = Math.Min(_position + offset, _tokens.Count - 1); return _tokens[index]; } private void Report(StfoToken token, string message) { _diagnostics.Add(new StfoDiagnostic(StfoDiagnosticCode.SyntaxError, StfoDiagnosticSeverity.Error, message, new StfoSourceLocation(_file, token.Line, token.Column, Math.Max(1, token.Text.Length)))); } private static string UnescapeString(string text) { if (text.Length < 2) { return text; } StringBuilder stringBuilder = new StringBuilder(text.Length - 2); for (int i = 1; i < text.Length - 1; i++) { if (text[i] != '\\' || i + 1 >= text.Length - 1) { stringBuilder.Append(text[i]); continue; } i++; StringBuilder stringBuilder2 = stringBuilder; stringBuilder2.Append(text[i] switch { 'n' => '\n', 'r' => '\r', 't' => '\t', '\\' => '\\', '"' => '"', '\'' => '\'', _ => text[i], }); } return stringBuilder.ToString(); } } internal enum StfoLimitKind { Instructions, MemoryKB, TimeoutSeconds, CallDepth, ImportDepth, SourceKB, CollectionSize, StringLength } internal sealed record StfoPolicy(int MaximumInstructions, int MaximumMemoryKB, int MaximumExecutionSeconds, int MaximumCallDepth, int MaximumImportDepth, int MaximumSourceKB, int MaximumCollectionSize, int MaximumStringLength) { internal int GetMaximum(StfoLimitKind kind) { return kind switch { StfoLimitKind.Instructions => MaximumInstructions, StfoLimitKind.MemoryKB => MaximumMemoryKB, StfoLimitKind.TimeoutSeconds => MaximumExecutionSeconds, StfoLimitKind.CallDepth => MaximumCallDepth, StfoLimitKind.ImportDepth => MaximumImportDepth, StfoLimitKind.SourceKB => MaximumSourceKB, StfoLimitKind.CollectionSize => MaximumCollectionSize, StfoLimitKind.StringLength => MaximumStringLength, _ => throw new ArgumentOutOfRangeException("kind"), }; } } internal abstract record StfoSyntaxNode(StfoToken Token); internal sealed record StfoProgramSyntax(IReadOnlyList Statements, IReadOnlyList Diagnostics); internal abstract record StfoStatementSyntax : StfoSyntaxNode { protected StfoStatementSyntax(StfoToken Token) : base(Token) { } [CompilerGenerated] public new void Deconstruct(out StfoToken Token) { Token = base.Token; } } internal sealed record StfoImportStatementSyntax : StfoStatementSyntax { public string Path { get; init; } public StfoImportStatementSyntax(StfoToken Token, string Path) { this.Path = Path; base..ctor(Token); } [CompilerGenerated] public void Deconstruct(out StfoToken Token, out string Path) { Token = base.Token; Path = this.Path; } } internal sealed record StfoAssignmentStatementSyntax : StfoStatementSyntax { public string Name { get; init; } public StfoExpressionSyntax Expression { get; init; } public StfoAssignmentStatementSyntax(StfoToken Token, string Name, StfoExpressionSyntax Expression) { this.Name = Name; this.Expression = Expression; base..ctor(Token); } [CompilerGenerated] public void Deconstruct(out StfoToken Token, out string Name, out StfoExpressionSyntax Expression) { Token = base.Token; Name = this.Name; Expression = this.Expression; } } internal sealed record StfoPrintStatementSyntax : StfoStatementSyntax { public StfoExpressionSyntax Expression { get; init; } public StfoPrintStatementSyntax(StfoToken Token, StfoExpressionSyntax Expression) { this.Expression = Expression; base..ctor(Token); } [CompilerGenerated] public void Deconstruct(out StfoToken Token, out StfoExpressionSyntax Expression) { Token = base.Token; Expression = this.Expression; } } internal sealed record StfoOutputStatementSyntax : StfoStatementSyntax { public StfoExpressionSyntax Expression { get; init; } public StfoOutputStatementSyntax(StfoToken Token, StfoExpressionSyntax Expression) { this.Expression = Expression; base..ctor(Token); } [CompilerGenerated] public void Deconstruct(out StfoToken Token, out StfoExpressionSyntax Expression) { Token = base.Token; Expression = this.Expression; } } internal sealed record StfoExpressionStatementSyntax : StfoStatementSyntax { public StfoExpressionSyntax Expression { get; init; } public StfoExpressionStatementSyntax(StfoToken Token, StfoExpressionSyntax Expression) { this.Expression = Expression; base..ctor(Token); } [CompilerGenerated] public void Deconstruct(out StfoToken Token, out StfoExpressionSyntax Expression) { Token = base.Token; Expression = this.Expression; } } internal sealed record StfoIfStatementSyntax : StfoStatementSyntax { public StfoExpressionSyntax Condition { get; init; } public IReadOnlyList ThenStatements { get; init; } public IReadOnlyList ElseStatements { get; init; } public StfoIfStatementSyntax(StfoToken Token, StfoExpressionSyntax Condition, IReadOnlyList ThenStatements, IReadOnlyList ElseStatements) { this.Condition = Condition; this.ThenStatements = ThenStatements; this.ElseStatements = ElseStatements; base..ctor(Token); } [CompilerGenerated] public void Deconstruct(out StfoToken Token, out StfoExpressionSyntax Condition, out IReadOnlyList ThenStatements, out IReadOnlyList ElseStatements) { Token = base.Token; Condition = this.Condition; ThenStatements = this.ThenStatements; ElseStatements = this.ElseStatements; } } internal sealed record StfoFunctionStatementSyntax : StfoStatementSyntax { public string Name { get; init; } public IReadOnlyList Parameters { get; init; } public IReadOnlyList Body { get; init; } public StfoFunctionStatementSyntax(StfoToken Token, string Name, IReadOnlyList Parameters, IReadOnlyList Body) { this.Name = Name; this.Parameters = Parameters; this.Body = Body; base..ctor(Token); } [CompilerGenerated] public void Deconstruct(out StfoToken Token, out string Name, out IReadOnlyList Parameters, out IReadOnlyList Body) { Token = base.Token; Name = this.Name; Parameters = this.Parameters; Body = this.Body; } } internal sealed record StfoReturnStatementSyntax : StfoStatementSyntax { public StfoExpressionSyntax Expression { get; init; } public StfoReturnStatementSyntax(StfoToken Token, StfoExpressionSyntax Expression) { this.Expression = Expression; base..ctor(Token); } [CompilerGenerated] public void Deconstruct(out StfoToken Token, out StfoExpressionSyntax Expression) { Token = base.Token; Expression = this.Expression; } } internal sealed record StfoForStatementSyntax : StfoStatementSyntax { public string Variable { get; init; } public StfoExpressionSyntax Collection { get; init; } public IReadOnlyList Body { get; init; } public StfoForStatementSyntax(StfoToken Token, string Variable, StfoExpressionSyntax Collection, IReadOnlyList Body) { this.Variable = Variable; this.Collection = Collection; this.Body = Body; base..ctor(Token); } [CompilerGenerated] public void Deconstruct(out StfoToken Token, out string Variable, out StfoExpressionSyntax Collection, out IReadOnlyList Body) { Token = base.Token; Variable = this.Variable; Collection = this.Collection; Body = this.Body; } } internal sealed record StfoWhileStatementSyntax : StfoStatementSyntax { public StfoExpressionSyntax Condition { get; init; } public IReadOnlyList Body { get; init; } public StfoWhileStatementSyntax(StfoToken Token, StfoExpressionSyntax Condition, IReadOnlyList Body) { this.Condition = Condition; this.Body = Body; base..ctor(Token); } [CompilerGenerated] public void Deconstruct(out StfoToken Token, out StfoExpressionSyntax Condition, out IReadOnlyList Body) { Token = base.Token; Condition = this.Condition; Body = this.Body; } } internal abstract record StfoExpressionSyntax : StfoSyntaxNode { protected StfoExpressionSyntax(StfoToken Token) : base(Token) { } [CompilerGenerated] public new void Deconstruct(out StfoToken Token) { Token = base.Token; } } internal sealed record StfoLiteralExpressionSyntax : StfoExpressionSyntax { public object? Value { get; init; } public StfoLiteralExpressionSyntax(StfoToken Token, object? Value) { this.Value = Value; base..ctor(Token); } [CompilerGenerated] public void Deconstruct(out StfoToken Token, out object? Value) { Token = base.Token; Value = this.Value; } } internal sealed record StfoNameExpressionSyntax : StfoExpressionSyntax { public string Name { get; init; } public StfoNameExpressionSyntax(StfoToken Token, string Name) { this.Name = Name; base..ctor(Token); } [CompilerGenerated] public void Deconstruct(out StfoToken Token, out string Name) { Token = base.Token; Name = this.Name; } } internal sealed record StfoUnaryExpressionSyntax : StfoExpressionSyntax { public StfoTokenKind Operator { get; init; } public StfoExpressionSyntax Operand { get; init; } public StfoUnaryExpressionSyntax(StfoToken Token, StfoTokenKind Operator, StfoExpressionSyntax Operand) { this.Operator = Operator; this.Operand = Operand; base..ctor(Token); } [CompilerGenerated] public void Deconstruct(out StfoToken Token, out StfoTokenKind Operator, out StfoExpressionSyntax Operand) { Token = base.Token; Operator = this.Operator; Operand = this.Operand; } } internal sealed record StfoBinaryExpressionSyntax : StfoExpressionSyntax { public StfoExpressionSyntax Left { get; init; } public StfoTokenKind Operator { get; init; } public StfoExpressionSyntax Right { get; init; } public StfoBinaryExpressionSyntax(StfoToken Token, StfoExpressionSyntax Left, StfoTokenKind Operator, StfoExpressionSyntax Right) { this.Left = Left; this.Operator = Operator; this.Right = Right; base..ctor(Token); } [CompilerGenerated] public void Deconstruct(out StfoToken Token, out StfoExpressionSyntax Left, out StfoTokenKind Operator, out StfoExpressionSyntax Right) { Token = base.Token; Left = this.Left; Operator = this.Operator; Right = this.Right; } } internal sealed record StfoCallExpressionSyntax : StfoExpressionSyntax { public string Name { get; init; } public IReadOnlyList Arguments { get; init; } public StfoCallExpressionSyntax(StfoToken Token, string Name, IReadOnlyList Arguments) { this.Name = Name; this.Arguments = Arguments; base..ctor(Token); } [CompilerGenerated] public void Deconstruct(out StfoToken Token, out string Name, out IReadOnlyList Arguments) { Token = base.Token; Name = this.Name; Arguments = this.Arguments; } } internal sealed record StfoListExpressionSyntax : StfoExpressionSyntax { public IReadOnlyList Items { get; init; } public StfoListExpressionSyntax(StfoToken Token, IReadOnlyList Items) { this.Items = Items; base..ctor(Token); } [CompilerGenerated] public void Deconstruct(out StfoToken Token, out IReadOnlyList Items) { Token = base.Token; Items = this.Items; } } internal enum StfoTokenKind { EndOfFile, NewLine, Identifier, Number, String, At, LeftParenthesis, RightParenthesis, LeftBracket, RightBracket, Comma, Colon, Dot, Assign, Pipe, Plus, Minus, Star, Slash, EqualEqual, NotEqual, Less, LessOrEqual, Greater, GreaterOrEqual } internal sealed record StfoToken(StfoTokenKind Kind, string Text, int Line, int Column, int Offset); internal sealed record StfoLexResult(IReadOnlyList Tokens, IReadOnlyList Diagnostics); }