├── .gitignore ├── LICENSE ├── README.md ├── fbl ├── ErrorCodes.cs ├── ErrorHandler.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── State.cs ├── interpretation │ ├── Context.cs │ ├── Interpreter.cs │ ├── Module.cs │ └── modules │ │ └── LanguageModule.cs ├── optimization │ └── Optimizer.cs ├── parsing │ ├── Node.cs │ ├── Parser.cs │ ├── nodes │ │ ├── BlockNode.cs │ │ ├── CoreNode.cs │ │ ├── ExpressionNode.cs │ │ ├── FunctionCallNode.cs │ │ └── data_holders │ │ │ ├── FunctionNode.cs │ │ │ ├── NumberNode.cs │ │ │ ├── StringNode.cs │ │ │ └── VariableNode.cs │ └── parsers │ │ ├── ExpressionParser.cs │ │ ├── FunctionParser.cs │ │ └── ModuleParser.cs └── tokenization │ ├── Token.cs │ ├── TokenType.cs │ ├── Tokenizer.cs │ ├── TokenizerOptions.cs │ └── parsers │ ├── GlobalParser.cs │ └── modules │ ├── CommentaryParser.cs │ ├── IdentifierParser.cs │ ├── NumberParser.cs │ ├── OperatorParser.cs │ ├── StringParser.cs │ └── WhitespaceParser.cs └── language ├── array.fbl ├── core.fbl ├── maybe.fbl ├── operators.fbl ├── pair.fbl ├── primitives.fbl ├── program.fbl ├── testing.fbl ├── tests ├── language_test.fbl ├── std_test.fbl └── types_test.fbl ├── tools.fbl ├── types.fbl └── types_def.fbl /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual studio 2 | *.config 3 | *.csproj* 4 | *.sln 5 | 6 | .vs 7 | bin 8 | obj 9 | 10 | _optimize* 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Michael Ermishin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | fbl-git 2 | -------------------------------------------------------------------------------- /fbl/ErrorCodes.cs: -------------------------------------------------------------------------------- 1 | namespace FBL 2 | { 3 | public enum ErrorCodes 4 | { 5 | // Start up 6 | C_FileDoesNotExists = 1, 7 | 8 | 9 | // Tokenization 10 | T_UnexpectedEndOfFile = 10, 11 | 12 | T_InvalidIdentifier = 20, 13 | 14 | T_InvalidCompilerDirectiveName = 21, 15 | T_CompilerDirectiveNameIsNotStated = 22, 16 | 17 | T_SpecialCharacterDoesNotExists = 30, 18 | T_MisleadingCharacter = 31, 19 | 20 | 21 | // Parsing 22 | P_IdentifierExpected = 100, 23 | P_MethodTypeExpected = 101, 24 | P_MethodNameExpected = 102, 25 | P_OpeningBracketExpected = 103, 26 | 27 | P_DuplicatedModifier = 110, 28 | P_ReferenceCanNotBeConstant = 111, 29 | P_ArrayCanNotContainReferences = 112, 30 | 31 | P_ColonBeforeTypeSpeceficationNotFound = 120, 32 | 33 | P_UnknownUnit = 130, 34 | P_ClosingBraceRequired = 131, 35 | 36 | 37 | 38 | // Optimization 39 | // ... 40 | 41 | 42 | // Translation 43 | // ... 44 | 45 | 46 | // Compilation 47 | // ... 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /fbl/ErrorHandler.cs: -------------------------------------------------------------------------------- 1 | using FBL.Parsing; 2 | using FBL.Tokenization; 3 | using System; 4 | using System.Linq; 5 | 6 | namespace FBL 7 | { 8 | public static class ErrorHandler 9 | { 10 | public static void LogError(State state) 11 | { 12 | LogErrorInfo(state); 13 | if (state is Parser.State) { state.Restore(); } 14 | 15 | var lastToken = GetLastTokenOrEof(state); 16 | LogErrorPosition(lastToken); 17 | 18 | var codePointerString = new String( 19 | '^', 20 | Math.Max(1, lastToken.Length) 21 | ); 22 | 23 | LogCodePart(state, codePointerString, lastToken.Index); 24 | } 25 | 26 | public static void LogErrorInfo(State state) 27 | { 28 | Console.Error.WriteLine( 29 | $"Error #LC{state.ErrorCode.ToString("D3")}:\n{state.ErrorMessage}\n" 30 | ); 31 | } 32 | 33 | public static void LogErrorPosition(Token token) 34 | { 35 | Console.Error.WriteLine( 36 | $"Line: {token.Line}\nPosition: {token.Position}\n" 37 | ); 38 | } 39 | 40 | public static void LogErrorTokensPart(State state, int index, int leftBound, int rightBound) 41 | { 42 | var reader = state.Tokens.Skip(index - leftBound); 43 | var tokensToPrint = reader.Take(leftBound + rightBound + 1); 44 | 45 | Console.Error.WriteLine( 46 | String.Join( 47 | " ", 48 | tokensToPrint.Select(t => t.Value) 49 | ) 50 | ); 51 | 52 | var codePointerString = new String( 53 | '^', 54 | state.Tokens[index].Length 55 | ); 56 | var shift = tokensToPrint.Take(leftBound).Select(t => t.Value.Length).Sum() + leftBound; 57 | var underline = new String(' ', shift) + codePointerString; 58 | Console.Error.WriteLine(underline); 59 | } 60 | 61 | private static void LogCodePart(State state, string underline, int from) 62 | { 63 | Console.Error.WriteLine( 64 | state.Code 65 | .Substring(from, underline.Length) 66 | .Replace('\n', ' ') + 67 | "\n" + underline + "\n" 68 | ); 69 | } 70 | 71 | private static Token GetLastTokenOrEof(State state) 72 | { 73 | return state.Tokens.LastOrDefault() ?? new Token( 74 | 0, "", 1, 1, 75 | Tokenizer.State.Context.Global, 76 | TokenType.EOF, TokenSubType.EndOfFile 77 | ); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /fbl/Program.cs: -------------------------------------------------------------------------------- 1 | using FBL.Interpretation; 2 | using FBL.Interpretation.Modules; 3 | using FBL.Optimization; 4 | using FBL.Parsing; 5 | using FBL.Parsing.Nodes; 6 | using FBL.Tokenization; 7 | using System; 8 | using System.Diagnostics; 9 | using System.Globalization; 10 | using System.IO; 11 | using System.Threading; 12 | 13 | namespace FBL 14 | { 15 | public static class Program 16 | { 17 | static void Main(string[] args) 18 | { 19 | Console.OutputEncoding = System.Text.Encoding.UTF8; 20 | Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; 21 | 22 | if (args.Length == 0) 23 | { 24 | Console.WriteLine("Target file is required"); 25 | return; 26 | } 27 | 28 | if (!File.Exists(args[0])) 29 | { 30 | Console.Error.WriteLine( 31 | "Error #LC001:\n" + 32 | $"File {args[0]} does not exists:\n" + 33 | $" {Path.GetFullPath(args[0])}" 34 | ); 35 | return; 36 | } 37 | 38 | var code = File.ReadAllText(args[0]); 39 | var tokenizer = new Tokenizer(code, new TokenizerOptions 40 | { 41 | SkipWhitespace = true 42 | }); 43 | 44 | var watch = Stopwatch.StartNew(); 45 | var tokens = tokenizer.Tokenize(); 46 | watch.Stop(); 47 | Console.WriteLine("\n===--- STATS ---==="); 48 | 49 | Console.WriteLine($" [T] Tokens count: {tokens.Length}"); 50 | Console.WriteLine($" [T] Ellapsed time: {watch.Elapsed.TotalSeconds}s"); 51 | 52 | watch.Start(); 53 | var ast = Parser.Parse(tokens); 54 | var interpreter = new Interpreter(ast?.Code?.Context); 55 | interpreter.AddModule(new LanguageModule()); 56 | ast = Optimizer.Optimize(ast); 57 | watch.Stop(); 58 | 59 | Console.WriteLine($"\n [P] Ellapsed time: {watch.Elapsed.TotalSeconds}s"); 60 | 61 | if (ast == null) 62 | { 63 | Console.Error.WriteLine("Error occurred during compilation process"); 64 | return; 65 | } 66 | 67 | Console.WriteLine("\n\n===--- INTERPRETATION ---==="); 68 | 69 | 70 | try 71 | { 72 | watch.Start(); 73 | interpreter.Run(ast); 74 | watch.Stop(); 75 | Console.WriteLine($"\n [E] Ellapsed time: {watch.Elapsed.TotalSeconds}s"); 76 | 77 | Console.WriteLine("\n\n===--- RUN MAIN ---==="); 78 | 79 | watch.Start(); 80 | interpreter.Run("main", new StringNode("", false)); 81 | watch.Stop(); 82 | Console.WriteLine($"\n [E] Ellapsed time: {watch.Elapsed.TotalSeconds}s"); 83 | } 84 | catch (Exception e) 85 | { 86 | Console.WriteLine("===--- ERROR OCCURED ---==="); 87 | Console.WriteLine(e.Message); 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /fbl/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("fbl")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("fbl")] 12 | [assembly: AssemblyCopyright("Copyright © 2018")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("8fdabe58-abc9-42d5-bebd-044f3c90f85b")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /fbl/State.cs: -------------------------------------------------------------------------------- 1 | using FBL.Tokenization; 2 | using System.Collections.Generic; 3 | 4 | namespace FBL 5 | { 6 | /// 7 | /// Abstract Compiler state 8 | /// Holds stack of it's own copies 9 | /// Holds errors 10 | /// 11 | public abstract class State 12 | { 13 | /// 14 | /// Code that is being parsed 15 | /// 16 | public string Code { get; set; } 17 | /// 18 | /// List of parsed tokens 19 | /// 20 | public List Tokens { get; set; } 21 | 22 | public uint ErrorCode { get; set; } 23 | public string ErrorMessage { get; set; } 24 | 25 | 26 | /// 27 | /// Checks if any errors were written to state 28 | /// 29 | /// True if error is occured 30 | public bool IsErrorOccured() => ErrorCode > 0; 31 | 32 | /// 33 | /// Saves state's copy to stack 34 | /// 35 | public abstract void Save(); 36 | /// 37 | /// Restores state's copy from stack 38 | /// 39 | public abstract void Restore(); 40 | /// 41 | /// Removes state's copy from stack without restoring values 42 | /// 43 | public abstract void Drop(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /fbl/interpretation/Context.cs: -------------------------------------------------------------------------------- 1 | using FBL.Interpretation.Modules; 2 | using FBL.Parsing.Nodes; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace FBL.Interpretation 7 | { 8 | public class Context 9 | { 10 | public Context Parent; 11 | public Dictionary Values; 12 | public long LastVisitedAt = -1; 13 | 14 | public bool IsDeterministic = true; 15 | 16 | public Context() : this(null, new Dictionary()) 17 | { } 18 | 19 | public Context(Context context, Dictionary values) 20 | { 21 | this.Parent = context; 22 | this.Values = new Dictionary(values) 23 | { 24 | ["set"] = new FunctionNode((i, c) => Set(i, c), false, false) 25 | { Parameter = new VariableNode("name"), Context = this }, 26 | 27 | ["get"] = new FunctionNode((i, c) => Get(i, c), false, false) 28 | { Parameter = new VariableNode("name"), Context = this }, 29 | 30 | ["def"] = new FunctionNode((i, c) => Def(i, c), false, false) 31 | { Parameter = new VariableNode("name"), Context = this }, 32 | }; 33 | } 34 | 35 | public Context Clone() 36 | => new Context(Parent, Values) { IsDeterministic = IsDeterministic }; 37 | 38 | 39 | public ExpressionNode SetVariable(string name, ExpressionNode value) 40 | { 41 | var ctx = this; 42 | while (ctx != null) 43 | { 44 | if (ctx.Values.ContainsKey(name)) 45 | return ctx.Values[name] = value; 46 | 47 | ctx = ctx.Parent; 48 | } 49 | 50 | Values.Add(name, value); 51 | return value; 52 | } 53 | 54 | public ExpressionNode GetVariable(string name) 55 | { 56 | var ctx = this; 57 | while (ctx != null) 58 | { 59 | if (ctx.Values.TryGetValue(name, out ExpressionNode value)) 60 | return value; 61 | 62 | ctx = ctx.Parent; 63 | } 64 | 65 | return new ExpressionNode(); 66 | } 67 | 68 | public ExpressionNode Get(ExpressionNode input, Context context) 69 | => context.GetVariable(LanguageModule.ToString(input, context).StringValue); 70 | 71 | public FunctionNode Set(ExpressionNode input, Context context) 72 | => new FunctionNode( 73 | (v, c) => c.SetVariable(LanguageModule.ToString(input, c).StringValue, v), 74 | false, false 75 | ) 76 | { Parameter = new VariableNode("right"), Context = context }; 77 | 78 | public FunctionNode Def(ExpressionNode input, Context context) 79 | => new FunctionNode( 80 | (v, c) => context.Values.ContainsKey(LanguageModule.ToString(input, context).StringValue) 81 | ? throw new System.Exception("") 82 | : context.Values[LanguageModule.ToString(input, context).StringValue] = v, 83 | true, false 84 | ) 85 | { Parameter = new VariableNode("right"), Context = context }; 86 | 87 | 88 | public override string ToString() 89 | { 90 | return string.Join("\n", Values.Select(v => $" >> {v.Key}\n<< {v.Value}\n===--- PARENT ---===\n{Parent}")); 91 | } 92 | 93 | public bool DeepEquals(Context c, long visitId) 94 | { 95 | if (LastVisitedAt == visitId) 96 | return true; 97 | 98 | LastVisitedAt = visitId; 99 | 100 | if (this == c) 101 | return true; 102 | 103 | if (Values.Count != c?.Values.Count) 104 | return false; 105 | 106 | return Values.All(v => c.Values.ContainsKey(v.Key) && v.Value.DeepEquals(c.Values[v.Key], visitId)) 107 | && ((Parent == c.Parent) || (Parent?.DeepEquals(c.Parent, visitId) ?? false)); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /fbl/interpretation/Interpreter.cs: -------------------------------------------------------------------------------- 1 | using FBL.Parsing.Nodes; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace FBL.Interpretation 6 | { 7 | public partial class Interpreter 8 | { 9 | private Context globalContext = new Context(); 10 | private List loadedModules = new List(); 11 | 12 | 13 | public Interpreter(Context context) 14 | { 15 | globalContext = context; 16 | 17 | globalContext.Values["get"].Context = globalContext; 18 | globalContext.Values["set"].Context = globalContext; 19 | globalContext.Values["def"].Context = globalContext; 20 | } 21 | 22 | 23 | public ExpressionNode Run(CoreNode program) 24 | { 25 | program.Code.Context = globalContext; 26 | return Evaluate((dynamic)program.Code, globalContext); 27 | } 28 | 29 | public ExpressionNode Run(ExpressionNode node, StringNode data) 30 | { 31 | return Evaluate(new FunctionCallNode 32 | { 33 | Argument = data, 34 | CalleeExpression = node, 35 | Context = globalContext 36 | }, globalContext); 37 | } 38 | 39 | public ExpressionNode Run(string name, ExpressionNode args) 40 | { 41 | if (!globalContext.Values.TryGetValue(name, out ExpressionNode node)) 42 | return new ExpressionNode(); 43 | 44 | if (!(node is FunctionNode f)) 45 | return new ExpressionNode(); 46 | 47 | return Evaluate(new FunctionCallNode 48 | { 49 | Argument = args, 50 | CalleeExpression = f, 51 | Context = globalContext 52 | }, globalContext); 53 | } 54 | 55 | 56 | public void AddModule(IModule module) 57 | { 58 | module.OnLoad(this); 59 | loadedModules.Add(module); 60 | } 61 | 62 | 63 | public Context GetGlobalContext() => globalContext; 64 | 65 | private ExpressionNode Evaluate(FunctionCallNode node, Context context) 66 | { 67 | ExpressionNode left = Evaluate((dynamic)node.CalleeExpression, context); 68 | 69 | if (left is FunctionNode leftFunc) 70 | { 71 | ExpressionNode right = Evaluate((dynamic)node.Argument, context); 72 | 73 | if (leftFunc.Function != null) 74 | return leftFunc.Function(right, leftFunc.Context); 75 | 76 | var subContext = leftFunc.Context.Clone(); 77 | // subContext.Parent = context; 78 | subContext.Values[leftFunc.Parameter.Name] = right; 79 | 80 | return Evaluate((dynamic)leftFunc.Code, subContext); 81 | } 82 | 83 | string name = ""; 84 | if (node.CalleeExpression is VariableNode callee_var) 85 | name = $"with name '{callee_var.Name}'"; 86 | 87 | throw new InvalidOperationException( 88 | $"calling '{node.CalleeExpression?.GetType().Name ?? "null"}' {name}\n" + 89 | $" evaluated to {left?.GetType().Name}: {left?.ToString() ?? "null"}\n" + 90 | $"is impossible"); 91 | } 92 | 93 | 94 | private ExpressionNode Evaluate(BlockNode node, Context context) 95 | { 96 | ExpressionNode result = null; 97 | 98 | foreach (var exp in node.Code) 99 | result = Evaluate((dynamic)exp, context); 100 | 101 | return result; 102 | } 103 | 104 | private ExpressionNode Evaluate(VariableNode node, Context context) 105 | { 106 | var ctx = context; 107 | while (ctx != null) 108 | { 109 | if (ctx.Values.TryGetValue(node.Name, out ExpressionNode value)) 110 | return value; 111 | 112 | ctx = ctx.Parent; 113 | } 114 | 115 | return new ExpressionNode(); 116 | } 117 | 118 | private ExpressionNode Evaluate(ExpressionNode node, Context context) 119 | { 120 | var value = node?.Clone() ?? new ExpressionNode(); 121 | if (value is FunctionNode) 122 | { 123 | value.Context = value.Context.Clone(); 124 | value.Context.Parent = context; 125 | } 126 | 127 | return value; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /fbl/interpretation/Module.cs: -------------------------------------------------------------------------------- 1 | namespace FBL.Interpretation 2 | { 3 | public interface IModule 4 | { 5 | void OnLoad(Interpreter interpreter); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fbl/interpretation/modules/LanguageModule.cs: -------------------------------------------------------------------------------- 1 | using FBL.Parsing; 2 | using FBL.Parsing.Nodes; 3 | using FBL.Tokenization; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Globalization; 7 | using System.IO; 8 | using System.Linq; 9 | 10 | namespace FBL.Interpretation.Modules 11 | { 12 | public class LanguageModule : IModule 13 | { 14 | private Interpreter interpreter; 15 | private HashSet includedFiles = new HashSet(); 16 | 17 | private FunctionNode PutsFunctionNode = null; 18 | private long NextVisitId = 0; 19 | 20 | 21 | void IModule.OnLoad(Interpreter interpreter) 22 | { 23 | this.interpreter = interpreter; 24 | var context = interpreter.GetGlobalContext(); 25 | 26 | context.SetVariable( 27 | "include", 28 | new FunctionNode(Include, false, false) { Parameter = new VariableNode("filename") } 29 | ); 30 | 31 | // TODO: Temporary 32 | context.SetVariable( 33 | "input", 34 | new FunctionNode(Input, true, true) { Parameter = new VariableNode("type") } 35 | ); 36 | context.SetVariable( 37 | "print", 38 | new FunctionNode(Print, false, true) { Parameter = new VariableNode("value") } 39 | ); 40 | context.SetVariable( 41 | "puts", 42 | PutsFunctionNode = new FunctionNode(Puts, false, true) { Parameter = new VariableNode("value") } 43 | ); 44 | 45 | context.SetVariable( 46 | "int", 47 | new FunctionNode(ToInt, true, true) { Parameter = new VariableNode("value") } 48 | ); 49 | context.SetVariable( 50 | "number", 51 | new FunctionNode(ToNumber, true, true) { Parameter = new VariableNode("value") } 52 | ); 53 | context.SetVariable( 54 | "string", 55 | new FunctionNode(ToString, true, true) { Parameter = new VariableNode("value") } 56 | ); 57 | 58 | context.SetVariable( 59 | "get_type", 60 | new FunctionNode( 61 | (i, c) => new StringNode(i?.GetType().Name.Replace("Node", "").ToLower(), false), true, true) 62 | { Parameter = new VariableNode("value") } 63 | ); 64 | 65 | context.SetVariable( 66 | "abs", 67 | new FunctionNode(NumberAbsolute, true, true) { Parameter = new VariableNode("value") } 68 | ); 69 | 70 | 71 | context.SetVariable( 72 | "+", 73 | new FunctionNode(Add, true, true) { Parameter = new VariableNode("left") } 74 | ); 75 | context.SetVariable( 76 | "-", 77 | new FunctionNode(NumbersSub, true, true) { Parameter = new VariableNode("left") } 78 | ); 79 | context.SetVariable( 80 | "*", 81 | new FunctionNode(NumbersMul, true, true) { Parameter = new VariableNode("left") } 82 | ); 83 | context.SetVariable( 84 | "/", 85 | new FunctionNode(NumbersDiv, true, true) { Parameter = new VariableNode("left") } 86 | ); 87 | context.SetVariable( 88 | "%", 89 | new FunctionNode(NumbersMod, true, true) { Parameter = new VariableNode("left") } 90 | ); 91 | 92 | context.SetVariable( 93 | "if", 94 | new FunctionNode(IfExpression, true, true) 95 | { Parameter = new VariableNode("condition") } 96 | ); 97 | context.SetVariable( 98 | "equals", 99 | new FunctionNode( 100 | (a, c1) => new FunctionNode( 101 | (b, c2) => new NumberNode(a.DeepEquals(b, NextVisitId++) ? 1 : 0), 102 | false, true 103 | ) 104 | { Parameter = new VariableNode("right") }, false, true) 105 | { Parameter = new VariableNode("left") } 106 | ); 107 | } 108 | 109 | ExpressionNode Include(ExpressionNode input, Context context) 110 | { 111 | var path = Path.GetFullPath(ToString(input, context).StringValue); 112 | if (!File.Exists(path)) return new ExpressionNode(); 113 | 114 | if (includedFiles.Contains(path)) 115 | return new ExpressionNode(); 116 | 117 | includedFiles.Add(path); 118 | 119 | var code = File.ReadAllText(path); 120 | var tokenizer = new Tokenizer(code, new TokenizerOptions { SkipWhitespace = true }); 121 | var ast = Parser.Parse(tokenizer.Tokenize()); 122 | 123 | return interpreter.Run(ast); 124 | } 125 | 126 | NumberNode NumberAbsolute(ExpressionNode input, Context context) 127 | => new NumberNode(Math.Abs(ToNumber(input, context).NumericValue)); 128 | 129 | ExpressionNode IfExpression(ExpressionNode condition, Context context) 130 | => new FunctionNode((ExpressionNode onTrue, Context context_true) 131 | => new FunctionNode((ExpressionNode onFalse, Context context_false) 132 | => ToNumber(condition, context).NumericValue == 0 ? onFalse : onTrue, true, true) 133 | { Parameter = new VariableNode("on_false") }, true, true) 134 | { Parameter = new VariableNode("on_true") }; 135 | 136 | ExpressionNode Input(ExpressionNode type, Context context) 137 | { 138 | var data = new StringNode(Console.ReadLine(), false); 139 | var result = interpreter.Run(type, data); 140 | return result; 141 | } 142 | 143 | ExpressionNode Print(ExpressionNode node, Context context) 144 | { 145 | Console.WriteLine(node?.ToString() ?? "null"); 146 | return node; 147 | } 148 | 149 | ExpressionNode Puts(ExpressionNode node, Context context) 150 | { 151 | Console.Write(node?.ToString() ?? String.Empty); 152 | return PutsFunctionNode; 153 | } 154 | 155 | public static NumberNode ToInt(ExpressionNode node, Context context) 156 | { 157 | if (int.TryParse(GetLeadingInt(node.ToString()), NumberStyles.Number, CultureInfo.InvariantCulture, out int value)) 158 | return new NumberNode(value); 159 | 160 | return new NumberNode(0); 161 | } 162 | 163 | static string GetLeadingInt(string input) 164 | => new string(input.Trim().TakeWhile((c) => Char.IsDigit(c)).ToArray()); 165 | 166 | public static NumberNode ToNumber(ExpressionNode node, Context context) 167 | { 168 | if (decimal.TryParse(node.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, out decimal value)) 169 | return new NumberNode(value); 170 | 171 | return new NumberNode(0); 172 | } 173 | 174 | public static StringNode ToString(ExpressionNode node, Context context) 175 | => new StringNode(node.ToString(), false); 176 | 177 | 178 | ExpressionNode Add(ExpressionNode left, Context context) 179 | { 180 | return new FunctionNode( 181 | (right, c) => 182 | { 183 | if (left is NumberNode && right is NumberNode) 184 | { 185 | return new NumberNode( 186 | ToNumber(left, c).NumericValue 187 | + ToNumber(right, c).NumericValue); 188 | } 189 | 190 | return new StringNode(left.ToString() + right.ToString(), false); 191 | }, true, true 192 | ) 193 | { Parameter = new VariableNode("right") }; 194 | } 195 | 196 | ExpressionNode NumbersSub(ExpressionNode left, Context context) 197 | { 198 | return new FunctionNode( 199 | (right, c) => new NumberNode( 200 | ToNumber(left, c).NumericValue 201 | - ToNumber(right, c).NumericValue 202 | ), true, true 203 | ) 204 | { Parameter = new VariableNode("right") }; 205 | } 206 | 207 | ExpressionNode NumbersMul(ExpressionNode left, Context context) 208 | { 209 | return new FunctionNode( 210 | (right, c) => new NumberNode( 211 | ToNumber(left, c).NumericValue 212 | * ToNumber(right, c).NumericValue 213 | ), true, true 214 | ) 215 | { Parameter = new VariableNode("right") }; 216 | } 217 | 218 | ExpressionNode NumbersDiv(ExpressionNode left, Context context) 219 | { 220 | return new FunctionNode( 221 | (right, c) => new NumberNode( 222 | ToNumber(left, c).NumericValue 223 | / ToNumber(right, c).NumericValue 224 | ), true, true 225 | ) 226 | { Parameter = new VariableNode("right") }; 227 | } 228 | 229 | ExpressionNode NumbersMod(ExpressionNode left, Context context) 230 | { 231 | return new FunctionNode( 232 | (right, c) => new NumberNode( 233 | ToNumber(left, c).NumericValue 234 | % ToNumber(right, c).NumericValue 235 | ), true, true 236 | ) 237 | { Parameter = new VariableNode("right") }; 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /fbl/optimization/Optimizer.cs: -------------------------------------------------------------------------------- 1 | using FBL.Interpretation; 2 | using FBL.Parsing; 3 | using FBL.Parsing.Nodes; 4 | using FBL.Tokenization; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | 9 | namespace FBL.Optimization 10 | { 11 | public class Optimizer 12 | { 13 | private static List includedFiles = new List(100); 14 | 15 | private static List insertionQueue = new List(10); 16 | private static bool newDependencies = false; 17 | private static Dictionary> dependencyGraph = new Dictionary>(); 18 | 19 | 20 | private static string currentCodeName = null; 21 | private static Dictionary codes = null; 22 | 23 | 24 | public static CoreNode Optimize(CoreNode code) 25 | { 26 | int pass = 0; 27 | 28 | codes = new Dictionary() 29 | { 30 | { "", code } 31 | }; 32 | dependencyGraph.Add("", new HashSet()); 33 | 34 | BlockNode combined; 35 | bool anyOptimized; 36 | 37 | do 38 | { 39 | anyOptimized = false; 40 | newDependencies = false; 41 | 42 | combined = new BlockNode() { Context = code.Code.Context }; 43 | foreach (var c in codes) 44 | combined.Code.Add(c.Value.Code); 45 | 46 | File.WriteAllText($"_optimized_pass_{pass++}.fbl", combined.ToCodeString(0)); 47 | 48 | var oldCodes = new Dictionary(codes); 49 | foreach (var c in oldCodes) 50 | { 51 | currentCodeName = c.Key; 52 | var optimized = MakePass(c.Value); 53 | if (optimized != c.Value) 54 | { 55 | codes[c.Key] = optimized; 56 | anyOptimized = true; 57 | } 58 | } 59 | } while (anyOptimized || newDependencies); 60 | 61 | combined = new BlockNode() { Context = code.Code.Context }; 62 | foreach (var ins in Sort(codes.Keys, x => dependencyGraph[x])) 63 | { 64 | var c = codes[ins].Code; 65 | if (c is BlockNode cb) combined.Code.AddRange(cb.Code); 66 | else combined.Code.Add(c); 67 | } 68 | 69 | var opt = new CoreNode() { Code = combined }; 70 | do 71 | { 72 | anyOptimized = false; 73 | File.WriteAllText($"_optimized_pass_{pass++}.fbl", opt.ToCodeString(0)); 74 | 75 | code = opt; 76 | opt = MakePass(code); 77 | } while (opt != code); 78 | 79 | return opt; 80 | } 81 | 82 | public static IList Sort(IEnumerable source, Func> getDependencies) 83 | { 84 | var sorted = new List(); 85 | var visited = new Dictionary(); 86 | 87 | foreach (var item in source) 88 | { 89 | Visit(item, getDependencies, sorted, visited); 90 | } 91 | 92 | return sorted; 93 | } 94 | 95 | public static void Visit(T item, Func> getDependencies, List sorted, Dictionary visited) 96 | { 97 | var alreadyVisited = visited.TryGetValue(item, out bool inProcess); 98 | 99 | if (alreadyVisited) 100 | { 101 | if (inProcess) 102 | { 103 | throw new ArgumentException("Cyclic dependency found."); 104 | } 105 | } 106 | else 107 | { 108 | visited[item] = true; 109 | 110 | var dependencies = getDependencies(item); 111 | if (dependencies != null) 112 | { 113 | foreach (var dependency in dependencies) 114 | { 115 | Visit(dependency, getDependencies, sorted, visited); 116 | } 117 | } 118 | 119 | visited[item] = false; 120 | sorted.Add(item); 121 | } 122 | } 123 | 124 | 125 | public static CoreNode MakePass(CoreNode code) 126 | { 127 | var newCode = MakePass((dynamic)code.Code); 128 | 129 | if (code.Code == newCode) 130 | return code; 131 | 132 | return new CoreNode() { Code = newCode }; 133 | } 134 | 135 | private static ExpressionNode StaticInclude(ExpressionNode input, Context context) 136 | { 137 | if (!(input is StringNode inString)) 138 | throw new InvalidOperationException("A constant string must be passed to the include"); 139 | 140 | var path = Path.GetFullPath(inString.StringValue); 141 | 142 | if (!dependencyGraph.ContainsKey(currentCodeName)) 143 | dependencyGraph.Add(currentCodeName, new HashSet()); 144 | if (!dependencyGraph.ContainsKey(path)) 145 | dependencyGraph.Add(path, new HashSet()); 146 | 147 | dependencyGraph[currentCodeName].Add(path); 148 | 149 | if (includedFiles.Contains(path)) 150 | return new BlockNode(); 151 | 152 | if (!File.Exists(path)) 153 | throw new FileNotFoundException("Can not include the file!", path); 154 | 155 | Console.WriteLine($"Including: {path}"); 156 | includedFiles.Add(path); 157 | 158 | var code = File.ReadAllText(path); 159 | var tokenizer = new Tokenizer(code, new TokenizerOptions { SkipWhitespace = true }); 160 | var ast = Parser.Parse(tokenizer.Tokenize()); 161 | 162 | if (ast?.Code == null) 163 | throw new InvalidProgramException("Can not generate AST for given file"); 164 | 165 | codes.Add(path, ast); 166 | newDependencies = true; 167 | 168 | return new BlockNode(); 169 | } 170 | 171 | public static ExpressionNode MakePass(BlockNode node) 172 | { 173 | var newNode = new BlockNode() { Context = node.Context }; 174 | bool anyChanges = false; 175 | foreach (var line in node.Code) 176 | { 177 | var newExpression = MakePass((dynamic)line); 178 | anyChanges |= newExpression != line; 179 | 180 | if (newExpression is BlockNode nb) 181 | { 182 | newNode.Code.AddRange(nb.Code); 183 | anyChanges = true; 184 | } 185 | else 186 | { 187 | newNode.Code.Add(newExpression); 188 | } 189 | } 190 | 191 | if (newNode.Code.Count == 1) 192 | return newNode.Code[0]; 193 | 194 | return anyChanges ? newNode : node; 195 | } 196 | 197 | public static ExpressionNode MakePass(FunctionCallNode node) 198 | { 199 | var callee = MakePass((dynamic)node.CalleeExpression); 200 | var argument = MakePass((dynamic)node.Argument); 201 | 202 | ExpressionNode result = node; 203 | if (node.CalleeExpression != callee || node.Argument != argument) 204 | result = new FunctionCallNode { CalleeExpression = callee, Argument = argument }; 205 | 206 | if (callee is FunctionNode cf && cf.Function != null 207 | && cf.CheckedPure && cf.IsPureIn && cf.IsPureOut 208 | && (argument is NumberNode || argument is StringNode)) 209 | { 210 | return cf.Function(argument, node.Context); 211 | } 212 | 213 | return result; 214 | } 215 | 216 | public static ExpressionNode MakePass(FunctionNode node) 217 | { 218 | if (node.Function != null) 219 | return node; 220 | 221 | var newCode = MakePass((dynamic)node.Code); 222 | if (newCode != node.Code) 223 | { 224 | return new FunctionNode() 225 | { 226 | Code = newCode, 227 | Parameter = node.Parameter, 228 | 229 | CheckedPure = node.CheckedPure, 230 | IsPureIn = node.IsPureIn, 231 | IsPureOut = node.IsPureOut, 232 | 233 | Context = node.Context 234 | }; 235 | } 236 | 237 | return node; 238 | } 239 | 240 | public static ExpressionNode MakePass(VariableNode node) 241 | { 242 | if (node.Name != "include") 243 | return node; 244 | 245 | return new FunctionNode(StaticInclude, true, true); 246 | } 247 | 248 | public static ExpressionNode MakePass(ExpressionNode node) 249 | { 250 | // Too simple, nothing can be done 251 | return node; 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /fbl/parsing/Node.cs: -------------------------------------------------------------------------------- 1 | namespace FBL.Parsing 2 | { 3 | public abstract class Node 4 | { 5 | public abstract string ToCodeString(int depth); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fbl/parsing/Parser.cs: -------------------------------------------------------------------------------- 1 | using FBL.Parsing.Nodes; 2 | using FBL.Tokenization; 3 | using System.Collections.Generic; 4 | 5 | namespace FBL.Parsing 6 | { 7 | public static class Parser 8 | { 9 | public class State : FBL.State 10 | { 11 | private int _index = -1; 12 | private Stack _stateSaves = new Stack(2); 13 | private List _attributes = new List(); 14 | 15 | public int Index 16 | { 17 | get { return _index; } 18 | set { _index = value; } 19 | } 20 | 21 | 22 | public State(Token[] tokens) 23 | { 24 | Tokens = new List(tokens); 25 | } 26 | 27 | public State(State state) 28 | { 29 | Restore(state); 30 | } 31 | 32 | 33 | /// 34 | /// Gets next available token or null 35 | /// 36 | /// Next token 37 | public Token GetNextToken() 38 | { 39 | if (_index >= Tokens.Count) { return null; } 40 | 41 | _index += 1; 42 | return GetToken(); 43 | } 44 | 45 | /// 46 | /// Gets current token or null 47 | /// Then moves to next token 48 | /// 49 | /// Current token 50 | public Token GetTokenAndMove() 51 | { 52 | var tok = GetToken(); 53 | if (tok == null) { return null; } 54 | 55 | _index += 1; 56 | return tok; 57 | } 58 | 59 | /// 60 | /// Gets current token or null 61 | /// 62 | /// Current token 63 | public Token GetToken() 64 | { 65 | if (_index >= Tokens.Count) { return null; } 66 | if (_index < 0) { _index = 0; } 67 | 68 | return Tokens[_index]; 69 | } 70 | 71 | /// 72 | /// Gets next valuable token or null 73 | /// valuable - not empty/commentary 74 | /// 75 | /// Next valuable token 76 | public Token GetNextNeToken() 77 | { 78 | Token t; 79 | 80 | do 81 | { 82 | t = GetNextToken(); 83 | } while (t?.Type == TokenType.Commentary); 84 | 85 | return t; 86 | } 87 | 88 | /// 89 | /// Gets current token or null 90 | /// Then moves to the next valuable token 91 | /// valuable - not empty/commentary 92 | /// 93 | /// Current token 94 | public Token GetTokenAndMoveNe() 95 | { 96 | var tok = GetToken(); 97 | GetNextNeToken(); 98 | return tok; 99 | } 100 | 101 | /// 102 | /// Saves state's copy to stack 103 | /// 104 | public override void Save() => _stateSaves.Push(new State(this)); 105 | /// 106 | /// Restores state's copy from stack 107 | /// 108 | public override void Restore() => Restore(_stateSaves.Pop()); 109 | /// 110 | /// Removes state's copy from stack without restoring values 111 | /// 112 | public override void Drop() => _stateSaves.Pop(); 113 | 114 | /// 115 | /// Restores state's copy from given instance 116 | /// 117 | /// Target state 118 | public void Restore(State state) 119 | { 120 | _index = state._index; 121 | Tokens = state.Tokens; 122 | _stateSaves = state._stateSaves; 123 | } 124 | 125 | public int GetIndex() 126 | { 127 | return _index; 128 | } 129 | 130 | internal void PushdAttribute(FunctionCallNode call) 131 | { 132 | _attributes.Add(call); 133 | } 134 | 135 | internal void ClearAttributes() 136 | { 137 | _attributes.Clear(); 138 | } 139 | 140 | internal List GetAttributes() 141 | { 142 | return _attributes; 143 | } 144 | } 145 | 146 | /// 147 | /// Performs parsing of whole module (file) 148 | /// 149 | /// Tokens to parse 150 | /// Parsed AST 151 | public static CoreNode Parse(Token[] tokens) 152 | { 153 | var state = new State(tokens); 154 | var result = ModuleParser.Parse(state); 155 | 156 | if (!state.IsErrorOccured() && !state.GetToken().Is(TokenType.EOF)) 157 | { 158 | state.ErrorCode = (uint)ErrorCodes.T_UnexpectedEndOfFile; 159 | state.ErrorMessage = "Parsing was stopped not at the end of the file"; 160 | } 161 | 162 | if (!state.IsErrorOccured()) 163 | return result; 164 | 165 | ReportError(state); 166 | return null; 167 | } 168 | 169 | private static void ReportError(State state) 170 | { 171 | ErrorHandler.LogErrorInfo(state); 172 | ErrorHandler.LogErrorPosition(state.GetToken()); 173 | ErrorHandler.LogErrorTokensPart(state, state.GetIndex(), 3, 2); 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /fbl/parsing/nodes/BlockNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace FBL.Parsing.Nodes 6 | { 7 | public class BlockNode : ExpressionNode 8 | { 9 | /// 10 | /// Holds list of expressions 11 | /// 12 | public List Code { get; private set; } 13 | 14 | public BlockNode() 15 | { 16 | Code = new List(); 17 | } 18 | 19 | public override string ToString() 20 | { 21 | return string.Join("\n", Code); 22 | } 23 | 24 | public override string ToCodeString(int depth) 25 | { 26 | return $"\n{new String(' ', depth * 2)}: " + string.Join( 27 | $"\n{new String(' ', depth * 2)}: ", 28 | Code.Select(line => line.ToCodeString(depth + 1))); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /fbl/parsing/nodes/CoreNode.cs: -------------------------------------------------------------------------------- 1 | namespace FBL.Parsing.Nodes 2 | { 3 | public class CoreNode : Node 4 | { 5 | public ExpressionNode Code = null; 6 | 7 | public override string ToString() 8 | { 9 | return Code.ToString(); 10 | } 11 | 12 | public override string ToCodeString(int depth) 13 | { 14 | return Code.ToCodeString(0); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /fbl/parsing/nodes/ExpressionNode.cs: -------------------------------------------------------------------------------- 1 | using FBL.Interpretation; 2 | 3 | namespace FBL.Parsing.Nodes 4 | { 5 | public class ExpressionNode : Node 6 | { 7 | public Context Context { get; set; } = null; 8 | 9 | public virtual ExpressionNode Clone() 10 | { 11 | return this; 12 | } 13 | 14 | public override string ToString() 15 | { 16 | return "null"; 17 | } 18 | 19 | public virtual bool DeepEquals(ExpressionNode b, long visitId) 20 | { 21 | return this == b; 22 | } 23 | 24 | public override string ToCodeString(int depth) { return "#%@#^INVALID WTF#(%!?!?#%&@"; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /fbl/parsing/nodes/FunctionCallNode.cs: -------------------------------------------------------------------------------- 1 | namespace FBL.Parsing.Nodes 2 | { 3 | public class FunctionCallNode : ExpressionNode 4 | { 5 | public ExpressionNode CalleeExpression { get; set; } 6 | public ExpressionNode Argument { get; set; } 7 | 8 | 9 | public override string ToString() 10 | { 11 | return $"[ {CalleeExpression} \u2190 {Argument} ]"; 12 | } 13 | 14 | public override string ToCodeString(int depth) 15 | { 16 | var argStr = Argument.ToCodeString(depth); 17 | if (!(Argument is NumberNode || Argument is StringNode || Argument is VariableNode || Argument is FunctionNode)) 18 | argStr = $"( {argStr} )"; 19 | 20 | return $"{CalleeExpression.ToCodeString(depth)} {argStr}"; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /fbl/parsing/nodes/data_holders/FunctionNode.cs: -------------------------------------------------------------------------------- 1 | using FBL.Interpretation; 2 | using System; 3 | 4 | namespace FBL.Parsing.Nodes 5 | { 6 | public class FunctionNode : ExpressionNode 7 | { 8 | /// 9 | /// List of named parameters 10 | /// 11 | public VariableNode Parameter { get; set; } 12 | 13 | /// 14 | /// Block of code or MathExpression 15 | /// 16 | public ExpressionNode Code { get; set; } 17 | 18 | public Func Function { get; set; } 19 | 20 | public bool CheckedPure { get; set; } = false; 21 | public bool IsPureIn { get; set; } = false; 22 | public bool IsPureOut { get; set; } = false; 23 | 24 | 25 | public FunctionNode() { } 26 | 27 | public FunctionNode(Func import, bool isPureIn, bool isPureOut) 28 | { 29 | this.Function = import; 30 | this.CheckedPure = true; 31 | this.IsPureIn = isPureIn; 32 | this.IsPureOut = isPureOut; 33 | } 34 | 35 | /// 36 | /// Generates string representation of function. 37 | /// Used for debug/logging purposes 38 | /// 39 | /// Formatted string 40 | public override string ToString() 41 | { 42 | return $"(def [ {Parameter?.Name ?? "ø"} ] {String.Join(" : ", Code ?? new StringNode("ø", false))} )"; 43 | } 44 | 45 | 46 | public override ExpressionNode Clone() 47 | { 48 | return new FunctionNode 49 | { 50 | Parameter = Parameter, 51 | Code = Code, 52 | Function = Function, 53 | Context = Context 54 | }; 55 | } 56 | 57 | public override bool DeepEquals(ExpressionNode b, long visitId) 58 | { 59 | if (this == b) return true; 60 | 61 | if (b is FunctionNode bf) 62 | { 63 | return Code == bf.Code && Context.DeepEquals(bf.Context, visitId); 64 | } 65 | 66 | return false; 67 | } 68 | 69 | public override string ToCodeString(int depth) 70 | { 71 | return $"([ {Parameter.Name} ] {Code.ToCodeString(depth + 1)})"; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /fbl/parsing/nodes/data_holders/NumberNode.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace FBL.Parsing.Nodes 4 | { 5 | public class NumberNode : ExpressionNode 6 | { 7 | public decimal NumericValue { get; private set; } 8 | 9 | 10 | public NumberNode(string value) 11 | { 12 | NumericValue = decimal.Parse(value, NumberStyles.Number, CultureInfo.InvariantCulture); 13 | } 14 | 15 | public NumberNode(decimal value) 16 | { 17 | NumericValue = value; 18 | } 19 | 20 | public override string ToString() 21 | { 22 | return NumericValue.ToString(); 23 | } 24 | 25 | public override bool DeepEquals(ExpressionNode b, long visitId) 26 | { 27 | if (this == b) return true; 28 | if (b is NumberNode bn) 29 | return NumericValue == bn.NumericValue; 30 | 31 | return false; 32 | } 33 | 34 | public override string ToCodeString(int depth) 35 | { 36 | return NumericValue.ToString(CultureInfo.InvariantCulture); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /fbl/parsing/nodes/data_holders/StringNode.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace FBL.Parsing.Nodes 4 | { 5 | public class StringNode : ExpressionNode 6 | { 7 | private static Regex regexEscaper = new Regex(@"\\(.)", RegexOptions.Compiled); 8 | 9 | public string StringValue { get; set; } 10 | 11 | public StringNode(string value, bool escape) 12 | { 13 | if (escape) 14 | { 15 | string EscapeCharacter(Match m) 16 | { 17 | string character = m.Groups[1].Value; 18 | switch (character[0]) 19 | { 20 | case 'n': return "\n"; 21 | case 't': return "\t"; 22 | case 's': return " "; 23 | default: return character.ToString(); 24 | } 25 | } 26 | 27 | StringValue = regexEscaper.Replace(value, EscapeCharacter); 28 | } 29 | else 30 | { 31 | StringValue = value; 32 | } 33 | } 34 | 35 | public override string ToString() 36 | { 37 | return StringValue; 38 | } 39 | 40 | public override bool DeepEquals(ExpressionNode b, long visitId) 41 | { 42 | if (this == b) return true; 43 | 44 | if (b is StringNode bs) 45 | return StringValue == bs.StringValue; 46 | 47 | return false; 48 | } 49 | 50 | public override string ToCodeString(int depth) 51 | { 52 | var value = StringValue.Replace(@"\", @"\\").Replace("\t", @"\t").Replace("\n", @"\n"); 53 | return $"\"{value}\""; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /fbl/parsing/nodes/data_holders/VariableNode.cs: -------------------------------------------------------------------------------- 1 | namespace FBL.Parsing.Nodes 2 | { 3 | public class VariableNode : ExpressionNode 4 | { 5 | /// 6 | /// Variable name. Might be represented as path on access(separated by colon(:)) 7 | /// 8 | public string Name { get; set; } 9 | 10 | 11 | public VariableNode(string name) 12 | { 13 | Name = name; 14 | } 15 | 16 | 17 | /// 18 | /// Generates string representation of variable. 19 | /// Used for debug/logging purposes 20 | /// 21 | /// Formatted string 22 | public override string ToString() 23 | { 24 | return Name; 25 | } 26 | 27 | public override string ToCodeString(int depth) 28 | { 29 | return Name; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /fbl/parsing/parsers/ExpressionParser.cs: -------------------------------------------------------------------------------- 1 | using FBL.Interpretation; 2 | using FBL.Parsing.Nodes; 3 | using FBL.Tokenization; 4 | 5 | namespace FBL.Parsing 6 | { 7 | public static class ExpressionParser 8 | { 9 | public static ExpressionNode Parse(Parser.State state, Context context) 10 | { 11 | if (state.IsErrorOccured() || state.GetToken().Is(TokenType.EOF)) 12 | return null; 13 | 14 | var block = new BlockNode(); 15 | 16 | do 17 | { 18 | while (state.GetToken().Is(TokenSubType.Colon)) 19 | state.GetNextNeToken(); 20 | 21 | 22 | var unit = ParseUnit(state, context); 23 | FunctionCallNode fc_unit; 24 | 25 | if (unit == null || state.IsErrorOccured()) break; 26 | 27 | while (!state.IsErrorOccured() 28 | && !state.GetToken().Is(TokenType.EOF) 29 | && (fc_unit = ParseFunctionCall(state, unit, context)) != null) 30 | { 31 | unit = fc_unit; 32 | } 33 | 34 | block.Code.Add(unit); 35 | } while (state.GetToken().Is(TokenSubType.Colon)); 36 | 37 | return block; 38 | } 39 | 40 | private static ExpressionNode ParseUnit(Parser.State state, Context context) 41 | { 42 | if (state.GetToken().Is(TokenSubType.BraceSquareLeft)) 43 | { 44 | state.GetNextNeToken(); 45 | var func = new FunctionNode(); 46 | 47 | FunctionParser.ParseParametersList(state, func); 48 | if (state.IsErrorOccured()) 49 | return null; 50 | 51 | var subContext = new Context(context, context.Values); 52 | func.Code = Parse(state, subContext); 53 | func.Context = subContext; 54 | 55 | if (state.IsErrorOccured()) 56 | return null; 57 | 58 | return func; 59 | } 60 | 61 | return ParseValue(state, context); 62 | } 63 | 64 | private static ExpressionNode ParseValue(Parser.State state, Context context) 65 | { 66 | state.Save(); 67 | var token = state.GetTokenAndMoveNe(); 68 | 69 | if (token.Is(TokenType.Number)) 70 | { 71 | state.Drop(); 72 | return new NumberNode( 73 | value: token.Value 74 | ); 75 | } 76 | else if (token.Is(TokenType.Identifier)) 77 | { 78 | state.Drop(); 79 | return new VariableNode(token.Value); 80 | } 81 | else if (token.Is(TokenType.String)) 82 | { 83 | state.Drop(); 84 | return new StringNode(token.Value, true); 85 | } 86 | else if (token.Is(TokenSubType.BraceRoundLeft)) 87 | { 88 | state.Drop(); 89 | var node = Parse(state, context); 90 | if (state.IsErrorOccured()) 91 | return node; 92 | 93 | if (!state.GetToken().Is(TokenSubType.BraceRoundRight)) 94 | { 95 | state.ErrorCode = (uint)ErrorCodes.P_ClosingBraceRequired; 96 | state.ErrorMessage = 97 | "Expected , " + 98 | $"but <{state.GetToken().SubType}> was given"; 99 | return node; 100 | } 101 | 102 | state.GetNextNeToken(); 103 | return node; 104 | } 105 | state.Restore(); 106 | 107 | return null; 108 | } 109 | 110 | internal static FunctionCallNode ParseFunctionCall(Parser.State state, ExpressionNode func, Context context) 111 | { 112 | var call = new FunctionCallNode 113 | { 114 | CalleeExpression = func 115 | }; 116 | 117 | ExpressionNode arg = ParseUnit(state, context); 118 | if (state.IsErrorOccured() || arg == null) 119 | return null; 120 | 121 | call.Argument = arg; 122 | return call; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /fbl/parsing/parsers/FunctionParser.cs: -------------------------------------------------------------------------------- 1 | using FBL.Parsing.Nodes; 2 | using FBL.Tokenization; 3 | 4 | namespace FBL.Parsing 5 | { 6 | public static class FunctionParser 7 | { 8 | public static void ParseParametersList(Parser.State state, FunctionNode function) 9 | { 10 | if (state.GetToken().Is(TokenType.EOF)) 11 | { 12 | state.ErrorCode = (uint)ErrorCodes.T_UnexpectedEndOfFile; 13 | state.ErrorMessage = "Required as a function parameter"; 14 | return; 15 | } 16 | 17 | state.Save(); 18 | var token = state.GetTokenAndMoveNe(); 19 | if (!token.Is(TokenType.Identifier)) 20 | { 21 | state.Restore(); 22 | state.ErrorCode = (uint)ErrorCodes.P_IdentifierExpected; 23 | state.ErrorMessage = "Required as a function parameter"; 24 | return; 25 | } 26 | state.Drop(); 27 | 28 | function.Parameter = new VariableNode(token.Value); 29 | 30 | if (!state.GetToken().Is(TokenSubType.BraceSquareRight)) 31 | { 32 | state.ErrorCode = (uint)ErrorCodes.P_ClosingBraceRequired; 33 | state.ErrorMessage = "Required at the end of the function parameters list"; 34 | return; 35 | } 36 | 37 | state.GetNextNeToken(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /fbl/parsing/parsers/ModuleParser.cs: -------------------------------------------------------------------------------- 1 | using FBL.Interpretation; 2 | using FBL.Parsing.Nodes; 3 | using System; 4 | 5 | namespace FBL.Parsing 6 | { 7 | public static class ModuleParser 8 | { 9 | public static CoreNode Parse(Parser.State state) 10 | { 11 | var node = new CoreNode(); 12 | ExpressionNode value; 13 | 14 | int lastIndex = state.Index; 15 | var globalContext = new Context(); 16 | while ((value = ExpressionParser.Parse(state, globalContext)) != null && !state.IsErrorOccured()) 17 | { 18 | if (lastIndex == state.Index) 19 | break; 20 | 21 | lastIndex = state.Index; 22 | value.Context = globalContext; 23 | node.Code = value; 24 | 25 | if (state.Index + 1 < state.Tokens.Count && !state.IsErrorOccured()) 26 | { 27 | Console.WriteLine($"{state.Index} / {state.Tokens.Count}"); 28 | state.ErrorCode = (uint)ErrorCodes.T_UnexpectedEndOfFile; 29 | state.ErrorMessage = "Something really wrong happend"; 30 | } 31 | } 32 | 33 | return node; 34 | } 35 | 36 | } 37 | } -------------------------------------------------------------------------------- /fbl/tokenization/Token.cs: -------------------------------------------------------------------------------- 1 | namespace FBL.Tokenization 2 | { 3 | public class Token 4 | { 5 | /// 6 | /// Context in wich token is placed 7 | /// 8 | public Tokenizer.State.Context Context { get; set; } 9 | 10 | /// 11 | /// Token's index in code string 12 | /// 13 | public int Index { get; set; } 14 | /// 15 | /// Token's line 16 | /// 17 | public int Line { get; set; } 18 | /// 19 | /// Token's position on line 20 | /// 21 | public int Position { get; set; } 22 | 23 | /// 24 | /// String representation of token 25 | /// 26 | public string Value { get; set; } 27 | /// 28 | /// Length of string representation of token 29 | /// 30 | public int Length { get; set; } 31 | 32 | /// 33 | /// Base token type 34 | /// 35 | public TokenType Type { get; set; } 36 | /// 37 | /// Extended token type 38 | /// 39 | public TokenSubType SubType { get; set; } 40 | 41 | public Token 42 | ( 43 | string value, 44 | TokenType type, TokenSubType subType 45 | ) 46 | : this( 47 | 0, value, 0, 0, 48 | Tokenizer.State.Context.Global, 49 | type, subType 50 | ) 51 | { } 52 | 53 | public Token 54 | ( 55 | int index, 56 | string value, 57 | 58 | int line, int position, 59 | 60 | Tokenizer.State.Context context, 61 | TokenType type, TokenSubType subType 62 | ) 63 | { 64 | Context = context; 65 | 66 | Index = index; 67 | Line = line; 68 | Position = position; 69 | 70 | Value = value; 71 | Length = value.Length; 72 | 73 | Type = type; 74 | SubType = subType; 75 | } 76 | 77 | /// 78 | /// Formats token for debug usage 79 | /// 80 | /// Formatted string 81 | public override string ToString() 82 | { 83 | return $"({Line.ToString().PadLeft(3, '0')}:" + 84 | $"{Position.ToString().PadLeft(3, '0')}) " + 85 | $"{SubType.ToString().PadRight(20, ' ')} " + 86 | $"({Length.ToString().PadLeft(2, ' ')}) {Value}"; 87 | } 88 | 89 | /// 90 | /// Checks if token corresponds to given type and matches given value(string representation) 91 | /// 92 | /// Target type 93 | /// Target value(string representation) 94 | /// True if token matches needed value 95 | public bool Is(TokenType t, string v) => Type == t && Value == v; 96 | 97 | /// 98 | /// Checks if token corresponds to given type 99 | /// 100 | /// Target type 101 | /// True if token matches needed type 102 | public bool Is(TokenType t) => Type == t; 103 | 104 | /// 105 | /// Checks if token corresponds to given subtype and matches given value(string representation) 106 | /// 107 | /// Target extended type 108 | /// Target value(string representation) 109 | /// True if token matches needed value 110 | public bool Is(TokenSubType st, string v) => SubType == st && Value == v; 111 | 112 | /// 113 | /// Checks if token corresponds to given subtype 114 | /// 115 | /// Target extended type 116 | /// True if token matches needed type 117 | public bool Is(TokenSubType st) => SubType == st; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /fbl/tokenization/TokenType.cs: -------------------------------------------------------------------------------- 1 | namespace FBL.Tokenization 2 | { 3 | /// 4 | /// Base token type (11 values excluding Unknown) 5 | /// 6 | public enum TokenType 7 | { 8 | Unknown, 9 | 10 | Commentary, 11 | 12 | Identifier, 13 | 14 | Whitespace, 15 | 16 | Number, 17 | String, 18 | 19 | Operator, 20 | 21 | EOF 22 | } 23 | 24 | /// 25 | /// Extended token type (38 values excluding Unknown) 26 | /// 27 | public enum TokenSubType 28 | { 29 | Unknown, 30 | 31 | InlineCommentary, 32 | 33 | Identifier, 34 | 35 | Space, NewLine, 36 | 37 | Integer, Decimal, 38 | 39 | String, 40 | 41 | BraceRoundLeft, BraceRoundRight, 42 | BraceSquareLeft, BraceSquareRight, 43 | BraceCurlyLeft, BraceCurlyRight, 44 | 45 | Colon, SemiColon, 46 | 47 | EndOfFile 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /fbl/tokenization/Tokenizer.cs: -------------------------------------------------------------------------------- 1 | using FBL.Tokenization.TokenParsing; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace FBL.Tokenization 7 | { 8 | public class Tokenizer 9 | { 10 | public class State : FBL.State 11 | { 12 | public enum Context 13 | { 14 | Global, 15 | Block, 16 | 17 | InterpolatedString, 18 | RegularExpression 19 | } 20 | 21 | private Stack contextStack = new Stack(4); 22 | private Stack _stateSaves = new Stack(4); 23 | public TokenizerOptions Options { get; set; } 24 | 25 | /// 26 | /// Returns current character by inner index 27 | /// Returns empty symbol('\0') if no more characters are presented in code 28 | /// 29 | public char CurrentCharacter => Index < Code.Length ? Code[Index] : '\0'; 30 | 31 | 32 | /// 33 | /// Current character index in code string 34 | /// 35 | public int Index { get; set; } = 0; 36 | /// 37 | /// Current line number. 38 | /// Increments on each 'New line symbol'('\n') 39 | /// 40 | public int Line { get; set; } = 1; 41 | /// 42 | /// Index in code string pointing on line start 43 | /// Used to calculate position on line by current character's index 44 | /// 45 | public int LineBegin { get; set; } 46 | /// 47 | /// Returns current character's position on line starting from 1 48 | /// 49 | public int Position => Index - LineBegin + 1; 50 | 51 | public State(string code) 52 | { 53 | Code = code; 54 | } 55 | 56 | public State(State s) 57 | { 58 | Restore(s); 59 | } 60 | 61 | 62 | /// 63 | /// Adds new context to stack 64 | /// 65 | /// New context 66 | public void PushContext(Context context) => contextStack.Push(context); 67 | 68 | /// 69 | /// Returns current context without removing it from stack 70 | /// 71 | /// 72 | public Context PeekContext() => contextStack.Peek(); 73 | /// 74 | /// Returns current context and removes it from stack 75 | /// 76 | /// 77 | public Context PopContext() => contextStack.Pop(); 78 | 79 | /// 80 | /// Saves state's copy to stack 81 | /// 82 | public override void Save() => _stateSaves.Push(new State(this)); 83 | /// 84 | /// Restores state's copy from stack 85 | /// 86 | public override void Restore() => Restore(_stateSaves.Pop()); 87 | /// 88 | /// Removes state's copy from stack without restoring values 89 | /// 90 | public override void Drop() => _stateSaves.Pop(); 91 | 92 | /// 93 | /// Restores state's copy from given instance 94 | /// 95 | /// State to copy 96 | public void Restore(State state) 97 | { 98 | Code = state.Code; 99 | 100 | Index = state.Index; 101 | Line = state.Line; 102 | LineBegin = state.LineBegin; 103 | 104 | ErrorCode = state.ErrorCode; 105 | ErrorMessage = state.ErrorMessage; 106 | 107 | contextStack = state.contextStack; 108 | _stateSaves = state._stateSaves; 109 | } 110 | 111 | /// 112 | /// Returns parser that is suitable for givem context 113 | /// 114 | /// Target context 115 | /// Token parser for target context 116 | public static Func GetContextParser(Context context) 117 | { 118 | switch (context) 119 | { 120 | case Context.Global: 121 | case Context.Block: 122 | return GlobalParser.Parse; 123 | 124 | default: 125 | return null; 126 | } 127 | } 128 | 129 | /// 130 | /// Returns parser that is suitable for current context 131 | /// 132 | /// Token parser for current context 133 | public Func GetCurrentParser() 134 | => GetContextParser(PeekContext()); 135 | } 136 | 137 | /// 138 | /// Main tokenizer state 139 | /// 140 | public State state; 141 | 142 | public Tokenizer(string code, TokenizerOptions options) 143 | { 144 | state = new State(code) 145 | { 146 | Options = options 147 | }; 148 | state.PushContext(State.Context.Global); 149 | 150 | state.Tokens = new List(state.Code.Length / 8 + 1); 151 | } 152 | 153 | /// 154 | /// Performs tokenization of whole code 155 | /// 156 | /// Array of tokens 157 | public Token[] Tokenize() 158 | { 159 | while (true) 160 | { 161 | var token = GetNextToken(); 162 | 163 | if (token.Type == TokenType.EOF) 164 | { 165 | break; 166 | } 167 | } 168 | 169 | return state.Tokens.ToArray(); 170 | } 171 | 172 | /// 173 | /// Tokenizes only part of code that contains token. 174 | /// Or throws TokenizationException if token can not be parsed 175 | /// 176 | /// Next token of code 177 | public Token GetNextToken() 178 | { 179 | if (state.Index >= state.Code.Length) 180 | { 181 | var tok = new Token( 182 | state.Index, "", state.Line, state.Position, 183 | state.PeekContext(), 184 | TokenType.EOF, 185 | TokenSubType.EndOfFile 186 | ); 187 | 188 | if (state.Tokens.Count == 0 189 | || state.Tokens.Last().Type != TokenType.EOF) 190 | { 191 | state.Tokens.Add(tok); 192 | } 193 | 194 | return tok; 195 | } 196 | 197 | Token token = null; 198 | while (true) 199 | { 200 | var containsErrors = state.IsErrorOccured(); 201 | 202 | token = ParseNextToken(); 203 | if (!state.Options.SkipWhitespace 204 | || token.Type != TokenType.Whitespace) 205 | { 206 | if (!containsErrors) 207 | { 208 | state.Tokens.Add(token); 209 | 210 | if (token.Type != TokenType.EOF) 211 | { 212 | CheckErrors(token); 213 | } 214 | } 215 | 216 | break; 217 | } 218 | } 219 | 220 | return token; 221 | } 222 | 223 | private Token ParseNextToken() 224 | { 225 | Token token = null; 226 | token = state.GetCurrentParser()?.Invoke(state); 227 | 228 | if (token == null) 229 | { 230 | token = new Token( 231 | state.Index, "", state.Line, state.Position, 232 | state.PeekContext(), 233 | TokenType.EOF, 234 | TokenSubType.EndOfFile 235 | ); 236 | } 237 | 238 | return token; 239 | } 240 | 241 | private bool CheckErrors(Token lastToken) 242 | { 243 | if (state.IsErrorOccured()) 244 | { 245 | ErrorHandler.LogError(state); 246 | } 247 | 248 | if (lastToken.Type == TokenType.EOF 249 | && state.PeekContext() != State.Context.Global) 250 | { 251 | state.ErrorCode = (uint)ErrorCodes.T_UnexpectedEndOfFile; 252 | state.ErrorMessage = "Unexpected end of file\n" + 253 | $"Context '{state.PeekContext().ToString()}' wasn't closed"; 254 | 255 | ErrorHandler.LogError(state); 256 | return true; 257 | } 258 | 259 | return state.IsErrorOccured(); 260 | } 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /fbl/tokenization/TokenizerOptions.cs: -------------------------------------------------------------------------------- 1 | namespace FBL.Tokenization 2 | { 3 | public class TokenizerOptions 4 | { 5 | public bool SkipWhitespace { get; set; } = true; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /fbl/tokenization/parsers/GlobalParser.cs: -------------------------------------------------------------------------------- 1 | using FBL.Tokenization.TokenParsing.ParsingModules; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace FBL.Tokenization.TokenParsing 6 | { 7 | public static class GlobalParser 8 | { 9 | private static readonly List> parsers 10 | = new List> { 11 | WhitespaceParser.Parse, 12 | CommentaryParser.Parse, 13 | 14 | OperatorParser.Parse, 15 | 16 | NumberParser.Parse, 17 | IdentifierParser.Parse, 18 | 19 | StringParser.Parse, 20 | }; 21 | 22 | public static Token Parse(Tokenizer.State state) 23 | { 24 | foreach (var parser in parsers) 25 | { 26 | Token token; 27 | if (TryParse(state, parser, out token)) 28 | { 29 | return token; 30 | } 31 | } 32 | 33 | return null; 34 | } 35 | 36 | private static bool TryParse 37 | (Tokenizer.State state, Func parser, out Token token) 38 | { 39 | state.Save(); 40 | var stateCopy = new Tokenizer.State(state); 41 | 42 | token = parser(state); 43 | 44 | if (state.IsErrorOccured()) 45 | { 46 | state.Drop(); 47 | return true; 48 | } 49 | else if (token != null) 50 | { 51 | token.Index = stateCopy.Index; 52 | token.Line = stateCopy.Line; 53 | token.Position = stateCopy.Position; 54 | 55 | state.Drop(); 56 | return true; 57 | } 58 | 59 | state.Restore(); 60 | return false; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /fbl/tokenization/parsers/modules/CommentaryParser.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace FBL.Tokenization.TokenParsing.ParsingModules 4 | { 5 | public static class CommentaryParser 6 | { 7 | public static Token Parse(Tokenizer.State state) 8 | { 9 | if (state.CurrentCharacter == '`') 10 | { 11 | var begin = state.Index; 12 | state.Index += 1; 13 | 14 | ParseInlineCommentary(state); 15 | 16 | if (state.IsErrorOccured()) 17 | state.Index = begin + 2; 18 | 19 | return new Token( 20 | value: state.Code.Substring(begin, state.Index - begin), 21 | 22 | type: TokenType.Commentary, 23 | subType: TokenSubType.InlineCommentary 24 | ); 25 | } 26 | 27 | return null; 28 | } 29 | 30 | private static void ParseInlineCommentary(Tokenizer.State state) 31 | { 32 | while (state.CurrentCharacter != '\0' && !"\r\n".Contains(state.CurrentCharacter)) 33 | state.Index += 1; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /fbl/tokenization/parsers/modules/IdentifierParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace FBL.Tokenization.TokenParsing.ParsingModules 5 | { 6 | public static class IdentifierParser 7 | { 8 | public static Token Parse(Tokenizer.State state) 9 | { 10 | if (!IsMatching(state.CurrentCharacter, state)) { return null; } 11 | 12 | var begin = state.Index; 13 | state.Index += 1; 14 | while (IsMatching(state.CurrentCharacter, state)) 15 | { 16 | state.Index += 1; 17 | } 18 | 19 | return new Token( 20 | value: state.Code.Substring(begin, state.Index - begin), 21 | 22 | type: TokenType.Identifier, 23 | subType: TokenSubType.Identifier 24 | ); 25 | } 26 | 27 | private static bool IsMatching(char c, Tokenizer.State state) 28 | { 29 | const string possible = @"!?@#$%,^&|*/\_+~-<=>"; 30 | if (Char.IsLetter(c) || possible.Contains(c)) 31 | { 32 | if (c > 127) 33 | { 34 | // If identifier contains non-ascii letters 35 | // it will be written as error to state 36 | state.ErrorCode = (uint)ErrorCodes.T_InvalidIdentifier; 37 | state.ErrorMessage = 38 | "Identifier can contains only:\n" + 39 | " - latin letters\n" + 40 | $" - '{possible}' symbols"; 41 | } 42 | 43 | return true; 44 | } 45 | 46 | return false; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /fbl/tokenization/parsers/modules/NumberParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FBL.Tokenization.TokenParsing.ParsingModules 4 | { 5 | public static class NumberParser 6 | { 7 | public static Token Parse(Tokenizer.State state) 8 | { 9 | var s = new Tokenizer.State(state); 10 | 11 | int decimalPoint; 12 | ParseNumber(state, out decimalPoint); 13 | if (state.Index == s.Index) { return null; } 14 | 15 | return new Token( 16 | value: state.Code.Substring(s.Index, state.Index - s.Index), 17 | 18 | type: TokenType.Number, 19 | subType: decimalPoint >= 0 20 | ? TokenSubType.Decimal 21 | : TokenSubType.Integer 22 | ); 23 | } 24 | 25 | private static void ParseNumber(Tokenizer.State state, out int decimalPoint) 26 | { 27 | decimalPoint = -2; 28 | 29 | while (IsMatching(state, ref decimalPoint)) 30 | { 31 | state.Index += 1; 32 | } 33 | 34 | if (decimalPoint == state.Index - 1) 35 | { 36 | decimalPoint = -2; 37 | state.Index -= 1; 38 | } 39 | } 40 | 41 | private static bool IsMatching(Tokenizer.State state, ref int decimalPoint) 42 | { 43 | if (Char.IsDigit(state.CurrentCharacter)) 44 | { 45 | return true; 46 | } 47 | 48 | return IsMatchingDecimal(state, ref decimalPoint); 49 | } 50 | 51 | private static bool IsMatchingDecimal(Tokenizer.State state, ref int decimalPoint) 52 | { 53 | if (state.CurrentCharacter != '.' || decimalPoint >= 0) 54 | { 55 | return false; 56 | } 57 | 58 | decimalPoint = state.Index; 59 | return true; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /fbl/tokenization/parsers/modules/OperatorParser.cs: -------------------------------------------------------------------------------- 1 | namespace FBL.Tokenization.TokenParsing.ParsingModules 2 | { 3 | public static class OperatorParser 4 | { 5 | public static Token Parse(Tokenizer.State state) 6 | { 7 | var st = GetTypeFor(state.CurrentCharacter); 8 | 9 | if (st == TokenSubType.Unknown) { return null; } 10 | 11 | var t = new Token( 12 | value: state.CurrentCharacter.ToString(), 13 | 14 | type: TokenType.Operator, 15 | subType: st 16 | ); 17 | 18 | state.Index += 1; 19 | return t; 20 | } 21 | 22 | private static TokenSubType GetTypeFor(char c) 23 | { 24 | switch (c) 25 | { 26 | case ':': return TokenSubType.Colon; 27 | case ';': return TokenSubType.SemiColon; 28 | 29 | 30 | case '(': return TokenSubType.BraceRoundLeft; 31 | case ')': return TokenSubType.BraceRoundRight; 32 | 33 | case '[': return TokenSubType.BraceSquareLeft; 34 | case ']': return TokenSubType.BraceSquareRight; 35 | 36 | case '{': return TokenSubType.BraceCurlyLeft; 37 | case '}': return TokenSubType.BraceCurlyRight; 38 | 39 | 40 | default: return TokenSubType.Unknown; 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /fbl/tokenization/parsers/modules/StringParser.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace FBL.Tokenization.TokenParsing.ParsingModules 4 | { 5 | public static class StringParser 6 | { 7 | public static Token Parse(Tokenizer.State state) 8 | { 9 | if (state.CurrentCharacter != '"') { return null; } 10 | 11 | var begin = state.Index + 1; 12 | 13 | do 14 | { 15 | state.Index += 1; 16 | while (state.CurrentCharacter == '\\') 17 | { 18 | state.Index += 2; 19 | } 20 | } while (!"\"\0".Contains(state.CurrentCharacter)); 21 | 22 | if (state.CurrentCharacter == '\0') 23 | { 24 | state.Index -= 1; 25 | state.ErrorCode = (uint)ErrorCodes.T_UnexpectedEndOfFile; 26 | state.ErrorMessage = "End of string was not found"; 27 | } 28 | 29 | return new Token( 30 | value: state.Code.Substring(begin, state.Index++ - begin), 31 | 32 | type: TokenType.String, 33 | subType: TokenSubType.String 34 | ); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /fbl/tokenization/parsers/modules/WhitespaceParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FBL.Tokenization.TokenParsing.ParsingModules 4 | { 5 | public static class WhitespaceParser 6 | { 7 | public static Token Parse(Tokenizer.State state) 8 | { 9 | if (!Char.IsWhiteSpace(state.CurrentCharacter)) { return null; } 10 | 11 | var c = state.CurrentCharacter; 12 | var isNewLine = c == '\n'; 13 | if (isNewLine) 14 | { 15 | state.Line += 1; 16 | state.LineBegin = state.Index + 1; 17 | } 18 | 19 | var token = new Token( 20 | value: c.ToString(), 21 | 22 | type: TokenType.Whitespace, 23 | subType: isNewLine 24 | ? TokenSubType.NewLine 25 | : TokenSubType.Space 26 | ); 27 | 28 | state.Index += 1; 29 | return token; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /language/array.fbl: -------------------------------------------------------------------------------- 1 | : include "core.fbl" 2 | : include "tools.fbl" 3 | : include "types.fbl" 4 | 5 | 6 | ` Array 7 | : ( def "ArrayType" (Type "Array" [ declare_property ] 8 | : declare_property "is_empty" true 9 | : declare_property "length" 0 10 | 11 | : declare_property "raw" ([ getter ] getter true void void) 12 | 13 | : declare_property "top" 14 | 15 | : declare_property "push" 16 | : declare_property "pop" 17 | 18 | ` Special operators 19 | : declare_property << ` Alias for 'push' 20 | )) 21 | 22 | ` Standard constructor 23 | : ( def "Array" ([ --- ] ArrayType [ self ] 24 | : self <- "push" ([ value ] 25 | : self <- "length" (+ (self -> "length") 1) 26 | : self <- "is_empty" false 27 | 28 | : self <- "top" value 29 | 30 | : def "__raw" (self -> "raw") 31 | : self <- "raw" ([ getter ] getter false value __raw) 32 | : self 33 | ) 34 | : self <- "pop" ([ --- ] run ( 35 | : if (self -> "is_empty") 36 | ([ --- ] 37 | : puts "\nERROR: Can not pop element from an empty array!\n" 38 | : false 39 | ) 40 | ([ --- ] 41 | : self <- "length" (- (self -> "length") 1) 42 | : self <- "is_empty" (equals (self -> "length") 0) 43 | 44 | : self <- "raw" (self "raw" ([_][_][#] #)) 45 | : self <- "top" (self "raw" ([_][#][_] #)) 46 | 47 | : true 48 | ) 49 | )) 50 | 51 | ` Adding new push operator alias 52 | : self <- << (self -> "push") 53 | )) 54 | 55 | ` Itertools for arrays 56 | : ( def "map" [ function ] [ array ] 57 | : ([ new_array ] 58 | : ( def "__map" [ function ] [ array_raw ] run ( 59 | if (array_raw ([#][_][_] #)) void 60 | ([ --- ] 61 | : __map function (array_raw ([_][_][#] #)) 62 | : new_array << (function (array_raw ([_][#][_] #))) 63 | ) 64 | )) 65 | : __map function (array -> "raw") 66 | : new_array 67 | ) (default Array) 68 | ) 69 | 70 | : ( def "reduce" [ function ] [ accumulator ] [ array ] 71 | : foreach ([ value ] 72 | : set "accumulator" (function accumulator value) 73 | ) array 74 | : accumulator 75 | ) 76 | 77 | : ( def "foreach" [ function ] [ array ] 78 | : ( def "__foreach" [ function ] [ array_raw ] run ( 79 | if (array_raw ([#][_][_] #)) void 80 | ([ --- ] 81 | : __foreach function (array_raw ([_][_][#] #)) 82 | : function (array_raw ([_][#][_] #)) 83 | ) 84 | )) 85 | : __foreach function (array -> "raw") 86 | ) 87 | 88 | : ( def "enumerate" [ from ] [ to ] [ step ] 89 | : ([ array_new ] 90 | : while ([ --- ] is_positive (* (sign (- to from)) (sign step))) 91 | ([ --- ] 92 | : array_new << from 93 | : set "from" (+ from step) 94 | ) 95 | : array_new 96 | ) (default Array) 97 | ) 98 | -------------------------------------------------------------------------------- /language/core.fbl: -------------------------------------------------------------------------------- 1 | : include "operators.fbl" 2 | 3 | 4 | : ( def "void" [ --- ] void ) 5 | : ( def "run" [ function ] function void ) 6 | : ( def "id" [ value ] value ) 7 | 8 | 9 | : ( def "__equals" equals ) 10 | : ( set "equals" [ left ] [ right ] __if (__equals left right) true false ) 11 | 12 | 13 | : ( def "true" [ first ] [ second ] first ) 14 | : ( def "false" [ first ] [ second ] second ) 15 | : ( def "__if" if ) 16 | : ( set "if" [ condition ] [ on_true ] [ on_false ] condition on_true on_false ) 17 | -------------------------------------------------------------------------------- /language/maybe.fbl: -------------------------------------------------------------------------------- 1 | : include "core.fbl" 2 | : include "tools.fbl" 3 | 4 | 5 | : def "MaybeType" (Type "Maybe" [ declare_property ] 6 | : declare_property "value" 7 | 8 | : declare_property "is_nothing" true 9 | : declare_property "is_just" false 10 | 11 | : declare_property >>= 12 | ) 13 | 14 | : def "Nothing" (MaybeType [ self ] 15 | : self <- >>= ([ function ] Nothing) 16 | 17 | : self <- <- void 18 | ) 19 | 20 | : def "Just" ([ value ] MaybeType [ self ] 21 | : self <- "value" value 22 | : self <- "is_nothing" false 23 | : self <- "is_just" true 24 | 25 | : self <- >>= ([ function ] @ function self "value") 26 | 27 | : self <- <- void 28 | ) 29 | 30 | : def "maybe" ( [ default ] [ transform ] [ mb ] run ( 31 | if (mb "is_just") 32 | ([ --- ] @ transform mb "value") 33 | ([ --- ] default) 34 | )) 35 | 36 | : def "from_maybe" ( [ default ] [ mb ] maybe default id mb ) 37 | 38 | : def "return" ( [ value ] Just value ) 39 | -------------------------------------------------------------------------------- /language/operators.fbl: -------------------------------------------------------------------------------- 1 | : def "<<" "$op_move_left" 2 | : def ">>" "$op_move_right" 3 | 4 | : def "<-" "$op_arrow_left" 5 | : def "->" "$op_arrow_right" 6 | 7 | : def "<<<" "$op_move_l_left" 8 | : def ">>>" "$op_move_l_right" 9 | 10 | : def "<>" "$op_left_right" 11 | 12 | : def ">>=" "$op_pipe_optional" 13 | -------------------------------------------------------------------------------- /language/pair.fbl: -------------------------------------------------------------------------------- 1 | : include "core.fbl" 2 | : include "operators.fbl" 3 | : include "types.fbl" 4 | 5 | 6 | : def "PairType" (Type "Pair" ([ declare_property ] 7 | : declare_property "first" 8 | : declare_property "second" 9 | )) 10 | 11 | : def "Pair" ([ --- ] default PairType) 12 | 13 | : def "PairOf" ([ a ] [ b ] PairType [ self ] 14 | : self <- "first" a 15 | : self <- "second" b 16 | ) 17 | 18 | : def "curry" ([ function ] [ a ] [ b ] function (PairOf a b)) 19 | 20 | : def "uncurry" ([ function ] [ pair ] function (pair "first") (pair "second")) 21 | 22 | : def "pair_swap" ([ pair ] PairOf (pair "second") (pair "first")) 23 | 24 | : def "ppair" ([ pair ] puts "(" (pair "first") ", " (pair "second") ")\n") 25 | -------------------------------------------------------------------------------- /language/primitives.fbl: -------------------------------------------------------------------------------- 1 | : include "core.fbl" 2 | 3 | 4 | : def "#" ([ value ] 5 | (if (equals (get_type value) "string") (String value)) 6 | (if (equals (get_type value) "number") (Number value)) 7 | (if (equals (get_type value) "function") (FunctionType value)) 8 | ) 9 | 10 | : def "NullType" (Type "Null" [ declare_property ]) 11 | 12 | : def "NumberType" (Type "Number" [ dp ] dp "value" 0 ) 13 | : def "StringType" (Type "String" [ dp ] dp "value" "" ) 14 | : def "FunctionType" (Type "Function" [ dp ] dp "value" void) 15 | 16 | 17 | : def "Number" ([ value ] 18 | if (equals (get_type value) "number") 19 | (NumberType [ self ] self <- "value" value) 20 | Null 21 | ) 22 | 23 | : def "String" ([ value ] 24 | if (equals (get_type value) "string") 25 | (NumberType [ self ] self <- "value" value) 26 | Null 27 | ) 28 | 29 | : def "Function" ([ value ] 30 | if (equals (get_type value) "function") 31 | (NumberType [ self ] self <- "value" value) 32 | Null 33 | ) 34 | 35 | : def "Null" (default NullType) 36 | -------------------------------------------------------------------------------- /language/program.fbl: -------------------------------------------------------------------------------- 1 | : include "tests/language_test.fbl" 2 | : include "tests/std_test.fbl" 3 | : include "tests/types_test.fbl" 4 | 5 | ` : set "print" void 6 | ` : set "puts" void 7 | 8 | : (def "main" [ args ] 9 | : run functions_tests 10 | : run builtins_tests 11 | 12 | : run std_tests 13 | 14 | : run types_tests 15 | ) 16 | -------------------------------------------------------------------------------- /language/testing.fbl: -------------------------------------------------------------------------------- 1 | : include "core.fbl" 2 | : include "tools.fbl" 3 | 4 | 5 | ` Declaring counters in global scope 6 | ` so 'assert_equals' can access them 7 | : def "_$tests_total" void 8 | : def "_$tests_passed" void 9 | 10 | 11 | ` Tests wrappers 12 | : ( def "test" [ title ] [ code ] 13 | : puts "\n===--- " title " ---===\n" 14 | 15 | : set "_$tests_total" 0 16 | : set "_$tests_passed" 0 17 | : run code 18 | : puts "\nTests passed: " _$tests_passed " / " _$tests_total "\n" 19 | ) 20 | 21 | : ( def "describe" [ title ] [ code ] 22 | : puts "\nTesting #" title "\n" 23 | : run code 24 | ) 25 | 26 | 27 | ` Main testing functions 28 | : ( def "assert_equals" [ expected ] [ actual ] [ message ] run ( 29 | : set "_$tests_total" (+ _$tests_total 1) 30 | : if (not (equals expected actual)) 31 | ([ --- ] puts " FAILED...\n Expected " expected " but got " actual "\n Message: " message "\n") 32 | ([ --- ] puts " passed - " message "\n" : set "_$tests_passed" (+ _$tests_passed 1)) 33 | ) ) 34 | -------------------------------------------------------------------------------- /language/tests/language_test.fbl: -------------------------------------------------------------------------------- 1 | : include "core.fbl" 2 | : include "testing.fbl" 3 | 4 | 5 | : ( def "functions_tests" [ --- ] 6 | ` Functions for recursion tests 7 | : ( def "factorial" [ n ] run ( 8 | if (equals n 0) 9 | ([ --- ] 1) 10 | ([ --- ] * n (factorial (- n 1))) 11 | ) ) 12 | 13 | ` Functions for context tests 14 | : ( def "value" void ) 15 | : ( def "return_from_global" [ --- ] value ) 16 | : ( def "context_parent" [ value ] run [ --- ] value ) 17 | : ( def "context_parent_global" [ value ] run return_from_global ) 18 | 19 | : test "functions" ([ --- ] 20 | : describe "context_parent" ([ --- ] 21 | : assert_equals (42) (context_parent 42) "return integer (42)" 22 | : assert_equals ("foo") (context_parent "foo") "return string (foo)" 23 | : assert_equals (test) (context_parent test) "return function(test)" 24 | : 25 | : assert_equals (void) (context_parent_global 42) "return void (ignore integer (42))" 26 | : assert_equals (void) (context_parent_global "foo") "return void (ignore string (foo))" 27 | : assert_equals (void) (context_parent_global test) "return void (ignore function(test))" 28 | ) 29 | : describe "factorial" ([ --- ] 30 | : assert_equals (1) (factorial 0) "0! == 1" 31 | : assert_equals (1) (factorial 1) "1! == 1" 32 | : assert_equals (6) (factorial 3) "3! == 6" 33 | : assert_equals (120) (factorial 5) "5! == 120" 34 | : assert_equals (362880) (factorial 9) "9! == 362880" 35 | ) 36 | ) 37 | ) 38 | 39 | : ( def "builtins_tests" [ --- ] 40 | : test "built-ins" ([ --- ] 41 | : describe "math" ([ --- ] 42 | : assert_equals 10 (+ 0 10) "0 + 10 == 10" 43 | : assert_equals 10 (+ 3 7) "3 + 7 == 10" 44 | : assert_equals 3 (- 10 7) "10 - 7 == 3" 45 | : assert_equals 12 (* 3 4) "3 * 4 == 12" 46 | : assert_equals 4 (/ 12 3) "12 / 3 == 4" 47 | : assert_equals 3 (% 17 7) "17 % 7 == 3" 48 | ) 49 | : describe "convertion" ([ --- ] 50 | : assert_equals 10 (int "10") "convert string to integer" 51 | : assert_equals 10 (int "10.5") "convert string with trailing symbols to integer" 52 | : assert_equals 10 (int 10.5) "convert decimal numbers to integers" 53 | : assert_equals 10.5 (number "10.5") "convert string to decimal number" 54 | : assert_equals "1" (string 1) "convert numbers to string" 55 | ) 56 | ) 57 | ) 58 | -------------------------------------------------------------------------------- /language/tests/std_test.fbl: -------------------------------------------------------------------------------- 1 | : include "core.fbl" 2 | : include "tools.fbl" 3 | 4 | : include "array.fbl" 5 | : include "pair.fbl" 6 | : include "maybe.fbl" 7 | 8 | : include "testing.fbl" 9 | 10 | 11 | 12 | : ( def "std_tests" [ --- ] 13 | : test "std" ([ --- ] 14 | : describe "primitives" ([ --- ] 15 | : def "x" (Number 5) 16 | : def "xs" (@ run x "to_string") 17 | : assert_equals "Number{ value: 5 }" xs "5.to_string() should return 'Number{ value: 5 }'" 18 | ) 19 | 20 | : describe "array" ([ --- ] 21 | : def "a" (default Array) 22 | : assert_equals 0 (a -> "length") "empty array's length should be 0" 23 | : assert_equals true (a "is_empty") "array 'is_empty' should be true on initialization" 24 | 25 | : a << 5 << 3 << 1 26 | 27 | : assert_equals 3 (a -> "length") "array's length after 3 pushes should be equal to 3" 28 | : assert_equals false (a "is_empty") "array 'is_empty' should be false after elements addition" 29 | 30 | : a << 42 31 | : run (a "pop") 32 | : assert_equals 3 (a -> "length") "remove top element from the array" 33 | 34 | : assert_equals "531" (reduce + "" a) "reduce an array to a string '531'" 35 | : assert_equals 108 (reduce + 99 a) "reduce an array to an int '108' by addition with base 99" 36 | : assert_equals 15 (reduce * 1 a) "reduce an array to an int '15' by multiplication from 1" 37 | 38 | : def "b" (map (+ 2) a) 39 | : assert_equals "753" (reduce + "" b) "reduce an array to a string '753' by mapping with (+ 2)" 40 | 41 | 42 | : def "c" (enumerate 1 11 2) 43 | : assert_equals 5 (c -> "length") "enumeration from 1 to 11 with step 2 exclusive has 5 elements" 44 | : assert_equals "13579" (reduce + "" c) "enumeration from 1 to 11 with step 2 is '13579'" 45 | 46 | : def "d" (enumerate 9 (- 0 1) (- 0 2)) 47 | : assert_equals 5 (d -> "length") "enumeration from 9 to -1 with step -2 exclusive has 5 elements" 48 | : assert_equals "97531" (reduce + "" d) "enumeration from 9 to -1 with step -2 is '97531'" 49 | 50 | : def "e" (enumerate 0 500 1) 51 | : assert_equals 500 (e -> "length") "big arrays from enumeration (500 elements)" 52 | : def "em" (map (+ " ") e) 53 | : assert_equals 500 (e -> "length") "big arrays mapping (500 elements)" 54 | : def "es" (reduce + "array: [" em) 55 | ) 56 | 57 | : describe "pair" ([ --- ] 58 | : def "p" (PairOf "a" "b") 59 | : def "q" (PairOf "b" "a") 60 | 61 | : assert_equals "Pair" (typeof p "name") "typeof (a, b) == 'Pair'" 62 | : assert_equals false (equals (p <-) (q <-)) "`=` operator is different for pair instances" 63 | 64 | : assert_equals "a" (p "first") "(a, b).first == a" 65 | : assert_equals "b" (p "second") "(a, b).second == b" 66 | : assert_equals false (equals p q) "(a, b) != (b, a)" 67 | 68 | : set "p" (pair_swap p) 69 | : assert_equals "b" (p "first") "(b, a).first == b" 70 | : assert_equals "a" (p "second") "(b, a).second == a" 71 | : assert_equals true (equals p q) "(b, a) == (b, a)" 72 | 73 | : def "_gf" ([ a ] [ b ] a) 74 | : def "_gfu" (uncurry _gf) 75 | : def "_gfuc" ([ p ] p "first") 76 | : def "_gfc" (curry _gfuc) 77 | 78 | : assert_equals "b" (_gfu p) "uncurry ([a][b] a) (b, a) == b" 79 | : assert_equals "a" (_gfc "a" "b") "curry ([p] p.first) a b == b" 80 | ) 81 | 82 | : describe "maybe" ([ --- ] 83 | : def "x" (Just 5) 84 | : def "y" (Just 7) 85 | : def "z" (Nothing) 86 | 87 | : assert_equals "Maybe" (typeof x "name") "typeof (Just a) == 'Maybe'" 88 | : assert_equals 5 (x "value") "(Just a).value == a" 89 | : assert_equals void (z "value") "(Nothing).value == void" 90 | 91 | : def "pipe_a" ([ x ] @ return string x ) 92 | : def "pipe_b" ([ x ] @ return (+ 1) x ) 93 | 94 | : assert_equals (Just "6") (x >>= pipe_b >>= pipe_a) "Just 5 >>= (+ 1) >>= string == Just '6'" 95 | : assert_equals (Just "15") (x >>= pipe_a >>= pipe_b) "Just 5 >>= string >>= (+ 1) == Just '15'" 96 | : assert_equals Nothing (z >>= pipe_a >>= pipe_b) "Nothing >>= (+ 1) >>= string == Nothing" 97 | ) 98 | ) 99 | ) 100 | -------------------------------------------------------------------------------- /language/tests/types_test.fbl: -------------------------------------------------------------------------------- 1 | : include "core.fbl" 2 | : include "tools.fbl" 3 | : include "types.fbl" 4 | 5 | 6 | : ( def "types_tests" [ args ] 7 | : test "types" ([ --- ] 8 | : def "Vector" (Type "Vector" ([ declare_property ] 9 | ` Initializing properties by 0 by default 10 | : declare_property "x" 0 11 | : declare_property "y" 0 12 | : declare_property "z" 0 13 | )) 14 | 15 | ` Creating vector constructor aliases 16 | : def "VectorZero" ([ --- ] default Vector) 17 | : def "VectorOne" ([ --- ] 18 | Vector ([ self ] ` Passing the custom constructor 19 | : self <- "x" 1 20 | : self <- "y" 1 21 | : self <- "z" 1 22 | ) 23 | ) 24 | 25 | ` Testing vectors construction & usage 26 | : describe "creation" ([ --- ] 27 | : def "a" (default VectorZero) 28 | : def "b" (default VectorZero) 29 | : def "c" (default VectorOne) 30 | 31 | : assert_equals "Vector" (typeof a "name") "(typeof a).name == 'Vector'" 32 | : assert_equals "Vector" (typeof b "name") "(typeof b).name == 'Vector'" 33 | : assert_equals "Vector" (typeof c "name") "(typeof c).name == 'Vector'" 34 | 35 | 36 | : assert_equals true (equals a b) "2 Vector zero should have the same value. (0, 0, 0) == (0, 0, 0)" 37 | : assert_equals false (equals a c) "Vector zero should be different from Vector one. (0, 0, 0) != (1, 1, 1)" 38 | 39 | : a <- "x" 1 40 | : assert_equals false (equals a b) "Vectors should not be be equal after change. (0, 0, 0) != (1, 0, 0)" 41 | : b <- "x" 1 42 | : assert_equals true (equals a b) "Vectors should be equal after change. (1, 0, 0) == (1, 0, 0)" 43 | 44 | : assert_equals 1 (a -> "x") "Getters should return actual values. (1, 0, 0).x == 1" 45 | : assert_equals 0 (a -> "y") "Getters should return actual values. (1, 0, 0).y == 0" 46 | : assert_equals 0 (a -> "z") "Getters should return actual values. (1, 0, 0).z == 0" 47 | 48 | : def "d" a 49 | : assert_equals true (equals a d) "ref copied vector should be equal to the original. (1, 0, 0) == (1, 0, 0)" 50 | : d <- "z" 4 51 | : assert_equals true (equals a d) "ref copy should change the base value" 52 | ) 53 | ) 54 | ) 55 | -------------------------------------------------------------------------------- /language/tools.fbl: -------------------------------------------------------------------------------- 1 | : include "core.fbl" 2 | 3 | 4 | ` Convertion tools 5 | : ( def "bool" [ value ] __if value true false ) 6 | : ( def "bti" [ value ] value 1 0 ) 7 | : ( def "bts" [ value ] value "true" "false" ) 8 | 9 | 10 | ` Boolean logic 11 | : ( def "not" [ a ] a false true ) 12 | 13 | : ( def "and" [ a ] [ b ] a b a ) 14 | : ( def "or" [ a ] [ b ] a a b ) 15 | : ( def "xor" [ a ] [ b ] a (not b) b ) 16 | 17 | 18 | ` Utility functions 19 | : ( def "compose" [ f ] [ g ] [ arg ] f ( g arg )) 20 | : ( def "@" compose ) 21 | : ( def "arg_swap" [ function ] [ a ] [ b ] function b a ) 22 | : ( def "power" [ _power ] [ function ] [ value ] run ( 23 | __if _power 24 | ([ --- ] function (power (- _power 1) function value)) 25 | ([ --- ] value) 26 | )) 27 | 28 | ` Printing 29 | : ( def "pbool" [ value ] @ print bts value ) 30 | 31 | ` Working with numbers 32 | : ( def "is_positive" [ number ] bool (+ number (abs number)) ) 33 | : ( def "is_negative" [ number ] bool (- number (abs number)) ) 34 | : ( def "is_zero" [ number ] not (bool number) ) 35 | : ( def "sign" [ number ] - (@ bti is_positive number) (@ bti is_negative number) ) 36 | 37 | : ( def "is_greater" [ a ] [ b ] is_positive (- a b) ) 38 | : ( def "is_less" [ a ] [ b ] is_negative (- a b) ) 39 | : ( def "is_equal" [ a ] [ b ] is_zero (- a b) ) 40 | 41 | 42 | ` Iteration tools 43 | : ( def "loop" [ times ] [ code ] 44 | : ( def "__loop" [ from ] [ times ] [ code ] 45 | : (__if times code void) (- from times) 46 | : (__if times __loop void) from (- times 1) code 47 | ) 48 | : run (@ if is_positive times ([ --- ] __loop (int times) (int times) code) void) 49 | ) 50 | 51 | 52 | : ( def "while" [ condition ] [ code ] 53 | : run (@ if run condition ([ --- ] run code : while condition code) void) 54 | ) 55 | -------------------------------------------------------------------------------- /language/types.fbl: -------------------------------------------------------------------------------- 1 | : include "types_def.fbl" 2 | : include "primitives.fbl" 3 | -------------------------------------------------------------------------------- /language/types_def.fbl: -------------------------------------------------------------------------------- 1 | : include "core.fbl" 2 | : include "tools.fbl" 3 | 4 | 5 | : def "default" ([ type ] type void) 6 | : def "typeof" ([ object ] object "__type") 7 | 8 | 9 | : def "__types" void 10 | : run ([ --- ] 11 | : def "__$setter" set 12 | : def "__$getter" get 13 | 14 | : ( set "__types" [ action ] 15 | if (equals action <-) 16 | ([ name ] __$setter (+ "__$t_" name)) 17 | (if (equals action ->) 18 | ([ name ] __$getter (+ "__$t_" name)) 19 | (void) 20 | ) 21 | ) 22 | ) 23 | 24 | 25 | : ( def "Type" [ name ] [ describer ] 26 | : def "__$type_name" name 27 | : def "__$type_def" def 28 | : def "__$type_set" set 29 | : def "__$type_get" get 30 | : def "__$type_props_count" 0 31 | 32 | : def "__$type_add_prop" ( [ name ] 33 | : __$type_def (+ "__$type_var_" __$type_props_count) name 34 | : __$type_def (+ "__$type_exs_" name) true 35 | : __$type_set "__$type_props_count" (+ __$type_props_count 1) 36 | : __$type_def (+ "__$type_val_" (- __$type_props_count 1)) void 37 | : __$type_set (+ "__$type_val_" (- __$type_props_count 1)) 38 | ) 39 | 40 | : __$type_add_prop <- 41 | : __$type_add_prop -> 42 | : __$type_add_prop "to_string" 43 | : describer __$type_add_prop 44 | 45 | ` We should return the type instantiator (object constructor) 46 | : def "__$type_constructor" ([ constructor ] 47 | : def "__$object_set" set 48 | : def "__$object_get" get 49 | 50 | : def "__$object_set_u" ( [ name ] run ( 51 | if (equals true (__$type_get (+ "__$type_exs_" name))) 52 | ([ --- ] __$object_set (+ "__$object_var_" name)) 53 | ([ --- ] 54 | : puts "\nERROR: Object of type '" __$type_name "' does not contains a property named '" name "'\n" 55 | : void ` Return no value because of the error 56 | ) 57 | )) 58 | : def "__$object_get_u" ( [ name ] run ( 59 | if (equals true (__$type_get (+ "__$type_exs_" name))) 60 | ([ --- ] __$object_get (+ "__$object_var_" name)) 61 | ([ --- ] 62 | : puts "\nERROR: Object of type '" __$type_name "' does not contains a property named '" name "'\n" 63 | : void ` Return no value because of the error 64 | ) 65 | )) 66 | 67 | : loop __$type_props_count ([ property_index ] 68 | : __$object_set_u 69 | (__$type_get (+ "__$type_var_" property_index)) 70 | (__$type_get (+ "__$type_val_" property_index)) 71 | ) 72 | 73 | : __$object_set_u (__$type_get (+ "__$type_var_" 0)) __$object_set_u 74 | : __$object_set_u (__$type_get (+ "__$type_var_" 1)) __$object_get_u 75 | : __$object_set_u (__$type_get (+ "__$type_var_" 2)) ([ --- ] 76 | : def "__$_obj_str-repr" (+ __$type_name "{ ") 77 | : loop (- __$type_props_count 3) ([ property_index ] 78 | : def "__$_current-name" (__$type_get (+ "__$type_var_" (+ property_index 3))) 79 | : set "__$_obj_str-repr" (+ __$_obj_str-repr __$_current-name) 80 | : set "__$_obj_str-repr" (+ (+ __$_obj_str-repr ": ") (__$object_get_u __$_current-name)) 81 | : set "__$_obj_str-repr" (+ __$_obj_str-repr " ") 82 | ) 83 | : (+ __$_obj_str-repr "}") 84 | ) 85 | 86 | : (([ object ] constructor object : object) 87 | ([ action ] run ( 88 | if (equals action "__type") 89 | ([ --- ] __types -> name) 90 | ([ --- ] __$object_get_u action) 91 | )) 92 | ) 93 | ) 94 | 95 | ` Register information about newly created type 96 | : __types <- name ( [ action ] run ( 97 | if (equals action "name") 98 | ([ --- ] name) 99 | (if (equals action "constructor") 100 | ([ --- ] __$type_constructor) 101 | void 102 | ) 103 | )) 104 | 105 | ` Return the constructor 106 | : __$type_constructor 107 | ) 108 | --------------------------------------------------------------------------------