├── .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 |
--------------------------------------------------------------------------------