mirror of https://github.com/wwarthen/RomWBW.git
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.
810 lines
24 KiB
810 lines
24 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. //
|
|
// //
|
|
//////////////////////////////////////////////////////////////
|
|
//
|
|
// Elementizer.cpp
|
|
//
|
|
|
|
#include <string.h>
|
|
#include "PropellerCompilerInternal.h"
|
|
#include "Elementizer.h"
|
|
#include "SymbolEngine.h"
|
|
#include "ErrorStrings.h"
|
|
#include "Utilities.h"
|
|
|
|
// private
|
|
|
|
// set elementizer data from the currently set symbol entry
|
|
void Elementizer::SetFromSymbolEntry()
|
|
{
|
|
if (m_pSymbolEntry)
|
|
{
|
|
m_type = m_pSymbolEntry->m_data.type;
|
|
m_value = m_pSymbolEntry->m_data.value;
|
|
m_value_2 = m_pSymbolEntry->m_data.value_2;
|
|
if (m_pSymbolEntry->m_data.dual)
|
|
{
|
|
m_dual = true;
|
|
m_asm = m_pSymbolEntry->m_data.operator_type_or_asm;
|
|
}
|
|
else
|
|
{
|
|
m_dual = false;
|
|
m_opType = m_pSymbolEntry->m_data.operator_type_or_asm;
|
|
|
|
// fixup for AND and OR to have asm also
|
|
if (m_type == type_binary && m_opType == op_log_and)
|
|
{
|
|
m_asm = 0x18 + 0x40;
|
|
}
|
|
if (m_type == type_binary && m_opType == op_log_or)
|
|
{
|
|
m_asm = 0x1A + 0x40;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_type = 0;
|
|
m_value = 0;
|
|
m_value_2 = 0;
|
|
m_asm = -1;
|
|
m_opType = -1;
|
|
}
|
|
}
|
|
|
|
// public
|
|
|
|
// reset to start of source
|
|
void Elementizer::Reset()
|
|
{
|
|
m_sourceOffset = 0;
|
|
m_sourceFlags = 0;
|
|
}
|
|
|
|
// get the next element in source, returns true no error, bEof will be set to true if eof is hit
|
|
bool Elementizer::GetNext(bool& bEof)
|
|
{
|
|
// update back data
|
|
m_backOffsets[m_backIndex&0x03] = m_sourceOffset;
|
|
m_backFlags[m_backIndex&0x03] = m_sourceFlags;
|
|
m_backIndex++;
|
|
|
|
// default to type_undefined
|
|
m_type = 0;
|
|
m_value = 0;
|
|
m_value_2 = 0;
|
|
m_asm = -1;
|
|
m_opType = -1;
|
|
m_pSymbolEntry = 0;
|
|
|
|
// no error, and not end of file
|
|
int error = error_none;
|
|
bEof = false;
|
|
bool bDocComment = false;
|
|
int constantBase = 0;
|
|
|
|
// setup source and symbol pointers
|
|
char* pSource = m_pCompilerData->source;
|
|
int sourceStart = m_sourceOffset;
|
|
|
|
m_currentSymbol[0] = 0;
|
|
int symbolOffset = 0;
|
|
bool bConstantOverflow = false;
|
|
|
|
for (;;)
|
|
{
|
|
char currentChar = pSource[m_sourceOffset++];
|
|
|
|
// parse
|
|
if (constantBase > 0)
|
|
{
|
|
// this handles reading in a constant of base 2, 4, 10, or 16
|
|
// the constantBase value is set based on prefix characters handled below
|
|
|
|
if (currentChar == '_')
|
|
{
|
|
// skip over _'s
|
|
continue;
|
|
}
|
|
char digitValue;
|
|
if (!CheckDigit(currentChar, digitValue, (char)constantBase))
|
|
{
|
|
char notUsed;
|
|
char nextChar = pSource[m_sourceOffset];
|
|
bool bNextCharDigit = CheckDigit(nextChar, notUsed, (char)constantBase);
|
|
|
|
if ((constantBase == 10 &&
|
|
(currentChar == '.' && bNextCharDigit)) ||
|
|
currentChar == 'e' || currentChar == 'E')
|
|
{
|
|
// handle float
|
|
bConstantOverflow = false;
|
|
m_sourceOffset = sourceStart;
|
|
if (GetFloat(pSource, m_sourceOffset, m_value))
|
|
{
|
|
m_sourceOffset--; // back up to point at last digit
|
|
m_type = type_con_float;
|
|
}
|
|
else
|
|
{
|
|
error = error_fpcmbw;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// done with this constant
|
|
m_sourceOffset--; // back up to point at last digit
|
|
m_type = type_con;
|
|
}
|
|
constantBase = 0;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// multiply accumulate the constant
|
|
unsigned int oldValue = m_value;
|
|
m_value *= constantBase;
|
|
|
|
// check for overflow
|
|
if (((unsigned int)m_value / constantBase) != oldValue)
|
|
{
|
|
bConstantOverflow = true;
|
|
}
|
|
|
|
m_value += digitValue;
|
|
}
|
|
continue;
|
|
}
|
|
else if (m_sourceFlags != 0)
|
|
{
|
|
// old string? (continue parsing a string)
|
|
|
|
// for strings, m_sourceFlags will start out 0, and then cycle between 1 and 2 for
|
|
// each character of the string, when it is 1, a type_comma is returned, when it is
|
|
// 2 the next character is returned
|
|
|
|
// return a comma element between each character of the string
|
|
if (m_sourceFlags == 1)
|
|
{
|
|
m_sourceFlags++;
|
|
m_sourceOffset--;
|
|
m_type = type_comma;
|
|
break;
|
|
}
|
|
|
|
// reset flag
|
|
m_sourceFlags = 0;
|
|
|
|
// check for errors
|
|
if (currentChar == '\"')
|
|
{
|
|
error = error_es;
|
|
break;
|
|
}
|
|
else if (currentChar == 0)
|
|
{
|
|
m_sourceOffset--; // back up from eof
|
|
error = error_eatq;
|
|
break;
|
|
}
|
|
else if (currentChar == 13)
|
|
{
|
|
error = error_eatq;
|
|
break;
|
|
}
|
|
|
|
// return the character
|
|
m_value = currentChar;
|
|
|
|
// check the next character, if it's not a " then setup so the next
|
|
// call returns a type_comma, if it is a ", then we are done with this string
|
|
// and we leave the offset pointing after the "
|
|
currentChar = pSource[m_sourceOffset++];
|
|
if (currentChar != '\"')
|
|
{
|
|
m_sourceOffset--;
|
|
m_sourceFlags++;
|
|
}
|
|
|
|
// return the character constant
|
|
m_type = type_con;
|
|
break;
|
|
}
|
|
else if (currentChar == '\"')
|
|
{
|
|
// new string (start parsing a string)
|
|
|
|
// we got here because m_sourceFlags was 0 and the character is a "
|
|
|
|
// get first character of string
|
|
currentChar = pSource[m_sourceOffset++];
|
|
|
|
// check for errors
|
|
if (currentChar == '\"')
|
|
{
|
|
error = error_es;
|
|
break;
|
|
}
|
|
else if (currentChar == 0)
|
|
{
|
|
m_sourceOffset--; // back up from eof
|
|
error = error_eatq;
|
|
break;
|
|
}
|
|
else if (currentChar == 13)
|
|
{
|
|
error = error_eatq;
|
|
break;
|
|
}
|
|
|
|
// return the character in value
|
|
m_value = currentChar & 0x000000FF;
|
|
|
|
// check the next character, it's it's not a " then setup so the next
|
|
// call returns a type_comma, if it is a " then it means it's a one character
|
|
// string and we leave the offset pointing after the "
|
|
currentChar = pSource[m_sourceOffset++];
|
|
if (currentChar != '\"')
|
|
{
|
|
m_sourceOffset--; // back up, so this character will be read after the type_comma
|
|
m_sourceFlags = 1; // cause the next call to return a type_comma
|
|
}
|
|
|
|
// return the character constant
|
|
m_type = type_con;
|
|
break;
|
|
}
|
|
else if (currentChar == 0)
|
|
{
|
|
// eof
|
|
m_type = type_end;
|
|
bEof = true;
|
|
m_sourceOffset--;
|
|
sourceStart = m_sourceOffset;
|
|
break;
|
|
}
|
|
else if (currentChar == 13)
|
|
{
|
|
// eol
|
|
m_type = type_end;
|
|
break;
|
|
}
|
|
else if (currentChar <= ' ')
|
|
{
|
|
// space or tab?
|
|
sourceStart = m_sourceOffset;
|
|
continue;
|
|
}
|
|
else if (currentChar == '\'')
|
|
{
|
|
// comment
|
|
// read until end of line or file, handle doc comment
|
|
if (pSource[m_sourceOffset] == '\'')
|
|
{
|
|
m_sourceOffset++; // skip over second '
|
|
bDocComment = true;
|
|
g_pCompilerData->doc_flag = true;
|
|
}
|
|
for (;;)
|
|
{
|
|
currentChar = pSource[m_sourceOffset++];
|
|
if (currentChar == 0)
|
|
{
|
|
m_sourceOffset--; // back up from eof
|
|
m_type = type_end;
|
|
bEof = true;
|
|
break;
|
|
}
|
|
if (bDocComment)
|
|
{
|
|
DocPrint(currentChar);
|
|
}
|
|
if (currentChar == 13)
|
|
{
|
|
m_type = type_end;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
else if (currentChar == '{')
|
|
{
|
|
// brace comment
|
|
// read the whole comment, handling doc comments as needed
|
|
int braceCommentLevel = 1;
|
|
if (pSource[m_sourceOffset] == '{')
|
|
{
|
|
m_sourceOffset++; // skip over second {
|
|
bDocComment = true;
|
|
g_pCompilerData->doc_flag = true;
|
|
if (pSource[m_sourceOffset] == 13)
|
|
{
|
|
m_sourceOffset++; // skip over end if present
|
|
}
|
|
}
|
|
for (;;)
|
|
{
|
|
currentChar = pSource[m_sourceOffset++];
|
|
if (currentChar == 0)
|
|
{
|
|
if (bDocComment)
|
|
{
|
|
error = error_erbb;
|
|
}
|
|
else
|
|
{
|
|
error = error_erb;
|
|
}
|
|
m_sourceOffset--; // back up from eof
|
|
sourceStart = m_sourceOffset;
|
|
break;
|
|
}
|
|
else if (!bDocComment && currentChar == '{')
|
|
{
|
|
braceCommentLevel++;
|
|
}
|
|
else if (currentChar == '}')
|
|
{
|
|
if (bDocComment && pSource[m_sourceOffset] == '}')
|
|
{
|
|
m_sourceOffset++; // skip over second }
|
|
break;
|
|
}
|
|
else if (!bDocComment)
|
|
{
|
|
braceCommentLevel--;
|
|
if (braceCommentLevel < 1)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (bDocComment)
|
|
{
|
|
DocPrint(currentChar);
|
|
}
|
|
}
|
|
if (error == error_none)
|
|
{
|
|
sourceStart = m_sourceOffset;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else if (currentChar == '}')
|
|
{
|
|
// unmatched brace comment end
|
|
error = error_bmbpbb;
|
|
break;
|
|
}
|
|
else if (currentChar == '%')
|
|
{
|
|
// binary
|
|
currentChar = pSource[m_sourceOffset++];
|
|
char temp;
|
|
if (currentChar == '%')
|
|
{
|
|
// double binary
|
|
currentChar = pSource[m_sourceOffset++];
|
|
if (!CheckDigit(currentChar, temp, 4))
|
|
{
|
|
error = error_idbn;
|
|
break;
|
|
}
|
|
constantBase = 4;
|
|
}
|
|
else
|
|
{
|
|
if (!CheckDigit(currentChar, temp, 2))
|
|
{
|
|
error = error_idbn;
|
|
break;
|
|
}
|
|
constantBase = 2;
|
|
}
|
|
m_sourceOffset--; // back up to first digit
|
|
// constantBase is now set, so loop back around to read in the constant
|
|
continue;
|
|
}
|
|
else if (currentChar == '$')
|
|
{
|
|
// hex
|
|
currentChar = pSource[m_sourceOffset++];
|
|
char temp;
|
|
if (!CheckDigit(currentChar, temp, 16))
|
|
{
|
|
m_sourceOffset--;
|
|
m_type = type_asm_org;
|
|
break;
|
|
}
|
|
constantBase = 16;
|
|
m_sourceOffset--; // back up to first digit
|
|
// constantBase is now set, so loop back around to read in the constant
|
|
continue;
|
|
}
|
|
else if (currentChar >= '0' && currentChar <= '9')
|
|
{
|
|
// dec
|
|
constantBase = 10;
|
|
m_sourceOffset--; // back up to first digit
|
|
// constantBase is now set, so loop back around to read in the constant
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// symbol
|
|
currentChar = Uppercase(currentChar);
|
|
if (CheckWordChar(currentChar))
|
|
{
|
|
// do word symbol
|
|
while(CheckWordChar(currentChar) && symbolOffset <= symbol_limit)
|
|
{
|
|
m_currentSymbol[symbolOffset++] = currentChar;
|
|
currentChar = Uppercase(pSource[m_sourceOffset++]);
|
|
}
|
|
if (symbolOffset > symbol_limit)
|
|
{
|
|
error = error_sexc;
|
|
}
|
|
else
|
|
{
|
|
// back up so we point at last char of symbol
|
|
m_sourceOffset--;
|
|
// terminate symbol
|
|
m_currentSymbol[symbolOffset] = 0;
|
|
m_pSymbolEntry = m_pSymbolEngine->FindSymbol(m_currentSymbol);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// try non-word symbol (one or two char operators)
|
|
m_currentSymbol[symbolOffset++] = currentChar;
|
|
currentChar = pSource[m_sourceOffset++];
|
|
|
|
bool bDoOneChar = false;
|
|
bool bDoTwoChar = false;
|
|
|
|
// if the next char is not whitespace or eol
|
|
if (currentChar > ' ')
|
|
{
|
|
// three char symbol
|
|
|
|
// assign second char into symbol
|
|
m_currentSymbol[symbolOffset++] = currentChar;
|
|
|
|
// read third char into symbol
|
|
m_currentSymbol[symbolOffset++] = pSource[m_sourceOffset++];
|
|
|
|
// terminate symbol
|
|
m_currentSymbol[symbolOffset] = 0;
|
|
|
|
m_pSymbolEntry = m_pSymbolEngine->FindSymbol(m_currentSymbol);
|
|
if (m_pSymbolEntry == 0)
|
|
{
|
|
bDoTwoChar = true;
|
|
symbolOffset--;
|
|
}
|
|
}
|
|
|
|
if (bDoTwoChar)
|
|
{
|
|
// two char symbol
|
|
|
|
// back up so we point at last char of symbol
|
|
m_sourceOffset--;
|
|
|
|
// terminate symbol
|
|
m_currentSymbol[symbolOffset] = 0;
|
|
|
|
m_pSymbolEntry = m_pSymbolEngine->FindSymbol(m_currentSymbol);
|
|
if (m_pSymbolEntry == 0)
|
|
{
|
|
bDoOneChar = true;
|
|
symbolOffset--;
|
|
}
|
|
}
|
|
|
|
if (bDoOneChar || currentChar <= ' ')
|
|
{
|
|
// one char symbol
|
|
|
|
// back up so we point at last char of symbol
|
|
m_sourceOffset--;
|
|
|
|
// terminate symbol
|
|
m_currentSymbol[symbolOffset] = 0;
|
|
|
|
m_pSymbolEntry = m_pSymbolEngine->FindSymbol(m_currentSymbol);
|
|
if (m_pSymbolEntry == 0)
|
|
{
|
|
error = error_uc;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bConstantOverflow)
|
|
{
|
|
error = error_ce32b;
|
|
}
|
|
|
|
// update pointers
|
|
m_pCompilerData->source_start = sourceStart;
|
|
m_pCompilerData->source_finish = m_sourceOffset;
|
|
|
|
// if we got a symbol, then set the type, value, etc.
|
|
if (m_type == 0 && m_pSymbolEntry)
|
|
{
|
|
SetFromSymbolEntry();
|
|
}
|
|
|
|
if (error != error_none)
|
|
{
|
|
m_pCompilerData->error = true;
|
|
m_pCompilerData->error_msg = g_pErrorStrings[error];
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// if the next element is type, then return true, else false, retains value
|
|
bool Elementizer::GetElement(int type)
|
|
{
|
|
int value = m_value; // save current value
|
|
|
|
bool bEof = false;
|
|
GetNext(bEof);
|
|
|
|
if (GetType() != type)
|
|
{
|
|
m_pCompilerData->error = true;
|
|
int errorNum = 0;
|
|
switch (type)
|
|
{
|
|
case type_left: errorNum = error_eleft; break;
|
|
case type_right: errorNum = error_eright; break;
|
|
case type_rightb: errorNum = error_erightb; break;
|
|
case type_comma: errorNum = error_ecomma; break;
|
|
case type_pound: errorNum = error_epound; break;
|
|
case type_colon: errorNum = error_ecolon; break;
|
|
case type_dot: errorNum = error_edot; break;
|
|
case type_sub: errorNum = error_easn; break;
|
|
case type_end: errorNum = error_eeol; break;
|
|
}
|
|
m_pCompilerData->error_msg = g_pErrorStrings[errorNum];
|
|
return false;
|
|
}
|
|
|
|
m_value = value; // restore saved value
|
|
|
|
return true;
|
|
}
|
|
|
|
// check if next element is of the given type, if so return true, if not, backup and return false
|
|
bool Elementizer::CheckElement(int type)
|
|
{
|
|
bool bEof = false;
|
|
GetNext(bEof);
|
|
if (GetType() == type)
|
|
{
|
|
return true;
|
|
}
|
|
Backup();
|
|
return false;
|
|
}
|
|
|
|
// scan for the next block element of type, returns true if no error, , bEof will be set to true if eof is hit
|
|
bool Elementizer::GetNextBlock(int type, bool& bEof)
|
|
{
|
|
bool bFound = false;
|
|
while(bFound == false)
|
|
{
|
|
if (GetNext(bEof) == false || bEof == true)
|
|
{
|
|
break;
|
|
}
|
|
if (GetType() == type_block && GetValue() == type)
|
|
{
|
|
if (GetColumn() != 1)
|
|
{
|
|
m_pCompilerData->error = true;
|
|
m_pCompilerData->error_msg = g_pErrorStrings[error_bdmbifc];
|
|
return false;
|
|
}
|
|
bFound = true;
|
|
}
|
|
}
|
|
// if we found the block or we hit eof, then we got no error so return true
|
|
return (bFound || bEof);
|
|
}
|
|
|
|
// returns column of most recent Element gotten
|
|
int Elementizer::GetColumn()
|
|
{
|
|
char* pSource = m_pCompilerData->source;
|
|
int sourceStart = m_pCompilerData->source_start;
|
|
if (sourceStart == 0)
|
|
{
|
|
// we are at the start of the source, so return 1
|
|
return 1;
|
|
}
|
|
|
|
// back up until we hit a CR character
|
|
while(pSource[sourceStart] != 13 && sourceStart > 0)
|
|
{
|
|
sourceStart--;
|
|
}
|
|
|
|
// advance forward one, (off of the CR)
|
|
sourceStart++;
|
|
|
|
if (sourceStart == m_pCompilerData->source_start)
|
|
{
|
|
// we are at the start of the line, so return 1
|
|
return 1;
|
|
}
|
|
|
|
// adjust source pointer to start of line
|
|
pSource += sourceStart;
|
|
// adjust sourceStart such that it is how many characters we backed up
|
|
sourceStart = m_pCompilerData->source_start - sourceStart;
|
|
|
|
// count the characters we backed up over, accounting for tabs (tabs are 8 chars)
|
|
int column = 0;
|
|
for (int i = 0; i < sourceStart; i++)
|
|
{
|
|
if (pSource[i] == 9)
|
|
{
|
|
column |= 7;
|
|
}
|
|
column++;
|
|
}
|
|
|
|
return column + 1;
|
|
}
|
|
|
|
int Elementizer::GetCurrentLineNumber(int &offsetToStartOfLine, int& offsetToEndOfLine)
|
|
{
|
|
int lineCount = 1;
|
|
|
|
char* pSource = m_pCompilerData->source;
|
|
int scanEnd = m_pCompilerData->source_start;
|
|
offsetToStartOfLine = -1;
|
|
while (scanEnd > 0)
|
|
{
|
|
if (pSource[--scanEnd] == 13)
|
|
{
|
|
if (offsetToStartOfLine == -1)
|
|
{
|
|
offsetToStartOfLine = scanEnd+1;
|
|
}
|
|
lineCount++;
|
|
}
|
|
}
|
|
if (offsetToStartOfLine == -1)
|
|
{
|
|
offsetToStartOfLine = 0;
|
|
}
|
|
scanEnd = m_pCompilerData->source_start;
|
|
while (pSource[scanEnd] != 0)
|
|
{
|
|
if (pSource[scanEnd] == 13 || pSource[scanEnd] == 0)
|
|
{
|
|
break;
|
|
}
|
|
scanEnd++;
|
|
}
|
|
offsetToEndOfLine = scanEnd;
|
|
|
|
return lineCount;
|
|
}
|
|
|
|
// backup to the previous element
|
|
void Elementizer::Backup()
|
|
{
|
|
m_backIndex--;
|
|
m_sourceOffset = m_backOffsets[m_backIndex&0x03];
|
|
m_sourceFlags = m_backFlags[m_backIndex&0x03];
|
|
}
|
|
|
|
void Elementizer::ObjConToCon()
|
|
{
|
|
m_type -= (type_objcon - type_con);
|
|
}
|
|
|
|
void Elementizer::DatResToLong()
|
|
{
|
|
if (m_type == type_dat_long_res)
|
|
{
|
|
m_type = type_dat_long;
|
|
}
|
|
}
|
|
|
|
bool Elementizer::SubToNeg()
|
|
{
|
|
if (m_type == type_binary && m_opType == op_sub)
|
|
{
|
|
m_type = type_unary;
|
|
m_opType = op_neg;
|
|
m_value = 0;
|
|
m_value_2 = 0;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Elementizer::NegConToCon()
|
|
{
|
|
if (m_type == type_binary && m_opType == op_sub)
|
|
{
|
|
int savedValue = m_value;
|
|
bool bEof = false;
|
|
if (!GetNext(bEof))
|
|
{
|
|
return false;
|
|
}
|
|
if (m_type == type_con)
|
|
{
|
|
m_value = -m_value;
|
|
}
|
|
else if (m_type == type_con_float)
|
|
{
|
|
m_value |= 0x80000000;
|
|
}
|
|
else
|
|
{
|
|
Backup();
|
|
m_type = type_binary;
|
|
m_asm = -1;
|
|
m_opType = op_sub;
|
|
m_value = savedValue;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Elementizer::FindSymbol(const char* symbol)
|
|
{
|
|
m_pSymbolEntry = m_pSymbolEngine->FindSymbol(symbol);
|
|
SetFromSymbolEntry();
|
|
return true;
|
|
}
|
|
|
|
void Elementizer::BackupSymbol()
|
|
{
|
|
strcpy(m_pCompilerData->symbolBackup, m_currentSymbol);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
// 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. //
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
|