forked from MirrorRepos/RomWBW
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
755 lines
20 KiB
755 lines
20 KiB
//////////////////////////////////////////////////////////////
|
|
// //
|
|
// Propeller Spin/PASM Compiler //
|
|
// (c)2012-2016 Parallax Inc. DBA Parallax Semiconductor. //
|
|
// Adapted from Chip Gracey's x86 asm code by Roy Eltham //
|
|
// See end of file for terms of use. //
|
|
// //
|
|
//////////////////////////////////////////////////////////////
|
|
//
|
|
// CompileExpression.cpp
|
|
//
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include "Utilities.h"
|
|
#include "PropellerCompilerInternal.h"
|
|
#include "SymbolEngine.h"
|
|
#include "Elementizer.h"
|
|
#include "ErrorStrings.h"
|
|
#include "CompileUtilities.h"
|
|
|
|
//
|
|
//************************************************************************
|
|
//* Expression Compiler *
|
|
//************************************************************************
|
|
//
|
|
// Basic expression syntax rules: i.e. 4000/(||x*5)//127)+1
|
|
//
|
|
// Any one of these... Must be followed by any one of these...
|
|
// ------------------------------------------------------------------
|
|
// term binary operator
|
|
// ) )
|
|
// <end>
|
|
//
|
|
// Any one of these... Must be followed by any one of these... *
|
|
// ------------------------------------------------------------------
|
|
// unary operator term
|
|
// binary operator unary operator
|
|
// ( (
|
|
//
|
|
// * initial element of an expression
|
|
//
|
|
|
|
// forward declarations
|
|
bool CompileTerm();
|
|
bool CompileSubExpression(int precedence);
|
|
bool CompileTopExpression();
|
|
|
|
// Compile expression with sub-expressions
|
|
bool CompileExpression()
|
|
{
|
|
if (!CompileTopExpression())
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CompileTopExpression()
|
|
{
|
|
if (!CompileSubExpression(11))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CompileSubExpression_Term()
|
|
{
|
|
// get next element ignoring any leading +'s
|
|
bool bEof = false;
|
|
do
|
|
{
|
|
if (!g_pElementizer->GetNext(bEof))
|
|
{
|
|
return false;
|
|
}
|
|
} while (g_pElementizer->GetType() == type_binary && g_pElementizer->GetOpType() == op_add);
|
|
|
|
if (!g_pElementizer->NegConToCon())
|
|
{
|
|
return false;
|
|
}
|
|
g_pElementizer->SubToNeg();
|
|
|
|
int opType = g_pElementizer->GetOpType();
|
|
|
|
switch (g_pElementizer->GetType())
|
|
{
|
|
case type_atat:
|
|
if (!CompileSubExpression(0))
|
|
{
|
|
return false;
|
|
}
|
|
if (!EnterObj(0x97)) // memop byte+index+pbase+address
|
|
{
|
|
return false;
|
|
}
|
|
if (!EnterObj(0)) // address 0
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case type_unary:
|
|
if (!CompileSubExpression(g_pElementizer->GetValue())) // value = precedence for type_unary
|
|
{
|
|
return false;
|
|
}
|
|
if (!EnterObj((unsigned char)opType | 0xE0)) // math
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case type_left:
|
|
if (!CompileTopExpression())
|
|
{
|
|
return false;
|
|
}
|
|
if (!g_pElementizer->GetElement(type_right))
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (!CompileTerm())
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CompileSubExpression(int precedence)
|
|
{
|
|
precedence--;
|
|
if (precedence < 0)
|
|
{
|
|
if (!CompileSubExpression_Term())
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (!CompileSubExpression(precedence))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
bool bEof = false;
|
|
if (!g_pElementizer->GetNext(bEof))
|
|
{
|
|
return false;
|
|
}
|
|
if (g_pElementizer->GetType() != type_binary)
|
|
{
|
|
g_pElementizer->Backup();
|
|
break;
|
|
}
|
|
// if we got here then it's type_binary (so the value is the precedence)
|
|
if (g_pElementizer->GetValue() != precedence)
|
|
{
|
|
g_pElementizer->Backup();
|
|
break;
|
|
}
|
|
int opType = g_pElementizer->GetOpType();
|
|
if (!CompileSubExpression(precedence))
|
|
{
|
|
return false;
|
|
}
|
|
if (!EnterObj((unsigned char)(opType | 0xE0)))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// CompileTerm functions
|
|
//
|
|
|
|
// compile constant(constantexpression)
|
|
bool CompileTerm_ConExp()
|
|
{
|
|
if (!g_pElementizer->GetElement(type_left))
|
|
{
|
|
return false;
|
|
}
|
|
if (!GetTryValue(true, false))
|
|
{
|
|
return false;
|
|
}
|
|
if (!CompileConstant(GetResult()))
|
|
{
|
|
return false;
|
|
}
|
|
return g_pElementizer->GetElement(type_right);
|
|
}
|
|
|
|
// compile string("constantstring")
|
|
bool CompileTerm_ConStr()
|
|
{
|
|
if (g_pCompilerData->str_enable == false)
|
|
{
|
|
g_pCompilerData->error = true;
|
|
g_pCompilerData->error_msg = g_pErrorStrings[error_snah];
|
|
return false;
|
|
}
|
|
if (!g_pElementizer->GetElement(type_left))
|
|
{
|
|
return false;
|
|
}
|
|
if (!StringConstant_GetIndex()) // get index in g_pCompilerData->str_index
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// get the string into the string constant buffer
|
|
for (;;)
|
|
{
|
|
if (!GetTryValue(true, false))
|
|
{
|
|
return false;
|
|
}
|
|
int value = GetResult();
|
|
if (g_pCompilerData->intMode == 2 || value == 0 || value > 0xFF)
|
|
{
|
|
g_pCompilerData->error = true;
|
|
g_pCompilerData->error_msg = g_pErrorStrings[error_scmr];
|
|
return false;
|
|
}
|
|
if (!StringConstant_EnterChar((unsigned char)(value & 0xFF))) // add character to string constant buffer
|
|
{
|
|
return false;
|
|
}
|
|
// more characters?
|
|
bool bComma = false;
|
|
if (!GetCommaOrRight(bComma))
|
|
{
|
|
return false;
|
|
}
|
|
if (!bComma)
|
|
{
|
|
// got right ')'
|
|
break;
|
|
}
|
|
}
|
|
StringConstant_EnterChar(0); // enter 0 terminator into string constant buffer
|
|
|
|
if (!EnterObj(0x87)) // (memcp byte+pbase+address)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
StringConstant_EnterPatch(); // enter string constant patch address
|
|
|
|
// enter two address bytes (patched later)
|
|
if (!EnterObj(0x80))
|
|
{
|
|
return false;
|
|
}
|
|
return EnterObj(0);
|
|
}
|
|
|
|
// compile float(integer)/round(float)/trunc(float)
|
|
bool CompileTerm_FloatRoundTrunc()
|
|
{
|
|
g_pElementizer->Backup(); // backup to float/round/trunc
|
|
|
|
if (!GetTryValue(true, false))
|
|
{
|
|
return false;
|
|
}
|
|
return CompileConstant(GetResult());
|
|
}
|
|
|
|
bool CompileTerm_Sub(unsigned char anchor, int value)
|
|
{
|
|
if (!EnterObj(anchor)) // drop anchor
|
|
{
|
|
return false;
|
|
}
|
|
if (!CompileParameters((value & 0x0000FF00) >> 8))
|
|
{
|
|
return false;
|
|
}
|
|
if (!EnterObj(0x05)) // call sub
|
|
{
|
|
return false;
|
|
}
|
|
return EnterObj((unsigned char)(value & 0xFF)); // index of sub
|
|
}
|
|
|
|
// compile obj[].pub
|
|
bool CompileTerm_ObjPub(unsigned char anchor, int value)
|
|
{
|
|
if (!EnterObj(anchor)) // drop anchor
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// check for [index]
|
|
bool bIndex = false;
|
|
int expSourcePtr = 0;
|
|
if (!CheckIndex(bIndex, expSourcePtr))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!g_pElementizer->GetElement(type_dot))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// lookup the pub symbol
|
|
if (!GetObjSymbol(type_objpub, (char)((value & 0x0000FF00) >> 8)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int objPubValue = g_pElementizer->GetValue();
|
|
|
|
// compile any parameters the pub has
|
|
if (!CompileParameters((objPubValue & 0x0000FF00) >> 8))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
unsigned char byteCode = 0x06; // call obj.pub
|
|
if (bIndex)
|
|
{
|
|
if (!CompileOutOfSequenceExpression(expSourcePtr))
|
|
{
|
|
return false;
|
|
}
|
|
byteCode = 0x07; // call obj[].pub
|
|
}
|
|
if (!EnterObj(byteCode))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!EnterObj((unsigned char)(value & 0xFF))) // index of obj
|
|
{
|
|
return false;
|
|
}
|
|
return EnterObj((unsigned char)(objPubValue & 0xFF)); // index of objpub
|
|
}
|
|
|
|
// compile obj[].pub\obj[]#con
|
|
bool CompileTerm_ObjPubCon(int value)
|
|
{
|
|
if (!g_pElementizer->CheckElement(type_pound)) // check for obj#con
|
|
{
|
|
// not obj#con, so do obj[].pub
|
|
return CompileTerm_ObjPub(0, value);
|
|
}
|
|
// lookup the symbol to get the value to compile
|
|
if (!GetObjSymbol(type_objcon, (char)((value & 0x0000FF00) >> 8)))
|
|
{
|
|
return false;
|
|
}
|
|
return CompileConstant(g_pElementizer->GetValue());
|
|
}
|
|
|
|
// compile \sub or \obj
|
|
bool CompileTerm_Try(unsigned char anchor)
|
|
{
|
|
bool bEof = false;
|
|
if (!g_pElementizer->GetNext(bEof))
|
|
{
|
|
return false;
|
|
}
|
|
if (g_pElementizer->GetType() == type_sub)
|
|
{
|
|
return CompileTerm_Sub(anchor, g_pElementizer->GetValue());
|
|
}
|
|
else if (g_pElementizer->GetType() == type_obj)
|
|
{
|
|
return CompileTerm_ObjPub(anchor, g_pElementizer->GetValue());
|
|
}
|
|
|
|
g_pCompilerData->error = true;
|
|
g_pCompilerData->error_msg = g_pErrorStrings[error_easoon];
|
|
return false;
|
|
}
|
|
|
|
bool CompileLook(int column, int param)
|
|
{
|
|
column = column; // stop warning
|
|
|
|
param &= 0xFF; // we only care about the bottom byte
|
|
|
|
unsigned char byteCode = 0x35; // constant 0
|
|
if (param < 0x80) // zero based?
|
|
{
|
|
byteCode += 1; // not, so make it a constant 1
|
|
}
|
|
if (!EnterObj(byteCode))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!BlockStack_CompileConstant()) // enter address constant
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!g_pElementizer->GetElement(type_left))
|
|
{
|
|
return false;
|
|
}
|
|
if (!CompileExpression()) // compile primary value
|
|
{
|
|
return false;
|
|
}
|
|
if (!g_pElementizer->GetElement(type_colon))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
bool bRange = false;
|
|
if (!CompileRange(bRange)) // compile (next) value/range
|
|
{
|
|
return false;
|
|
}
|
|
byteCode = (unsigned char)param;
|
|
if (bRange)
|
|
{
|
|
byteCode |= 2;
|
|
}
|
|
if (!EnterObj(byteCode & 0x7F))
|
|
{
|
|
return false;
|
|
}
|
|
bool bComma = false;
|
|
if (!GetCommaOrRight(bComma))
|
|
{
|
|
return false;
|
|
}
|
|
if (!bComma)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!EnterObj(0x0F)) // lookdone
|
|
{
|
|
return false;
|
|
}
|
|
|
|
BlockStack_Write(0, g_pCompilerData->obj_ptr); // set address
|
|
return true;
|
|
}
|
|
|
|
// compile 'lookup'/'lookdown'
|
|
// this one compiles like a block (see InstructionBlockCompiler.cpp stuff)
|
|
bool CompileTerm_Look(int value)
|
|
{
|
|
if (!BlockNest_New(type_i_look, 1))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!OptimizeBlock(0, value, &CompileLook))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
BlockNest_End();
|
|
return true;
|
|
}
|
|
|
|
bool CompileTerm_ClkMode()
|
|
{
|
|
if (!EnterObj(0x38)) // constant 4
|
|
{
|
|
return false;
|
|
}
|
|
if (!EnterObj(4))
|
|
{
|
|
return false;
|
|
}
|
|
return EnterObj(0x80); // read byte[]
|
|
}
|
|
|
|
bool CompileTerm_ClkFreq()
|
|
{
|
|
if (!EnterObj(0x35)) // constant 0
|
|
{
|
|
return false;
|
|
}
|
|
return EnterObj(0xC0); // read long[]
|
|
}
|
|
|
|
bool CompileTerm_ChipVer()
|
|
{
|
|
if (!EnterObj(0x34)) // constant -1
|
|
{
|
|
return false;
|
|
}
|
|
return EnterObj(0x80); // read byte[]
|
|
}
|
|
|
|
bool CompileTerm_CogId()
|
|
{
|
|
if (!EnterObj(0x3F)) // reg op
|
|
{
|
|
return false;
|
|
}
|
|
return EnterObj(0x89); // read id
|
|
}
|
|
|
|
bool CompileTerm_Inst(int value)
|
|
{
|
|
if (!CompileParameters((value & 0xFF) >> 6))
|
|
{
|
|
return false;
|
|
}
|
|
return EnterObj((unsigned char)(value & 0x3F)); // instruction
|
|
}
|
|
|
|
bool CompileTerm_CogNew(int value)
|
|
{
|
|
// see if first param is a sub
|
|
if (!g_pElementizer->GetElement(type_left))
|
|
{
|
|
return false;
|
|
}
|
|
bool bEof = false;
|
|
if (!g_pElementizer->GetNext(bEof))
|
|
{
|
|
return false;
|
|
}
|
|
if (g_pElementizer->GetType() == type_sub)
|
|
{
|
|
int subConstant = g_pElementizer->GetValue();
|
|
|
|
if (!g_pCompilerData->bFinalCompile && g_pCompilerData->bUnusedMethodElimination)
|
|
{
|
|
AddCogNewOrInit(g_pCompilerData->current_filename, subConstant & 0x000000FF);
|
|
}
|
|
|
|
// it is a sub, so compile as cognew(subname(params),stack)
|
|
if (!CompileParameters((g_pElementizer->GetValue() & 0x0000FF00) >> 8))
|
|
{
|
|
return false;
|
|
}
|
|
if (!CompileConstant(subConstant))
|
|
{
|
|
return false;
|
|
}
|
|
if (!g_pElementizer->GetElement(type_comma))
|
|
{
|
|
return false;
|
|
}
|
|
if (!CompileExpression()) // compile stack expression
|
|
{
|
|
return false;
|
|
}
|
|
if (!g_pElementizer->GetElement(type_right))
|
|
{
|
|
return false;
|
|
}
|
|
if (!EnterObj(0x15)) // run
|
|
{
|
|
return false;
|
|
}
|
|
return EnterObj((unsigned char)(value & 0x3F)); // coginit
|
|
}
|
|
|
|
// it is not a sub, so backup and compile as cognew(address, parameter)
|
|
g_pElementizer->Backup();
|
|
g_pElementizer->Backup();
|
|
|
|
if (!EnterObj(0x34)) // constant -1
|
|
{
|
|
return false;
|
|
}
|
|
return CompileTerm_Inst(value);
|
|
}
|
|
|
|
// compile @var
|
|
bool CompileTerm_At()
|
|
{
|
|
unsigned char varType = 0;
|
|
unsigned char varSize = 0;
|
|
int varAddress = 0;
|
|
int varIndexSourcePtr = 0;
|
|
if (!GetVariable(varType, varSize, varAddress, varIndexSourcePtr))
|
|
{
|
|
return false;
|
|
}
|
|
if (varType == type_reg || varType == type_spr)
|
|
{
|
|
g_pCompilerData->error = true;
|
|
g_pCompilerData->error_msg = g_pErrorStrings[error_eamvaa];
|
|
return false;
|
|
}
|
|
return CompileVariable(3, 0, varType, varSize, varAddress, varIndexSourcePtr);
|
|
}
|
|
|
|
bool CompileTerm()
|
|
{
|
|
int type = g_pElementizer->GetType();
|
|
int value = g_pElementizer->GetValue();
|
|
|
|
switch(type)
|
|
{
|
|
case type_con:
|
|
case type_con_float:
|
|
return CompileConstant(value);
|
|
case type_conexp:
|
|
return CompileTerm_ConExp();
|
|
case type_constr:
|
|
return CompileTerm_ConStr();
|
|
case type_float:
|
|
case type_round:
|
|
case type_trunc:
|
|
return CompileTerm_FloatRoundTrunc();
|
|
case type_back:
|
|
return CompileTerm_Try(0x02);
|
|
case type_sub:
|
|
return CompileTerm_Sub(0, value);
|
|
case type_obj:
|
|
return CompileTerm_ObjPubCon(value);
|
|
case type_i_look:
|
|
return CompileTerm_Look(value);
|
|
case type_i_clkmode:
|
|
return CompileTerm_ClkMode();
|
|
case type_i_clkfreq:
|
|
return CompileTerm_ClkFreq();
|
|
case type_i_chipver:
|
|
return CompileTerm_ChipVer();
|
|
case type_i_cogid:
|
|
return CompileTerm_CogId();
|
|
case type_i_cognew:
|
|
return CompileTerm_CogNew(value);
|
|
case type_i_ar: // instruction always-returns
|
|
case type_i_cr: // instruction can-return
|
|
return CompileTerm_Inst(value);
|
|
case type_at: // @var
|
|
return CompileTerm_At();
|
|
case type_inc: // assign pre-inc w/push ++var
|
|
return CompileVariable_PreIncOrDec(0xA0);
|
|
case type_dec: // assign pre-dec w/push --var
|
|
return CompileVariable_PreIncOrDec(0xB0);
|
|
case type_til: // assign sign-extern byte w/push ~var
|
|
return CompileVariable_PreSignExtendOrRandom(0x90);
|
|
case type_tiltil: // assign sign-extern word w/push ~~var
|
|
return CompileVariable_PreSignExtendOrRandom(0x94);
|
|
case type_rnd: // assign random forward w/push ?var
|
|
return CompileVariable_PreSignExtendOrRandom(0x88);
|
|
}
|
|
|
|
unsigned char varType = 0;
|
|
unsigned char varSize = 0;
|
|
int varAddress = 0;
|
|
int varIndexSourcePtr = 0;
|
|
bool bVariable = false;
|
|
if (!CheckVariable(bVariable, varType, varSize, varAddress, varIndexSourcePtr))
|
|
{
|
|
return false;
|
|
}
|
|
if (!bVariable)
|
|
{
|
|
g_pCompilerData->error = true;
|
|
g_pCompilerData->error_msg = g_pErrorStrings[error_eaet];
|
|
return false;
|
|
}
|
|
|
|
// check for post-var modifier
|
|
bool bEof = false;
|
|
if (!g_pElementizer->GetNext(bEof))
|
|
{
|
|
return false;
|
|
}
|
|
type = g_pElementizer->GetType();
|
|
switch (type)
|
|
{
|
|
case type_inc: // assign post-inc w/push var++
|
|
return CompileVariable_IncOrDec(0xA8, varType, varSize, varAddress, varIndexSourcePtr);
|
|
case type_dec: // assign post-dec w/push var--
|
|
return CompileVariable_IncOrDec(0xB8, varType, varSize, varAddress, varIndexSourcePtr);
|
|
case type_rnd: // assign random reverse w/push var?
|
|
return CompileVariable_Assign(0x8C, varType, varSize, varAddress, varIndexSourcePtr);
|
|
case type_til: // assign post-clear w/push var~
|
|
return CompileVariable_Assign(0x98, varType, varSize, varAddress, varIndexSourcePtr);
|
|
case type_tiltil: // assign post-set w/push var~~
|
|
return CompileVariable_Assign(0x9C, varType, varSize, varAddress, varIndexSourcePtr);
|
|
case type_assign: // assign write w/push var :=
|
|
return CompileVariable_Expression(0x80, varType, varSize, varAddress, varIndexSourcePtr);
|
|
}
|
|
|
|
unsigned char varOperator = 0x80; // assign write w/push
|
|
// var binaryop?
|
|
if (type == type_binary)
|
|
{
|
|
varOperator = 0xC0; // assign math w/swapargs w/push
|
|
varOperator |= (unsigned char)(g_pElementizer->GetOpType());
|
|
|
|
// check for '=' after binary op
|
|
if (!g_pElementizer->GetNext(bEof))
|
|
{
|
|
return false;
|
|
}
|
|
if (g_pElementizer->GetType() == type_equal)
|
|
{
|
|
return CompileVariable_Expression(varOperator, varType, varSize, varAddress, varIndexSourcePtr);
|
|
}
|
|
else
|
|
{
|
|
g_pElementizer->Backup(); // not '=' so backup
|
|
}
|
|
}
|
|
g_pElementizer->Backup(); // no post-var modifier, so backup
|
|
return CompileVariable(0, varOperator, varType, varSize, varAddress, varIndexSourcePtr);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
// TERMS OF USE: MIT License //
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this //
|
|
// software and associated documentation files (the "Software"), to deal in the Software //
|
|
// without restriction, including without limitation the rights to use, copy, modify, //
|
|
// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to //
|
|
// permit persons to whom the Software is furnished to do so, subject to the following //
|
|
// conditions: //
|
|
// //
|
|
// The above copyright notice and this permission notice shall be included in all copies //
|
|
// or substantial portions of the Software. //
|
|
// //
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, //
|
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A //
|
|
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT //
|
|
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION //
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE //
|
|
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. //
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
|