diff --git a/Parser.cs b/Parser.cs index e59dac6..3c5698d 100644 --- a/Parser.cs +++ b/Parser.cs @@ -8,13 +8,13 @@ using Interpreter.Tokens.Statements.Binary; using Interpreter.Tokens.Statements.Unary; -namespace Interpreter; +namespace Interpreter; public class Parser { public static int GetTopElementIndex(Token[] line, int startIndex, bool isRightBound) { - if (line[startIndex] is BinaryStatement) + if (line[startIndex] is BinaryStatement or MultilineStatementOperator) return startIndex; - + int highestPriorityNum = -1; int index = -1; for (int i = startIndex; i < line.Length && i >= 0; i += isRightBound ? 1 : -1) { @@ -23,7 +23,7 @@ public static int GetTopElementIndex(Token[] line, int startIndex, bool isRightB numBrackets++; else if (Program.ClosingBrackets.Contains(line[i].Str)) numBrackets--; - + while (numBrackets != 0) { i += isRightBound ? 1 : -1; if (Program.OpeningBrackets.Contains(line[i].Str)) @@ -46,7 +46,7 @@ public static int GetTopElementIndex(Token[] line, int startIndex, bool isRightB return index; } - + public static CheckedString[] CheckComment(CheckedString[] line) { for (int i = 0; i < line.Length; i++) if (line[i].Str == "#") @@ -54,7 +54,7 @@ public static CheckedString[] CheckComment(CheckedString[] line) { return line; } - + /** * This now practically just parses everything, so maybe some refactoring is needed */ @@ -96,15 +96,15 @@ private static Token[] BracketsParse(Token[] line, int i, string[] lines, ref in numBrackets++; else if (Program.ClosingBrackets.Contains(line[j].Str)) numBrackets--; - + while (numBrackets != 0) { if (isRightBound) subLine.Add(line[j]); else subLine = subLine.Prepend(line[j]).ToList(); - + j += isRightBound ? 1 : -1; - + if (Program.OpeningBrackets.Contains(line[j].Str)) numBrackets++; else if (Program.ClosingBrackets.Contains(line[j].Str)) @@ -132,23 +132,21 @@ private static Token[] BracketsParse(Token[] line, int i, string[] lines, ref in arguments.Add(subLine.ToArray()); else arguments = arguments.Prepend(subLine.ToArray()).ToList(); - + // Parse each element in the brackets and put it in a new list List properArguments = new List(); for (int j = 0; j < arguments.Count; j++) properArguments.Add(Parse(arguments[j], GetTopElementIndex(arguments[j].ToArray(), 0, isRightBound), lines, ref lineNo, depth + 1)); - + return properArguments.ToArray(); } - /** - * Note that this is a bad implementation with too strict constraints, but for now, only functionality is important - */ private static MultilineStatementOperator CurlyBracketsParse(Token[] line, string[] lines, ref int i, Token parent, int depth) { - // Get copy of vars so that it doesn't get affected by method calls lower in the recursion tree List tokens = new List(); - int initialIndex = ++i; // immediately increment i so that this function doesn't try to parse itself, but instead the next line (also fixes the error message) + int initialIndex = i; int numBrackets = 1; + bool isFirstBracketFound = false; + MultilineStatementOperator firstFoundBracket = null!; for (; i < lines.Length; i++) { CheckedString[] lexedLine = Lexer.Lex(lines[i], i + 1); @@ -158,36 +156,122 @@ private static MultilineStatementOperator CurlyBracketsParse(Token[] line, strin Token[] tokenizedLine = Tokenizer.Tokenize(lexedLine); + // Find first curly bracket int before = i; + bool doContinue = false; + if (!isFirstBracketFound) { + doContinue = true; + for (int j = 0; j < tokenizedLine.Length; j++) { + if (tokenizedLine[j] is MultilineStatementOperator mso) { + isFirstBracketFound = true; + firstFoundBracket = mso; + tokenizedLine = new ArraySegment(tokenizedLine, j + 1, tokenizedLine.Length - (j + 1)).ToArray(); + doContinue = tokenizedLine.Length == 0; // Happens if first line of declaration ends with an opening curly bracket + break; + } + } + } + + if (doContinue) // Happens for before mentioned case and if no curly bracket is found at all + continue; + tokens.Add(Parse(tokenizedLine, GetTopElementIndex(tokenizedLine, 0, true), lines, ref i, depth + 1)); if (i != before) continue; - foreach (CheckedString cs in lexedLine) { - if (cs.Str == "}") + foreach (Token t in tokenizedLine) { + if (t.Str == "}") numBrackets--; - else if (cs.Str == "{") + else if (t.Str == "{") numBrackets++; if (numBrackets == 0) { if (parent is OnStatement onStat && tokenizedLine.Length > 1 && tokenizedLine[1] is ElseStatement) onStat.ElseChild = (ElseStatement) Parse(tokenizedLine, 1, lines, ref i, depth + 1); - + goto FullBreak; } } } FullBreak: - + if (i >= lines.Length) - throw new FormatException("no matched bracket for bracket on line " + initialIndex); + throw new FormatException("no matched bracket for bracket on line " + (initialIndex + 1)); + + firstFoundBracket.Children = tokens.ToArray(); + + return firstFoundBracket; + } + + private static DictionaryAssignmentOperator[] SimpleObjectParse(Token[] line, string[] lines, ref int i, int startIndex, int depth) { + int initialIndex = i; + int numBrackets = 0; + List properLines = new List(); + List subLine = new List(); + for (; i < lines.Length; i++) { + Token[] tokenizedLine; + int j = 0; + if (i == initialIndex) { + tokenizedLine = line; + j = startIndex; + } else { + CheckedString[] lexedLine = Lexer.Lex(lines[i], i + 1); + + lexedLine = CheckComment(lexedLine); + if (lexedLine.Length == 0) + continue; + + tokenizedLine = Tokenizer.Tokenize(lexedLine); + } + + for (; j < tokenizedLine.Length; j++) { + Token t = tokenizedLine[j]; + if (t.Str == "}") + numBrackets--; + else if (t.Str == "{") { + if (numBrackets == 0) { // Exclude opening bracket from line + numBrackets++; + continue; + } + numBrackets++; + } + + if (t is CommaSeparator && numBrackets == 1) { + properLines.Add(subLine.ToArray()); + subLine = new List(); + continue; + } + + if (numBrackets == 0) { + properLines.Add(subLine.ToArray()); + goto FullBreak; + } + + subLine.Add(t); + } + } + FullBreak: + + List objectBody = new List(); + foreach (Token[] properLine in properLines) { + // TODO: fix the ConcatenationOperator thing, because right here it's not performing concatenation + if (properLine[0] is not StringToken || properLine[1] is not ConcatenationOperator) + throw new FormatException("simple objects should only consist of declarations"); + + Token[] parseLine = new ArraySegment(properLine, 2, properLine.Length - 2).ToArray(); + DictionaryAssignmentOperator objAssOp = new DictionaryAssignmentOperator(); + objAssOp.Left = properLine[0]; + objAssOp.Right = Parse(parseLine, GetTopElementIndex(parseLine, 0, true), lines, ref i, depth + 1); + objectBody.Add(objAssOp); + } - ((MultilineStatementOperator) line[^1]).Children = tokens.ToArray(); + if (i >= lines.Length) + throw new FormatException("no matched bracket for bracket on line " + (initialIndex + 1)); - return (MultilineStatementOperator) line[^1]; + return objectBody.ToArray(); } - + public static Token Parse(Token[] line, int i, string[] lines, ref int lineNo, int depth) { Token t = line[i]; @@ -197,12 +281,12 @@ public static Token Parse(Token[] line, int i, string[] lines, ref int lineNo, i line[i].IsDone = true; // Parse only the appropriate section (i.e. Left should only parse to the left and Right only to the right, that's what the array slicing does) - ((BinaryOperator) t).Left = SymmetricBinaryOperatorParse(line.Take(i+1).ToArray(), i, lines, ref lineNo, depth + 1, false); + ((BinaryOperator) t).Left = SymmetricBinaryOperatorParse(line.Take(i + 1).ToArray(), i, lines, ref lineNo, depth + 1, false); ((BinaryOperator) t).Right = SymmetricBinaryOperatorParse(new ArraySegment(line, i, line.Length - i).ToArray(), 0, lines, ref lineNo, depth + 1, true); break; case DeclarationOperator decOp: { - decOp.Left = Parse(line, i + 1, lines, ref lineNo, depth+1); - + decOp.Left = Parse(line, i + 1, lines, ref lineNo, depth + 1); + if (i + 2 < line.Length) // Only Parse right hand side if it exists decOp.Right = Parse(line, i + 2, lines, ref lineNo, depth + 1); break; @@ -218,10 +302,10 @@ public static Token Parse(Token[] line, int i, string[] lines, ref int lineNo, i numBrackets++; else if (Program.ClosingBrackets.Contains(line[j].Str)) numBrackets--; - + while (numBrackets != 0) { j--; - + if (Program.OpeningBrackets.Contains(line[j].Str)) numBrackets++; else if (Program.ClosingBrackets.Contains(line[j].Str)) @@ -231,10 +315,10 @@ public static Token Parse(Token[] line, int i, string[] lines, ref int lineNo, i if (j >= 0 && line[j] is DotOperator) j--; - - assOp.Left = Parse(line, j + 1, lines, ref lineNo, depth+1); + + assOp.Left = Parse(line, j + 1, lines, ref lineNo, depth + 1); Token[] subLine = new ArraySegment(line, i, line.Length - i).ToArray(); - assOp.Right = Parse(subLine, GetTopElementIndex(subLine, 1, true), lines, ref lineNo, depth+1); + assOp.Right = Parse(subLine, GetTopElementIndex(subLine, 1, true), lines, ref lineNo, depth + 1); break; } case ParenthesesOperator parOp: @@ -248,7 +332,7 @@ public static Token Parse(Token[] line, int i, string[] lines, ref int lineNo, i break; case VariableToken vt: { if (i + 1 < line.Length) - switch (line[i+1]) { + switch (line[i + 1]) { case ParenthesesOperator: vt.Args = Parse(line, i + 1, lines, ref lineNo, depth + 1); break; @@ -267,31 +351,31 @@ public static Token Parse(Token[] line, int i, string[] lines, ref int lineNo, i addition = 2; fs.Name = line[i + 1].Str; } - + Token left = Parse(line, i + addition, lines, ref lineNo, depth + 1); if (left is not ParenthesesOperator po) throw new FormatException("statement condition/parameter declaration on line " + left.Line + " is missing parentheses"); - + statement.Left = po; - + int numBrackets = 0; - int j = i+1; + int j = i + 1; do { if (Program.ClosingBrackets.Contains(line[j].Str)) numBrackets--; else if (Program.OpeningBrackets.Contains(line[j].Str)) numBrackets++; - + j++; } while (numBrackets != 0); - + statement.Right = CurlyBracketsParse(line, lines, ref lineNo, statement, depth + 1); break; } case ElseStatement or ClassStatement: { if (t is ClassStatement classStat) classStat.Name = line[i + 1].Str; - + Token child = CurlyBracketsParse(line, lines, ref lineNo, t, depth + 1); if (child is not MultilineStatementOperator mso) throw new FormatException("statement argument on line " + child.Line + " needs curly brackets"); @@ -307,12 +391,22 @@ public static Token Parse(Token[] line, int i, string[] lines, ref int lineNo, i unStat.Child = po; break; } + case MultilineStatementOperator mso: { // TODO: fix parsing of nested dictionaries + if (mso.Str == "}") + break; + + // If for some reason the Token[] is changed within mso and an element is replaced by a type + // which is not a subclass or superclass of DictionaryAssignmentOperator, the program will error, but that should never happen + mso.Children = SimpleObjectParse(line, lines, ref lineNo, i, depth + 1); + mso.IsDictionary = true; + break; + } } t.Line = line[i].Line; line[i].IsDone = true; - + return t; } } \ No newline at end of file diff --git a/Tokens/Operators/Binary/DictionaryAssignmentOperator.cs b/Tokens/Operators/Binary/DictionaryAssignmentOperator.cs new file mode 100644 index 0000000..d8cbed1 --- /dev/null +++ b/Tokens/Operators/Binary/DictionaryAssignmentOperator.cs @@ -0,0 +1,15 @@ +using TrieDictionary; +using Interpreter.Types.Util; +using String = Interpreter.Types.Comparable.String; +using Object = Interpreter.Types.Object; + +namespace Interpreter.Tokens.Operators.Binary; + +public class DictionaryAssignmentOperator : BinaryOperator { + public DictionaryAssignmentOperator() { + Symbol = ":"; + } + + public override Object Evaluate(List> vars) => new DictionaryEntry((String) Left.Evaluate(vars), Right.Evaluate(vars)); +} + diff --git a/Tokens/Operators/Binary/DotOperator.cs b/Tokens/Operators/Binary/DotOperator.cs index fac8064..b1b1d4d 100644 --- a/Tokens/Operators/Binary/DotOperator.cs +++ b/Tokens/Operators/Binary/DotOperator.cs @@ -1,6 +1,7 @@ using Interpreter.Types.Comparable; using Interpreter.Types.Function; using Interpreter.Types.Util; +using Interpreter.Types; using TrieDictionary; using Array = Interpreter.Types.Array; using Object = Interpreter.Types.Object; @@ -14,7 +15,7 @@ public DotOperator() { public override Object Evaluate(List> vars) { Object leftObj = Left.Evaluate(vars); - Object res = leftObj.Properties[Right.Str]; + Object res = leftObj.Properties.Contains(Right.Str) ? leftObj.Properties[Right.Str] : new Null(); VariableToken properRight = (VariableToken) Right; if (res is Function f) { diff --git a/Tokens/Operators/N-Ary/MultilineStatementOperator.cs b/Tokens/Operators/N-Ary/MultilineStatementOperator.cs index e9a37e3..126e144 100644 --- a/Tokens/Operators/N-Ary/MultilineStatementOperator.cs +++ b/Tokens/Operators/N-Ary/MultilineStatementOperator.cs @@ -1,11 +1,14 @@ using Interpreter.Tokens.Statements.Binary; using Interpreter.Tokens.Statements.Unary; +using Interpreter.Types.Util; using TrieDictionary; using Object = Interpreter.Types.Object; +using Dictionary = Interpreter.Types.Dictionary; namespace Interpreter.Tokens.Operators.N_Ary; public class MultilineStatementOperator : NAryOperator { + public bool IsDictionary = false; public bool IsPartOfFunction = false; public MultilineStatementOperator() { @@ -13,6 +16,16 @@ public MultilineStatementOperator() { } public override Object Evaluate(List> vars) { + if (IsDictionary) { + Dictionary dictionary = new Dictionary(); + foreach (Token t in Children) { + DictionaryEntry entry = (DictionaryEntry) t.Evaluate(vars); + dictionary.Properties[entry.Key.Str] = entry.Value; + } + + return dictionary; + } + foreach (Token t in Children) { if (t is ReturnStatement) { if (!IsPartOfFunction) diff --git a/Types/Dictionary.cs b/Types/Dictionary.cs new file mode 100644 index 0000000..79effe1 --- /dev/null +++ b/Types/Dictionary.cs @@ -0,0 +1,23 @@ +using System.Text; + +namespace Interpreter.Types; + +public class Dictionary : Object { + public override string GetType() => "Dictionary"; + + public override string ToString() => ToString(0); + + public string ToString(int indent) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < indent; i++) + sb.Append('\t'); + string indentStr = sb.ToString(); + + sb = new StringBuilder("{\n"); + foreach (string s in Properties.GetKeySet()) + sb.Append(indentStr).Append($"\t{s}: {(Properties[s] is Dictionary dict ? dict.ToString(indent + 1) : Properties[s].ToString())}\n"); + sb.Append(indentStr).Append('}'); + + return sb.ToString(); + } +} diff --git a/Types/Util/DictionaryEntry.cs b/Types/Util/DictionaryEntry.cs new file mode 100644 index 0000000..423a46e --- /dev/null +++ b/Types/Util/DictionaryEntry.cs @@ -0,0 +1,18 @@ +using String = Interpreter.Types.Comparable.String; + +namespace Interpreter.Types.Util; + +public class DictionaryEntry : Object { + public String Key; + public Object Value; + + public DictionaryEntry(String key, Object value) { + Key = key; + Value = value; + } + + public override string GetType() => "DictionaryEntry"; + + public override string ToString() => $"\"{Key}\": {Value.ToString()}"; +} +