diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d4ff2bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.dub +docs.json +__dummy.html +*.o +*.obj +snol +.vscode diff --git a/PROGRAM SPECIFICATIONS b/PROGRAM SPECIFICATIONS new file mode 100644 index 0000000..49f450f --- /dev/null +++ b/PROGRAM SPECIFICATIONS @@ -0,0 +1,193 @@ +PROGRAM SPECIFICATIONS +The name of the custom language is Simple Number-Only Language (SNOL) SNOL is a simplified custom language that only involves integer and +real values, operations, and expressions. + +The following set of specifications covers the rules that should be observed and implemented in using SNOL: +1. Formatting +• Whitespace +• Spaces are used to separate tokens (constructs) of the language but are not necessary. A command can have no space in it. +• Extra spaces are irrelevant + +• SNOL is case-sensitive +2. Data type +• There is no type declaration +• The type of a variable is determined by the nature of the value it holds +• There are only two types of values: integer and floating-point +• Writing an integer literal follows the format described by this EBNF rule: +[-]digit{digit} where: digit represents a number from 0 to 9 +• Writing a floating-point literal follows the format described by this EBNF rule: +[-]digit{digit}.{digit} where: digit represents a number from 0 to 9 + +3. Variable +• A variable is considered as a simple expression that evaluates to its corresponding value +• Naming a variable follows the format described by this EBNF rule: +letter{(letter|digit)} +where: letter represents a symbol from a to z and from A to Z +where: digit represents a number from 0 to 9 +note: a keyword cannot be used as a variable name +• Variables must be defined first before they can be used +• To define a variable, its first use must be one of the following: +• as the destination variable in an assignment operation +• as the target variable in an input operation +• A variable that has already been defined to be of a certain type can be redefined to be of a different type by subjecting the variable to +either the assignment operation (as the destination variable) or the input operation (as the target variable). +Example: num = 0 Explanation: this defines the variable num to be of type integer since its value is 0 (an integer) +BEG num Explanation: if a floating-point value is given here, num will become a floating-point type variable + +4. Assignment, input, and output operations +• Assignment operation: +var = expr +where: var represents the destination variable (where the value of expr will be stored) +where: expr is an arithmetic operation, a variable name, or a literal +• Input operation: +BEG var +where: var is the target variable (where the user-input value will be stored) +• The input operation is similar to C’s scanf function and is for asking user-input value +• Output operation: +PRINT out +where: out is either a variable name or a literal +• The output operation is similar to C’s printf function and is for displaying value +• Each of the assignment, input, and output operations does not evaluate to any value when executed. +5. Arithmetic operations +• Each operation is in infix notation and follows C’s precedence and associativity rules +• Operations: + +where: expr1 is either a literal or a variable +where: expr2 is either a literal or a variable +For the modulo operation, only integer type is allowed for both expr1 and expr2 +• Each operation is an expression that evaluates to its corresponding result +Example: the command “1 + 2” evaluates to 3 +Addition +Subtraction +Multiplication +Division +Modulo + +expr1 + expr2 +expr1 - expr2 +expr1 * expr2 +expr1 / expr2 +expr1 % expr2 + +Same as: expr1 + expr2 in C +Same as: expr1 - expr2 in C +Same as: expr1 * expr2 in C +Same as: expr1 / expr2 in C +Same as: expr1 % expr2 in C + +• All operands in an operation should be of the same type +Example: the command “2 + 1.5” is not valid since the operands 2 and 1.5 are not of the same type + +6. Exit operation: When given, this operation terminates the interpreter environment. The exit operation takes the form: EXIT! +7. Command: A command can be any valid literal, variable, and operation. + +Sample run of the program you will be writing to implement SNOL and the corresponding explanation of each part: +Sample run: +The SNOL environment is now active, you may proceed with +giving your commands. +Command: num = 0 +Command: PRINT num +SNOL> [num] = 0 +Command: BEG var +SNOL> Please enter value for [var] +Input: 75 +Command: 1 + var2 +SNOL> Error! [var2] is not defined! +Command: num + 1.5 +SNOL> Error! Operands must be of the same type in an +arithmetic operation! +Command: BEG num +SNOL> Please enter value for [num]: +Input: 25.3 +Command: num + 1.5 +Command: + num 6 +SNOL> Unknown command! Does not match any valid +command of the language. +Command: PRINT num +SNOL> [num] = 25.3 +Command: num + BEG num 3 +SNOL> Unknown command! Does not match any valid +command of the language. +Command: num = 2 + 3 +Command: PRINT num +SNOL> [num] = 5 +Command: var +Command: EXIT! +Interpreter is now terminated... + +Explanation: +Introductory message. Displayed at the start of the + + +interpreter run. +Assignment operation on [num] +Output operation on [num] +Input operation on [var] +Interpreter is asking for the value to be stored to [var] +trying to add 1 and the value of [var2], but [var2] is not + + +defined +trying to add the value of num and 1.5, but the two values + + +are not of the same type +Input operation on [num]. By giving it a floating-point value, + + +its type now becomes floating-point. +Adding the value of [num] and 1.5 +trying to give a command that is not recognized by the + + +language. +Display value of [num] +trying to give a command that is not recognized by the + + +language. +Assignment operation on [num] with the value coming from + + +the arithmetic operation involved. +Print operation on [num] +Simply specifying a simple expression. +Invoking the exit command +Program execution is terminated + + + +For the implementation: +1. In the sample run, the lines that start with the word “Command: “ are the command lines. Whenever the interpreter is in execution: the +interpreter asks the user for a command, the user gives the command, then the interpreter executes the command. This is repeated until the +command EXIT! is executed. +2. Only one command at a time is allowed to be given in the command line. When the interpreter asks for a command, the user is to type the +command then press enter. +3. In the sample run, the lines that start with “Input: “ are for when the interpreter is asking for a value to be stored to the specified variable. This +only appears when the command given is BEG. When the interpreter asks for an input, the user is to type the input then press enter. +Everything the user types before pressing the enter key should be treated as the input the user is trying to give. +4. Command execution: +• When BEG is encountered: display message similar in the sample run and ask the user for an input value and store it to the target +variable. No message display after command is successfully executed. +• When PRINT is encountered: display the value of the operand. +• When an arithmetic operation is encountered, perform the corresponding operation. No message display when command is +successfully executed. +• When the input operation is encountered, evaluate the operand and store its value to the destination variable. No message display +when command is successfully executed. + +5. Checking for errors: +• Unknown word: any word that is not a keyword (e.g., BEG, EXIT!, etc.), not a valid literal, and not a valid variable name. +Sample command: !num = 0 +Sample message: Unknown word [!num] +• Undefined word: a valid variable name that has not been defined (associated to a type). +Sample command: var + 2 // Assume that this is the first use of var +Sample message: Undefined variable [var] +• Unknown command: a command that does not completely follow the correct syntax specified in the language. +Sample command: BEG 5 + +Sample command: num + 2 5 +• Incompatible types: encountered when an arithmetic operation has operands that are not of the same type +Sample command: 2 + 1.5 +• Invalid number format: encountered when the user attempts to enter an input that contains symbols not related to the format of either +integer or floating-point. Since the only data types allowed are integer and floating-point, the interpreter should check if the input given +by the user follows either integer or floating-point literal format. \ No newline at end of file diff --git a/dscanner.ini b/dscanner.ini new file mode 100644 index 0000000..dd2e629 --- /dev/null +++ b/dscanner.ini @@ -0,0 +1,103 @@ +; Configure which static analysis checks are enabled +[analysis.config.StaticAnalysisConfig] +; Check variable, class, struct, interface, union, and function names against the Phobos style guide +style_check="disabled" +; Check for array literals that cause unnecessary allocation +enum_array_literal_check="enabled" +; Check for poor exception handling practices +exception_check="enabled" +; Check for use of the deprecated 'delete' keyword +delete_check="enabled" +; Check for use of the deprecated floating point operators +float_operator_check="enabled" +; Check number literals for readability +number_style_check="enabled" +; Checks that opEquals, opCmp, toHash, and toString are either const, immutable, or inout. +object_const_check="enabled" +; Checks for .. expressions where the left side is larger than the right. +backwards_range_check="enabled" +; Checks for if statements whose 'then' block is the same as the 'else' block +if_else_same_check="enabled" +; Checks for some problems with constructors +constructor_check="enabled" +; Checks for unused variables +unused_variable_check="enabled" +; Checks for unused labels +unused_label_check="enabled" +; Checks for unused function parameters +unused_parameter_check="enabled" +; Checks for duplicate attributes +duplicate_attribute="enabled" +; Checks that opEquals and toHash are both defined or neither are defined +opequals_tohash_check="enabled" +; Checks for subtraction from .length properties +length_subtraction_check="enabled" +; Checks for methods or properties whose names conflict with built-in properties +builtin_property_names_check="enabled" +; Checks for confusing code in inline asm statements +asm_style_check="enabled" +; Checks for confusing logical operator precedence +logical_precedence_check="enabled" +; Checks for undocumented public declarations +undocumented_declaration_check="disabled" +; Checks for poor placement of function attributes +function_attribute_check="enabled" +; Checks for use of the comma operator +comma_expression_check="enabled" +; Checks for local imports that are too broad. Only accurate when checking code used with D versions older than 2.071.0 +local_import_check="disabled" +; Checks for variables that could be declared immutable +could_be_immutable_check="enabled" +; Checks for redundant expressions in if statements +redundant_if_check="enabled" +; Checks for redundant parenthesis +redundant_parens_check="enabled" +; Checks for mismatched argument and parameter names +mismatched_args_check="enabled" +; Checks for labels with the same name as variables +label_var_same_name_check="enabled" +; Checks for lines longer than 120 characters +long_line_check="disabled" +; Checks for assignment to auto-ref function parameters +auto_ref_assignment_check="enabled" +; Checks for incorrect infinite range definitions +incorrect_infinite_range_check="enabled" +; Checks for asserts that are always true +useless_assert_check="enabled" +; Check for uses of the old-style alias syntax +alias_syntax_check="enabled" +; Checks for else if that should be else static if +static_if_else_check="enabled" +; Check for unclear lambda syntax +lambda_return_check="enabled" +; Check for auto function without return statement +auto_function_check="enabled" +; Check for sortedness of imports +imports_sortedness="disabled" +; Check for explicitly annotated unittests +explicitly_annotated_unittests="disabled" +; Check for properly documented public functions (Returns, Params) +properly_documented_public_functions="disabled" +; Check for useless usage of the final attribute +final_attribute_check="enabled" +; Check for virtual calls in the class constructors +vcall_in_ctor="enabled" +; Check for useless user defined initializers +useless_initializer="disabled" +; Check allman brace style +allman_braces_check="disabled" +; Check for redundant attributes +redundant_attributes_check="enabled" +; Check public declarations without a documented unittest +has_public_example="disabled" +; Check for asserts without an explanatory message +assert_without_msg="disabled" +; Check indent of if constraints +if_constraints_indent="disabled" +; Check for @trusted applied to a bigger scope than a single function +trust_too_much="enabled" +; Check for redundant storage classes on variable declarations +redundant_storage_classes="enabled" +; Check for unused function return values +unused_result="enabled" + diff --git a/dub.json b/dub.json new file mode 100644 index 0000000..407b68a --- /dev/null +++ b/dub.json @@ -0,0 +1,3 @@ +{ + "name": "snol" +} diff --git a/source/app.d b/source/app.d new file mode 100644 index 0000000..d9f1eb2 --- /dev/null +++ b/source/app.d @@ -0,0 +1,41 @@ +import std.stdio; +import ast; +import parser; +import runtimedata; + +void main() +{ + + Parser par; + EvalReturn ret; + Expr expr; + + Variable[string] varStack; + writeln("The SNOL environment is now active, you may proceed with giving your commands."); + do + { + write("Command: "); + try + { + par = Parser(readln()); + expr = par.parse(); + if (expr !is null) + { + + ret = expr.evaluate(varStack); + } + } + catch(ParseException e) + { + writeln(e.msg); + } + catch(RunException e) + { + writeln(e.msg); + } + + } + while (ret.type != EVAL_TYPE.EXIT); + writeln("Interpreter is now terminated..."); + +} diff --git a/source/ast.d b/source/ast.d new file mode 100644 index 0000000..8423401 --- /dev/null +++ b/source/ast.d @@ -0,0 +1,366 @@ +module ast; +import std.stdio; +import runtimedata; +import std.string; +import std.conv; + +abstract class Expr +{ + + Expr masterExpr = null; + + EvalReturn evaluate(ref Variable[string] varStack); + +} + +class FloatExpr : Expr +{ + this(float val) + { + value = val; + } + + float value; + + override EvalReturn evaluate(ref Variable[string] varStack) + { + + return EvalReturn(EVAL_TYPE.NUMBER, DATA_TYPE.FLOAT, value); + } +} + +class IntExpr : Expr +{ + this(int val) + { + value = val; + } + + int value; + + override EvalReturn evaluate(ref Variable[string] varStack) + { + return EvalReturn(EVAL_TYPE.NUMBER, DATA_TYPE.INTEGER, value); + } +} + +class VarExpr : Expr +{ + this(string var) + { + varName = var; + } + + string varName; + + override EvalReturn evaluate(ref Variable[string] varStack) + { + const Variable* varPtr = (varName in varStack); + if (varPtr is null) + { + return EvalReturn(EVAL_TYPE.UNDEFINED_VAR, DATA_TYPE.NONE, varName); + } + else + { + return EvalReturn(EVAL_TYPE.VAR, DATA_TYPE.VAR_POINTER, varName); + } + + } +} + +class OpExpr : Expr +{ + this(char op, bool unary = false) + { + this.op = op; + this.unary = unary; + + } + + bool unary; + char op; + Expr RHE; + Expr LHE; + + override EvalReturn evaluate(ref Variable[string] varStack) + { + EvalReturn left = void; + if (!unary) + { + left = LHE.evaluate(varStack); + } + + EvalReturn right = RHE.evaluate(varStack); + if (op == '=') //ASSIGN + { + if (right.type == EVAL_TYPE.UNDEFINED_VAR) + throw new RunException("Error! ["~right.sVar ~ "] is not defined!"); + if (left.type == EVAL_TYPE.UNDEFINED_VAR) + varStack[left.sVar] = Variable(); + + if (right.type == EVAL_TYPE.VAR) + { + varStack[left.sVar].dataType = varStack[right.sVar].dataType; + if (varStack[left.sVar].dataType == DATA_TYPE.INTEGER) + varStack[left.sVar].iVal = varStack[right.sVar].iVal; + else + varStack[left.sVar].fVal = varStack[right.sVar].fVal; + } + else + { + if (right.dataType == DATA_TYPE.INTEGER) + { + varStack[left.sVar].iVal = right.iVal; + varStack[left.sVar].dataType = right.dataType; + } + else if (right.dataType == DATA_TYPE.FLOAT) + { + varStack[left.sVar].fVal = right.fVal; + varStack[left.sVar].dataType = right.dataType; + } + } + return EvalReturn(EVAL_TYPE.ASSIGN, DATA_TYPE.NONE, 0); + + } + else if (op == '-' && unary) //UNARY - + { + if (right.type == EVAL_TYPE.UNDEFINED_VAR) + throw new RunException("Error! ["~right.sVar ~ "] is not defined!"); + if (right.type == EVAL_TYPE.VAR) + { + if (varStack[right.sVar].dataType == DATA_TYPE.INTEGER) + { + return EvalReturn(EVAL_TYPE.NUMBER, DATA_TYPE.INTEGER, -varStack[right.sVar] + .iVal); + } + else if (varStack[right.sVar].dataType == DATA_TYPE.FLOAT) + { + return EvalReturn(EVAL_TYPE.NUMBER, DATA_TYPE.FLOAT, -varStack[right.sVar].fVal); + } + } + else if (right.type == EVAL_TYPE.NUMBER) + { + if (right.dataType == DATA_TYPE.INTEGER) + return EvalReturn(EVAL_TYPE.NUMBER, DATA_TYPE.INTEGER, -right.iVal); + else if (right.dataType == DATA_TYPE.FLOAT) + return EvalReturn(EVAL_TYPE.NUMBER, DATA_TYPE.FLOAT, -right.fVal); + + } + + } + else if (op == '+' && unary) //UNARY + + { + if (right.type == EVAL_TYPE.UNDEFINED_VAR) + throw new RunException("Error! ["~right.sVar ~ "] is not defined!"); + if (right.type == EVAL_TYPE.VAR) + { + if (varStack[right.sVar].dataType == DATA_TYPE.INTEGER) + { + return EvalReturn(EVAL_TYPE.NUMBER, DATA_TYPE.INTEGER, varStack[right.sVar] + .iVal); + } + else if (varStack[right.sVar].dataType == DATA_TYPE.FLOAT) + { + return EvalReturn(EVAL_TYPE.NUMBER, DATA_TYPE.FLOAT, varStack[right.sVar].fVal); + } + } + else if (right.type == EVAL_TYPE.NUMBER) + { + if (right.dataType == DATA_TYPE.INTEGER) + return EvalReturn(EVAL_TYPE.NUMBER, DATA_TYPE.INTEGER, right.iVal); + else if (right.dataType == DATA_TYPE.FLOAT) + return EvalReturn(EVAL_TYPE.NUMBER, DATA_TYPE.FLOAT, right.fVal); + + } + + } + else if (indexOf("*/+-%", op) > -1) // rest + { + if (right.type == EVAL_TYPE.UNDEFINED_VAR) + throw new RunException("Error! ["~ right.sVar ~ "] is not defined!"); + if (left.type == EVAL_TYPE.UNDEFINED_VAR) + throw new RunException("Error! ["~right.sVar ~ "] is not defined!"); + + DATA_TYPE r; + DATA_TYPE l; + + + + union Data + { + int Int; + float Float; + } + Data leftData,rightData; + + + if (right.type == EVAL_TYPE.VAR) + { + if (varStack[right.sVar].dataType == DATA_TYPE.INTEGER) + { + r = varStack[right.sVar].dataType; + rightData.Int = varStack[right.sVar].iVal; + } + else if (varStack[right.sVar].dataType == DATA_TYPE.FLOAT) + { + r = varStack[right.sVar].dataType; + rightData.Float = varStack[right.sVar].fVal; + } + } + else if (right.type == EVAL_TYPE.NUMBER) + { + if (right.dataType == DATA_TYPE.INTEGER) + { + r = right.dataType; + rightData.Int = right.iVal; + } + else if (right.dataType == DATA_TYPE.FLOAT) + { + r = right.dataType; + rightData.Float = right.fVal; + } + + } + + if (left.type == EVAL_TYPE.VAR) + { + if (varStack[left.sVar].dataType == DATA_TYPE.INTEGER) + { + l = varStack[left.sVar].dataType; + leftData.Int = varStack[left.sVar].iVal; + } + else if (varStack[left.sVar].dataType == DATA_TYPE.FLOAT) + { + l = varStack[left.sVar].dataType; + leftData.Float = varStack[left.sVar].fVal; + } + } + else if (left.type == EVAL_TYPE.NUMBER) + { + if (left.dataType == DATA_TYPE.INTEGER) + { + l = right.dataType; + leftData.Int = left.iVal; + } + else if (left.dataType == DATA_TYPE.FLOAT) + { + l = left.dataType; + leftData.Float = left.fVal; + } + + } + + if (l != r) + throw new RunException("Error! Operands must be of the same type in an arithmetic operation!"); + + if (l == DATA_TYPE.INTEGER) + { + switch (op) + { + case '+': + return EvalReturn(EVAL_TYPE.NUMBER, l, leftData.Int + rightData.Int); + case '-': + return EvalReturn(EVAL_TYPE.NUMBER, l, leftData.Int - rightData.Int); + case '*': + return EvalReturn(EVAL_TYPE.NUMBER, l, leftData.Int * rightData.Int); + case '/': + return EvalReturn(EVAL_TYPE.NUMBER, l, leftData.Int / rightData.Int); + case '%': + return EvalReturn(EVAL_TYPE.NUMBER, l, leftData.Int % rightData.Int); + default: break; + } + } + else { + + switch (op) + { + case '+': + return EvalReturn(EVAL_TYPE.NUMBER, l, leftData.Float + rightData.Float); + case '-': + return EvalReturn(EVAL_TYPE.NUMBER, l, leftData.Float - rightData.Float); + case '*': + return EvalReturn(EVAL_TYPE.NUMBER, l, leftData.Float * rightData.Float); + case '/': + return EvalReturn(EVAL_TYPE.NUMBER, l, leftData.Float / rightData.Float); + case '%': + return EvalReturn(EVAL_TYPE.NUMBER, l, leftData.Float % rightData.Float); + default: break; + } + } + + } + + return EvalReturn(EVAL_TYPE.NUMBER, DATA_TYPE.NONE, 0); + } +} + +class BEGExpr : Expr +{ + + VarExpr RHE; + + override EvalReturn evaluate(ref Variable[string] varStack) + { + + EvalReturn r = RHE.evaluate(varStack); + + write("Input: "); + string input = strip(readln()); + + if (isNumeric(input)) + { + if (r.type == EVAL_TYPE.UNDEFINED_VAR) + varStack[r.sVar] = Variable(); + try + { + varStack[r.sVar].iVal = to!int(input); + varStack[r.sVar].dataType = DATA_TYPE.INTEGER; + + } + catch (ConvException e) + { + varStack[r.sVar].fVal = to!float(input); + varStack[r.sVar].dataType = DATA_TYPE.FLOAT; + } + } + else + { + throw new RunException("Error! Cannot convert input"); + } + + return EvalReturn(EVAL_TYPE.BEG, DATA_TYPE.NONE, 0); + } +} + +class PRINTExpr : Expr +{ + + VarExpr RHE; + override EvalReturn evaluate(ref Variable[string] varStack) + { + EvalReturn r = RHE.evaluate(varStack); + if (r.type == EVAL_TYPE.UNDEFINED_VAR) + { + writeln("SNOL> Error! [" ~ r.sVar ~ "] is not defined!"); + } + else if (r.type == EVAL_TYPE.VAR) + { + Variable* v = (r.sVar in varStack); + if (v.dataType == DATA_TYPE.INTEGER) + writeln("SNOL> [" ~ r.sVar ~ "] = ", v.iVal); + else + writeln("SNOL> [" ~ r.sVar ~ "] = ", v.fVal); + + } + + return EvalReturn(EVAL_TYPE.PRINT, DATA_TYPE.NONE, 0); + } +} + +class EXITExpr : Expr +{ + override EvalReturn evaluate(ref Variable[string] varStack) + { + return EvalReturn(EVAL_TYPE.EXIT, DATA_TYPE.NONE, 0); + } +} diff --git a/source/lexer.d b/source/lexer.d new file mode 100644 index 0000000..3639537 --- /dev/null +++ b/source/lexer.d @@ -0,0 +1,152 @@ +module lexer; +import std.stdio; +import core.stdc.ctype; +import std.string; + +enum TOKEN_TYPE +{ + OPERATOR, + INTEGER, + FLOAT, + VARIABLE, + PROCEDURE, + + UNKNOWN, + END_OF_TEXT +} + +struct Token +{ + TOKEN_TYPE type; + string data; + +} + +const string OPERATORS = "=+-*/%"; + +struct Lexer +{ + long position = 0; + private string textToTokenize; + this(string textToTokenize) + { + this.textToTokenize = textToTokenize; + + } + + Token getNextToken() + { + if (position >= textToTokenize.length) + return Token(TOKEN_TYPE.END_OF_TEXT, null); + string dataStr; + char lastChar = textToTokenize[position]; + + while (isspace(lastChar)) + { + position++; + if (position >= textToTokenize.length) + return Token(TOKEN_TYPE.END_OF_TEXT, null); + lastChar = textToTokenize[position]; + } + if (isalpha(lastChar)) + { + dataStr ~= lastChar; + lastChar = textToTokenize[++position]; + while (isalnum(lastChar)) + { + dataStr ~= lastChar; + position++; + if (position >= textToTokenize.length) + break; + else + + lastChar = textToTokenize[position]; + } + + if (dataStr == "BEG") + return Token(TOKEN_TYPE.PROCEDURE, dataStr); + + else if (dataStr == "PRINT") + { + return Token(TOKEN_TYPE.PROCEDURE, dataStr); + } + + else if (dataStr == "EXIT" && lastChar == '!') + { + position++; + return Token(TOKEN_TYPE.PROCEDURE, dataStr); + + } + + else + { + + return Token(TOKEN_TYPE.VARIABLE, dataStr); + } + + } + if (indexOf(OPERATORS, lastChar) > -1) + { + dataStr ~= lastChar; + position++; + return Token(TOKEN_TYPE.OPERATOR, dataStr); + } + + if (isdigit(lastChar)) + { + dataStr ~= lastChar; + position++; + if (position >= textToTokenize.length) + return Token(TOKEN_TYPE.INTEGER, dataStr); + lastChar = textToTokenize[position]; + while (isdigit(lastChar)) + { + dataStr ~= lastChar; + position++; + if (position >= textToTokenize.length) + return Token(TOKEN_TYPE.INTEGER, dataStr); + lastChar = textToTokenize[position]; + + } + + if (lastChar == '.') + { + dataStr ~= lastChar; + position++; + if (position >= textToTokenize.length) + { + position--; + return Token(TOKEN_TYPE.INTEGER, dataStr); + } + lastChar = textToTokenize[position]; + while (isdigit(lastChar)) + { + + dataStr ~= lastChar; + position++; + if (position >= textToTokenize.length) + return Token(TOKEN_TYPE.FLOAT, dataStr); + lastChar = textToTokenize[position]; + } + if(dataStr[$-1] == '.') + { + position--; + return Token(TOKEN_TYPE.INTEGER, dataStr[0..$-1]); + } + return Token(TOKEN_TYPE.FLOAT, dataStr); + + } + else + { + return Token(TOKEN_TYPE.INTEGER, dataStr); + } + + } + + dataStr ~= lastChar; + position++; + return Token(TOKEN_TYPE.UNKNOWN, dataStr); + + } + +} diff --git a/source/parser.d b/source/parser.d new file mode 100644 index 0000000..2eabf79 --- /dev/null +++ b/source/parser.d @@ -0,0 +1,291 @@ +module parser; +import lexer; +import ast; +import std.stdio; +import std.conv; +import std.string; + +class ParseException : Exception +{ + this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) pure nothrow @nogc @safe + { + super(msg, file, line, nextInChain); + } +} + +struct Parser +{ + Lexer lex; + Token[] tokens = []; + Expr[] expr = []; + + this(string textToParse) + { + lex = Lexer(textToParse); + } + + Expr parse() + { + syntaxSanityCheck(); + buildAST(); + + + Expr topExpr = null; + if(expr.length < 1) + return null; + foreach (Expr key; expr) + { + if (key.masterExpr is null) + { + topExpr = key; + break; + } + } + return topExpr; + + } + + private void syntaxSanityCheck() + { + Token currentToken = lex.getNextToken(); + tokens ~= currentToken; + while (currentToken.type != TOKEN_TYPE.END_OF_TEXT) + { + //writeln(currentToken); + + if (currentToken.type == TOKEN_TYPE.PROCEDURE && currentToken.data == "BEG") + { + currentToken = lex.getNextToken(); + tokens ~= currentToken; + if (currentToken.type != TOKEN_TYPE.VARIABLE) + { + throw new ParseException("Unknown command! Does not match any valid command of the language."); + } + + currentToken = lex.getNextToken(); + tokens ~= currentToken; + if (currentToken.type != TOKEN_TYPE.END_OF_TEXT) + { + throw new ParseException("Unknown command! Does not match any valid command of the language."); + } + + } + if (currentToken.type == TOKEN_TYPE.PROCEDURE && currentToken.data == "EXIT") + { + + currentToken = lex.getNextToken(); + tokens ~= currentToken; + if (currentToken.type != TOKEN_TYPE.END_OF_TEXT) + { + throw new ParseException("Unknown command! Does not match any valid command of the language."); + } + + } + if (currentToken.type == TOKEN_TYPE.PROCEDURE && currentToken.data == "PRINT") + { + currentToken = lex.getNextToken(); + tokens ~= currentToken; + if (currentToken.type != TOKEN_TYPE.VARIABLE) + { + throw new ParseException("Unknown command! Does not match any valid command of the language."); + } + + currentToken = lex.getNextToken(); + tokens ~= currentToken; + if (currentToken.type != TOKEN_TYPE.END_OF_TEXT) + { + throw new ParseException("Unknown command! Does not match any valid command of the language."); + } + + } + else if (currentToken.type == TOKEN_TYPE.OPERATOR) + { + + if (currentToken.data == "=") + { + + if ((tokens.length > 1 || tokens.length == 0) && tokens[0].type != TOKEN_TYPE + .VARIABLE) + { + throw new ParseException("Unknown command! Does not match any valid command of the language."); + } + currentToken = lex.getNextToken(); + tokens ~= currentToken; + + } + else if (currentToken.data == "-") + { + + currentToken = lex.getNextToken(); + tokens ~= currentToken; + if (currentToken.type != TOKEN_TYPE.VARIABLE && currentToken.type != TOKEN_TYPE.INTEGER && currentToken + .type != TOKEN_TYPE.FLOAT) + throw new ParseException("Unknown command! Does not match any valid command of the language."); + if (tokens.length <= 2 || (tokens[$ - 3].type != TOKEN_TYPE.VARIABLE && tokens[$ - 3].type != TOKEN_TYPE + .INTEGER && tokens[$ - 3].type != TOKEN_TYPE.FLOAT)) + { + tokens[$ - 2].data = 'u' ~ tokens[$ - 2].data; + } + } + else if (currentToken.data == "+") + { + + currentToken = lex.getNextToken(); + tokens ~= currentToken; + if (currentToken.type != TOKEN_TYPE.VARIABLE && currentToken.type != TOKEN_TYPE.INTEGER && currentToken + .type != TOKEN_TYPE.FLOAT) + throw new ParseException("Unknown command! Does not match any valid command of the language."); + if (tokens.length <= 2 || (tokens[$ - 3].type != TOKEN_TYPE.VARIABLE && tokens[$ - 3].type != TOKEN_TYPE + .INTEGER && tokens[$ - 3].type != TOKEN_TYPE.FLOAT)) + { + tokens[$ - 2].data = 'u' ~ tokens[$ - 2].data; + } + + } + else + { + if (tokens[$ - 2].type != TOKEN_TYPE.VARIABLE && tokens[$ - 2].type != TOKEN_TYPE.INTEGER && tokens[$ - 2] + .type != TOKEN_TYPE.FLOAT) + throw new ParseException( + "Unknown command! Does not match any valid"); + currentToken = lex.getNextToken(); + tokens ~= currentToken; + /*if(currentToken.type!=TOKEN_TYPE.VARIABLE && currentToken.type!=TOKEN_TYPE.INTEGER && currentToken.type!=TOKEN_TYPE.FLOAT) + throw new ParseException("Wrong usage of '"~tokens[$-1].data~"' operator");*/ + } + + } + + else if (currentToken.type == TOKEN_TYPE.FLOAT || currentToken.type == TOKEN_TYPE.INTEGER || currentToken + .type == TOKEN_TYPE.VARIABLE) + { + currentToken = lex.getNextToken(); + tokens ~= currentToken; + if (currentToken.type != TOKEN_TYPE.OPERATOR && currentToken.type != TOKEN_TYPE + .END_OF_TEXT) + { + + throw new ParseException("Unknown command! Does not match any valid command of the language."); + } + + } + else if( currentToken.type == TOKEN_TYPE.UNKNOWN) + { + throw new ParseException("Unexpected token: " ~ currentToken.data); + } + else + { + + currentToken = lex.getNextToken(); + tokens ~= currentToken; + } + } + + } + + private void opMaker(int id) + { + OpExpr opExpr = cast(OpExpr) expr[id]; + if (!opExpr.unary) + { + Expr leftExpr = expr[id - 1]; + while (leftExpr.masterExpr !is null) + { + leftExpr = leftExpr.masterExpr; + } + leftExpr.masterExpr = opExpr; + opExpr.LHE = leftExpr; + } + + Expr rightExpr = expr[id + 1]; + while (rightExpr.masterExpr !is null) + { + rightExpr = rightExpr.masterExpr; + } + rightExpr.masterExpr = opExpr; + opExpr.RHE = rightExpr; + } + + private void buildAST() + { + for (int i = 0; i < tokens.length; i++) + { + if (tokens[i].type == TOKEN_TYPE.END_OF_TEXT) + break; + else if (tokens[i].type == TOKEN_TYPE.FLOAT) + expr ~= new FloatExpr(to!float(tokens[i].data)); + else if (tokens[i].type == TOKEN_TYPE.INTEGER) + expr ~= new IntExpr(to!int(tokens[i].data)); + else if (tokens[i].type == TOKEN_TYPE.OPERATOR && tokens[i].data[0] == 'u') + expr ~= new OpExpr(to!char(tokens[i].data[1]), true); + else if (tokens[i].type == TOKEN_TYPE.OPERATOR) + expr ~= new OpExpr(to!char(tokens[i].data)); + else if (tokens[i].type == TOKEN_TYPE.VARIABLE) + expr ~= new VarExpr(to!string(tokens[i].data)); + else if (tokens[i].type == TOKEN_TYPE.PROCEDURE && tokens[i].data == "BEG") + expr ~= new BEGExpr(); + else if (tokens[i].type == TOKEN_TYPE.PROCEDURE && tokens[i].data == "PRINT") + expr ~= new PRINTExpr(); + else if (tokens[i].type == TOKEN_TYPE.PROCEDURE && tokens[i].data == "EXIT") + expr ~= new EXITExpr(); + + } + if(expr.length < 1) + return; + + for (int i = 0; i < expr.length; i++) // unary + { + if (typeid(expr[i]) == typeid(OpExpr) && (cast(OpExpr) expr[i]).unary) + { + opMaker(i); + } + + } + + for (int i = 0; i < expr.length; i++) // * / % + { + if (typeid(expr[i]) == typeid(OpExpr) && indexOf("*/%", (cast(OpExpr) expr[i]).op) > -1 && !( + cast(OpExpr) expr[i]).unary) + { + opMaker(i); + } + + } + for (int i = 0; i < expr.length; i++) // + - + { + if (typeid(expr[i]) == typeid(OpExpr) && indexOf("+-", (cast(OpExpr) expr[i]).op) > -1 && !( + cast(OpExpr) expr[i]).unary) + { + opMaker(i); + } + + } + for (int i = 0; i < expr.length; i++) // = + { + if (typeid(expr[i]) == typeid(OpExpr) && indexOf("=", (cast(OpExpr) expr[i]).op) > -1 && !( + cast(OpExpr) expr[i]).unary) + { + opMaker(i); + } + + } + + //PROCEDURES expr + if (typeid(expr[0]) == typeid(BEGExpr)) + { + BEGExpr begExpr = (cast(BEGExpr) expr[0]); + VarExpr rightExpr = cast(VarExpr) expr[1]; + rightExpr.masterExpr = begExpr; + begExpr.RHE = rightExpr; + } + else if (typeid(expr[0]) == typeid(PRINTExpr)) + { + PRINTExpr printExpr = (cast(PRINTExpr) expr[0]); + VarExpr rightExpr = cast(VarExpr) expr[1]; + rightExpr.masterExpr = printExpr; + printExpr.RHE = rightExpr; + } + + } + +} diff --git a/source/runtimedata.d b/source/runtimedata.d new file mode 100644 index 0000000..06a7b74 --- /dev/null +++ b/source/runtimedata.d @@ -0,0 +1,76 @@ +module runtimedata; + + +enum DEBUG = true; + +class RunException : Exception +{ + this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) pure nothrow @nogc @safe + { + super(msg, file, line, nextInChain); + } +} + + +enum DATA_TYPE +{ + FLOAT, + INTEGER, + VAR_POINTER, + NONE +} +enum EVAL_TYPE +{ + NUMBER, + UNDEFINED_VAR, + VAR, + PRINT, + BEG, + ASSIGN, + EXIT +} + +struct EvalReturn +{ + this(EVAL_TYPE type,DATA_TYPE dataType, float fVal ) + { + this.type=type; + this.dataType = dataType; + this.fVal = fVal; + } + this(EVAL_TYPE type,DATA_TYPE dataType, int iVal) + { + this.type=type; + this.dataType = dataType; + this.iVal = iVal; + + } + this(EVAL_TYPE type,DATA_TYPE dataType, string sVar) + { + this.type=type; + this.dataType = dataType; + this.sVar = sVar; + + } + + EVAL_TYPE type; + DATA_TYPE dataType; + union + { + float fVal; + int iVal; + string sVar; + } + +} + +struct Variable +{ + DATA_TYPE dataType; + union + { + float fVal; + int iVal; + } + +} \ No newline at end of file