Add existing files
This commit is contained in:
parent
51b4289c74
commit
0f35a253a9
|
@ -0,0 +1,7 @@
|
|||
.dub
|
||||
docs.json
|
||||
__dummy.html
|
||||
*.o
|
||||
*.obj
|
||||
snol
|
||||
.vscode
|
|
@ -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.
|
|
@ -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"
|
||||
|
|
@ -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...");
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue