Add existing files

This commit is contained in:
Mateusz Słodkowicz 2022-07-25 22:07:11 +02:00
parent 51b4289c74
commit 0f35a253a9
9 changed files with 1232 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.dub
docs.json
__dummy.html
*.o
*.obj
snol
.vscode

193
PROGRAM SPECIFICATIONS Normal file
View File

@ -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 Cs 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 Cs 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 Cs 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.

103
dscanner.ini Normal file
View File

@ -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"

3
dub.json Normal file
View File

@ -0,0 +1,3 @@
{
"name": "snol"
}

41
source/app.d Normal file
View File

@ -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...");
}

366
source/ast.d Normal file
View File

@ -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);
}
}

152
source/lexer.d Normal file
View File

@ -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);
}
}

291
source/parser.d Normal file
View File

@ -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;
}
}
}

76
source/runtimedata.d Normal file
View File

@ -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;
}
}