/*************************************************************************** * Copyright (C) 2004-2005 by Daniel Clarke * * daniel.jc@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "btreebase.h" #include "expression.h" #include "instruction.h" #include "parser.h" #include "pic14.h" #include "traverser.h" #include #include #include #include #include #include #include using namespace std; //BEGIN class Parser Parser::Parser( Microbe * _mb ) { m_code = 0; m_pPic = 0; mb = _mb; // Set up statement definitions. StatementDefinition definition; definition.append( Field(Field::Label, "label") ); m_definitionMap["goto"] = definition; definition.clear(); definition.append( Field(Field::Label, "label") ); m_definitionMap["call"] = definition; definition.clear(); definition.append( Field(Field::Expression, "expression") ); definition.append( Field(Field::Code, "code") ); m_definitionMap["while"] = definition; definition.clear(); m_definitionMap["end"] = definition; definition.clear(); definition.append( Field(Field::Label, "label") ); definition.append( Field(Field::Code, "code") ); // For backwards compataibility m_definitionMap["sub"] = definition; m_definitionMap["subroutine"] = definition; definition.clear(); definition.append( Field(Field::Label, "label") ); definition.append( Field(Field::Code, "code") ); m_definitionMap["interrupt"] = definition; definition.clear(); definition.append( Field(Field::Label, "alias") ); definition.append( Field(Field::Label, "dest") ); m_definitionMap["alias"] = definition; definition.clear(); definition.append( Field(Field::Expression, "expression") ); definition.append( Field(Field::FixedString, 0, "then", true) ); definition.append( Field(Field::Code, "ifCode") ); definition.append( Field(Field::Newline) ); definition.append( Field(Field::FixedString, 0, "else", false) ); definition.append( Field(Field::Code, "elseCode") ); m_definitionMap["if"] = definition; definition.clear(); definition.append( Field(Field::Expression, "initExpression") ); definition.append( Field(Field::FixedString, 0, "to", true) ); definition.append( Field(Field::Expression, "toExpression") ); definition.append( Field(Field::FixedString, 0, "step", false) ); definition.append( Field(Field::Expression, "stepExpression") ); definition.append( Field(Field::Code, "code") ); m_definitionMap["for"] = definition; definition.clear(); definition.append( Field(Field::Variable, "variable") ); m_definitionMap["decrement"] = definition; definition.clear(); definition.append( Field(Field::Variable, "variable") ); m_definitionMap["increment"] = definition; definition.clear(); definition.append( Field(Field::Variable, "variable") ); m_definitionMap["rotateleft"] = definition; definition.clear(); definition.append( Field(Field::Variable, "variable") ); m_definitionMap["rotateright"] = definition; definition.clear(); definition.append( Field(Field::Code, "code") ); m_definitionMap["asm"] = definition; definition.clear(); definition.append( Field(Field::Expression, "expression") ); m_definitionMap["delay"] = definition; definition.clear(); definition.append( Field(Field::Code, "code") ); definition.append( Field(Field::Newline) ); definition.append( Field(Field::FixedString, 0, "until", true) ); definition.append( Field(Field::Expression, "expression") ); m_definitionMap["repeat"] = definition; definition.clear(); definition.append( Field(Field::Name, "name") ); definition.append( Field(Field::PinList, "pinlist") ); m_definitionMap["sevenseg"] = definition; definition.clear(); definition.append( Field(Field::Name, "name") ); definition.append( Field(Field::PinList, "pinlist") ); m_definitionMap["keypad"] = definition; definition.clear(); } Parser::~Parser() { } Parser* Parser::createChildParser() { Parser * parser = new Parser( mb ); return parser; } Code * Parser::parseWithChild( const SourceLineList & lines ) { Parser * p = createChildParser(); Code * code = p->parse(lines); delete p; return code; } Code * Parser::parse( const SourceLineList & lines ) { StatementList sList; m_pPic = mb->makePic(); m_code = new Code(); m_pPic->setCode( m_code ); m_pPic->setParser(this); m_bPassedEnd = false; /* First pass ========== Here we go through the code making each line into a statement object, looking out for braced code as we go, if we find it then we put then we make attach the braced code to the statment. */ SourceLineList::const_iterator end = lines.end(); for ( SourceLineList::const_iterator slit = lines.begin(); slit != end; ++slit ) { Statement s; s.content = *slit; // Check to see if the line after next is a brace SourceLineList::const_iterator previous = slit; if ( (++slit != end) && (*slit).text() == "{" ) s.bracedCode = getBracedCode( & slit, end ); else slit = previous; if ( !s.text().isEmpty() ) sList.append(s); } mb->resetDest(); for( StatementList::Iterator sit = sList.begin(); sit != sList.end(); ++sit ) { m_currentSourceLine = (*sit).content; TQString line = (*sit).text(); TQString command; // e.g. "delay", "for", "subroutine", "increment", etc { int spacepos = line.find(' '); if ( spacepos >= 0 ) command = line.left( spacepos ); else command = line; } OutputFieldMap fieldMap; if ( (*sit).content.line() >= 0 ) m_code->append( new Instr_sourceCode( TQString("#MSRC\t%1; %2\t%3").arg( (*sit).content.line() + 1 ).arg( (*sit).content.url() ).arg( (*sit).content.text() ) )); bool showBracesInSource = (*sit).hasBracedCode(); if ( showBracesInSource ) m_code->append(new Instr_sourceCode("{")); // Use the first token in the line to look up the statement type DefinitionMap::Iterator dmit = m_definitionMap.find(command); if(dmit == m_definitionMap.end()) { if( !processAssignment( (*sit).text() ) ) { // Not an assignement, maybe a label if( (*sit).isLabel() ) { TQString label = (*sit).text().left( (*sit).text().length() - 1 ); ///TODO sanity check label name and then do error like "Bad label" m_pPic->Slabel( label ); } else mistake( Microbe::Microbe::UnknownStatement ); } continue; // Give up on the current statement } StatementDefinition definition = dmit.data(); // Start at the first white space character following the statement name int newPosition = 0; int position = command.length() + 1; // Temporaries for use inside the switch Field nextField; Statement nextStatement; bool errorInLine = false; bool finishLine = false; for( StatementDefinition::Iterator sdit = definition.begin(); sdit != definition.end(); ++sdit ) { // If there is an error, or we have finished the statement, // the stop. If we are at the end of a line in a multiline, then // break to fall through to the next line if( errorInLine || finishLine) break; Field field = (*sdit); TQString token; bool saveToken = false; bool saveBraced = false; bool saveSingleLine = false; switch(field.type()) { case (Field::Label): case (Field::Variable): case (Field::Name): { newPosition = line.find( ' ', position ); if(position == newPosition) { newPosition = -1; token = line.mid(position); } else token = line.mid(position, newPosition - position); if( token.isEmpty() ) { if(field.type() == Field::Label) mistake( Microbe::Microbe::LabelExpected ); else if (field.type() == Field::Variable) mistake( Microbe::VariableExpected ); else // field.type() == Field::Name mistake( Microbe::NameExpected ); errorInLine = true; continue; } position = newPosition; saveToken = true; break; } case (Field::Expression): { // This is slightly different, as there is nothing // in particular that delimits an expression, we just have to // look at what comes next and hope we can use that. StatementDefinition::Iterator it(sdit); ++it; if( it != definition.end() ) { nextField = (*it); if(nextField.type() == Field::FixedString) newPosition = line.find(TQRegExp("\\b" + nextField.string() + "\\b")); // Although code is not neccessarily braced, after an expression it is the only // sensilbe way to have it. else if(nextField.type() == Field::Code) { newPosition = line.find("{"); if(newPosition == -1) newPosition = line.length() + 1; } else if(nextField.type() == Field::Newline) newPosition = line.length()+1; else kdDebug() << "Bad statement definition - awkward field type after expression"; } else newPosition = line.length() + 1; if(newPosition == -1) { // Something was missing, we'll just play along for now, // the next iteration will catch whatever was supposed to be there } token = line.mid(position, newPosition - position); position = newPosition; saveToken = true; } break; case (Field::PinList): { // For now, just assume that the list of pins will continue to the end of the tokens. // (we could check until we come across a non-pin, but no command has that format at // the moment). token = line.mid( position + 1 ); position = line.length() + 1; if ( token.isEmpty() ) mistake( Microbe::PinListExpected ); else saveToken = true; break; } case (Field::Code): if ( !(*sit).hasBracedCode() ) { saveSingleLine = true; token = line.mid(position); position = line.length() + 1; } else if( position != -1 && position <= int(line.length()) ) { mistake( Microbe::UnexpectedStatementBeforeBracket ); errorInLine = true; continue; } else { // Because of the way the superstructure parsing works there is no // 'next line' as it were, the braced code is attached to the current line. saveBraced = true; } break; case (Field::FixedString): { // Is the string found, and is it starting in the right place? int stringPosition = line.find(TQRegExp("\\b"+field.string()+"\\b")); if( stringPosition != position || stringPosition == -1 ) { if( !field.compulsory() ) { position = -1; // Skip the next field ++sdit; continue; } else { // Otherwise raise an appropriate error mistake( Microbe::FixedStringExpected, field.string() ); errorInLine = true; continue; } } else { position += field.string().length() + 1; } } break; case (Field::Newline): // It looks like the best way to handle this is to just actually // look at the next line, and see if it begins with an expected fixed // string. // Assume there is a next field, it would be silly if there weren't. nextField = *(++StatementDefinition::Iterator(sdit)); if( nextField.type() == Field::FixedString ) { nextStatement = *(++StatementList::Iterator(sit)); newPosition = nextStatement.text().find(TQRegExp("\\b" + nextField.string() + "\\b")); if(newPosition != 0) { // If the next field is optional just carry on as nothing happened, // the next line will be processed as a new statement if(!nextField.compulsory()) continue; } position = 0; line = (*(++sit)).text(); m_currentSourceLine = (*sit).content; } break; case (Field::None): // Do nothing break; } if ( saveToken ) fieldMap[field.key()] = OutputField( token ); if ( saveSingleLine ) { SourceLineList list; list << SourceLine( token, 0, -1 ); fieldMap[field.key()] = OutputField( list ); } if ( saveBraced ) fieldMap[field.key()] = OutputField( (*sit).bracedCode ); // If position = -1, we have reached the end of the line. } // See if we got to the end of the line, but not all fields had been // processed. if( position != -1 && position <= int(line.length()) ) { mistake( Microbe::TooManyTokens ); errorInLine = true; } if( errorInLine ) continue; // Everything has been parsed up, so send it off for processing. processStatement( command, fieldMap ); if( showBracesInSource ) m_code->append(new Instr_sourceCode("}")); } delete m_pPic; return m_code; } bool Parser::processAssignment(const TQString &line) { TQStringList tokens = Statement::tokenise(line); // Have to have at least 3 tokens for an assignment; if ( tokens.size() < 3 ) return false; TQString firstToken = tokens[0]; firstToken = mb->alias(firstToken); // Well firstly we look to see if it is a known variable. // These can include 'special' variables such as ports // For now, the processor subclass generates ports it self // and puts them in a list for us. // Look for port variables first. if ( firstToken.contains(".") ) { PortPin portPin = m_pPic->toPortPin( firstToken ); // check port is valid if ( portPin.pin() == -1 ) mistake( Microbe::InvalidPort, firstToken ); // more error checking if ( tokens[1] != "=" ) mistake( Microbe::UnassignedPin ); TQString state = tokens[2]; if( state == "high" ) m_pPic->Ssetlh( portPin, true ); else if( state == "low" ) m_pPic->Ssetlh( portPin, false ); else mistake( Microbe::NonHighLowPinState ); } // no dots, lets try for just a port name else if( m_pPic->isValidPort( firstToken ) ) { // error checking if ( tokens[1] != "=" ) mistake( Microbe::UnassignedPort, tokens[1] ); Expression( m_pPic, mb, m_currentSourceLine, false ).compileExpression(line.mid(line.find("=")+1)); m_pPic->saveResultToVar( firstToken ); } else if ( m_pPic->isValidTris( firstToken ) ) { if( tokens[1] == "=" ) { Expression( m_pPic, mb, m_currentSourceLine, false ).compileExpression(line.mid(line.find("=")+1)); m_pPic->Stristate(firstToken); } } else { // Is there an assignment? if ( tokens[1] != "=" ) return false; if ( !mb->isValidVariableName( firstToken ) ) { mistake( Microbe::InvalidVariableName, firstToken ); return true; } // Don't care whether or not the variable is new; Microbe will only add it if it // hasn't been defined yet. mb->addVariable( Variable( Variable::charType, firstToken ) ); Expression( m_pPic, mb, m_currentSourceLine, false ).compileExpression(line.mid(line.find("=")+1)); Variable v = mb->variable( firstToken ); switch ( v.type() ) { case Variable::charType: m_pPic->saveResultToVar( v.name() ); break; case Variable::keypadType: mistake( Microbe::ReadOnlyVariable, v.name() ); break; case Variable::sevenSegmentType: m_pPic->SsevenSegment( v ); break; case Variable::invalidType: // Doesn't happen, but include this case to avoid compiler warnings break; } } return true; } SourceLineList Parser::getBracedCode( SourceLineList::const_iterator * it, SourceLineList::const_iterator end ) { // Note: The sourceline list has the braces on separate lines. // This function should only be called when the parser comes across a line that is a brace. assert( (**it).text() == "{" ); SourceLineList braced; // Jump past the first brace unsigned level = 1; ++(*it); for ( ; *it != end; ++(*it) ) { if ( (**it).text() == "{" ) level++; else if ( (**it).text() == "}" ) level--; if ( level == 0 ) return braced; braced << **it; } // TODO Error: mismatched bracing return braced; } void Parser::processStatement( const TQString & name, const OutputFieldMap & fieldMap ) { // Name is guaranteed to be something known, the calling // code has taken care of that. Also fieldMap is guaranteed to contain // all required fields. if ( name == "goto" ) m_pPic->Sgoto(fieldMap["label"].string()); else if ( name == "call" ) m_pPic->Scall(fieldMap["label"].string()); else if ( name == "while" ) m_pPic->Swhile( parseWithChild(fieldMap["code"].bracedCode() ), fieldMap["expression"].string() ); else if ( name == "repeat" ) m_pPic->Srepeat( parseWithChild(fieldMap["code"].bracedCode() ), fieldMap["expression"].string() ); else if ( name == "if" ) m_pPic->Sif( parseWithChild(fieldMap["ifCode"].bracedCode() ), parseWithChild(fieldMap["elseCode"].bracedCode() ), fieldMap["expression"].string() ); else if ( name == "sub" || name == "subroutine" ) { if(!m_bPassedEnd) { mistake( Microbe::InterruptBeforeEnd ); } else { m_pPic->Ssubroutine( fieldMap["label"].string(), parseWithChild( fieldMap["code"].bracedCode() ) ); } } else if( name == "interrupt" ) { TQString interrupt = fieldMap["label"].string(); if(!m_bPassedEnd) { mistake( Microbe::InterruptBeforeEnd ); } else if( !m_pPic->isValidInterrupt( interrupt ) ) { mistake( Microbe::InvalidInterrupt ); } else if ( mb->isInterruptUsed( interrupt ) ) { mistake( Microbe::InterruptRedefined ); } else { mb->setInterruptUsed( interrupt ); m_pPic->Sinterrupt( interrupt, parseWithChild( fieldMap["code"].bracedCode() ) ); } } else if( name == "end" ) { ///TODO handle end if we are not in the top level m_bPassedEnd = true; m_pPic->Send(); } else if( name == "for" ) { TQString step = fieldMap["stepExpression"].string(); bool stepPositive; if( fieldMap["stepExpression"].found() ) { if(step.left(1) == "+") { stepPositive = true; step = step.mid(1).stripWhiteSpace(); } else if(step.left(1) == "-") { stepPositive = false; step = step.mid(1).stripWhiteSpace(); } else stepPositive = true; } else { step = "1"; stepPositive = true; } TQString variable = fieldMap["initExpression"].string().mid(0,fieldMap["initExpression"].string().find("=")).stripWhiteSpace(); TQString endExpr = variable+ " <= " + fieldMap["toExpression"].string().stripWhiteSpace(); if( fieldMap["stepExpression"].found() ) { bool isConstant; step = processConstant(step,&isConstant); if( !isConstant ) mistake( Microbe::NonConstantStep ); } SourceLineList tempList; tempList << SourceLine( fieldMap["initExpression"].string(), 0, -1 ); m_pPic->Sfor( parseWithChild( fieldMap["code"].bracedCode() ), parseWithChild( tempList ), endExpr, variable, step, stepPositive ); } else if( name == "alias" ) { // It is important to get this the right way round! // The alias should be the key since two aliases could // point to the same name. TQString alias = fieldMap["alias"].string().stripWhiteSpace(); TQString dest = fieldMap["dest"].string().stripWhiteSpace(); // Check to see whether or not we've already aliased it... // if ( mb->alias(alias) != alias ) // mistake( Microbe::AliasRedefined ); // else mb->addAlias( alias, dest ); } else if( name == "increment" ) { TQString variableName = fieldMap["variable"].string(); if ( !mb->isVariableKnown( variableName ) ) mistake( Microbe::UnknownVariable ); else if ( !mb->variable( variableName ).isWritable() ) mistake( Microbe::ReadOnlyVariable, variableName ); else m_pPic->SincVar( variableName ); } else if( name == "decrement" ) { TQString variableName = fieldMap["variable"].string(); if ( !mb->isVariableKnown( variableName ) ) mistake( Microbe::UnknownVariable ); else if ( !mb->variable( variableName ).isWritable() ) mistake( Microbe::ReadOnlyVariable, variableName ); else m_pPic->SdecVar( variableName ); } else if( name == "rotateleft" ) { TQString variableName = fieldMap["variable"].string(); if ( !mb->isVariableKnown( variableName ) ) mistake( Microbe::UnknownVariable ); else if ( !mb->variable( variableName ).isWritable() ) mistake( Microbe::ReadOnlyVariable, variableName ); else m_pPic->SrotlVar( variableName ); } else if( name == "rotateright" ) { TQString variableName = fieldMap["variable"].string(); if ( !mb->isVariableKnown( variableName ) ) mistake( Microbe::UnknownVariable ); else if ( !mb->variable( variableName ).isWritable() ) mistake( Microbe::ReadOnlyVariable, variableName ); else m_pPic->SrotrVar( variableName ); } else if( name == "asm" ) { m_pPic->Sasm( SourceLine::toStringList( fieldMap["code"].bracedCode() ).join("\n") ); } else if( name == "delay" ) { // This is one of the rare occasions that the number will be bigger than a byte, // so suppressNumberTooBig must be used bool isConstant; TQString delay = processConstant(fieldMap["expression"].string(),&isConstant,true); if (!isConstant) mistake( Microbe::NonConstantDelay ); // else m_pPic->Sdelay( fieldMap["expression"].string(), ""); else { // TODO We should use the "delay" string returned by processConstant - not the expression (as, e.g. 2*3 won't be ok) int length_ms = literalToInt( fieldMap["expression"].string() ); if ( length_ms >= 0 ) m_pPic->Sdelay( length_ms * 1000 ); // Pause the delay length in microseconds else mistake( Microbe::NonConstantDelay ); } } else if ( name == "keypad" || name == "sevenseg" ) { TQStringList pins = TQStringList::split( ' ', fieldMap["pinlist"].string() ); TQString variableName = fieldMap["name"].string(); if ( mb->isVariableKnown( variableName ) ) { mistake( Microbe::VariableRedefined, variableName ); return; } PortPinList pinList; TQStringList::iterator end = pins.end(); for ( TQStringList::iterator it = pins.begin(); it != end; ++it ) { PortPin portPin = m_pPic->toPortPin(*it); if ( portPin.pin() == -1 ) { // Invalid port/pin //TODO mistake return; } pinList << portPin; } if ( name == "keypad" ) { Variable v( Variable::keypadType, variableName ); v.setPortPinList( pinList ); mb->addVariable( v ); } else // name == "sevenseg" { if ( pinList.size() != 7 ) mistake( Microbe::InvalidPinMapSize ); else { Variable v( Variable::sevenSegmentType, variableName ); v.setPortPinList( pinList ); mb->addVariable( v ); } } } } void Parser::mistake( Microbe::MistakeType type, const TQString & context ) { mb->compileError( type, context, m_currentSourceLine ); } // static function TQStringList Statement::tokenise(const TQString &line) { TQStringList result; TQString current; int count = 0; for(int i = 0; i < int(line.length()); i++) { TQChar nextChar = line[i]; if( nextChar.isSpace() ) { if( count > 0 ) { result.append(current); current = ""; count = 0; } } else if( nextChar == '=' ) { if( count > 0 ) result.append(current); current = ""; count = 0; result.append("="); } else if( nextChar == '{' ) { if( count > 0 ) result.append(current); current = ""; count = 0; result.append("{"); } else { count++; current.append(nextChar); } } if( count > 0 ) result.append(current); return result; } int Parser::doArithmetic(int lvalue, int rvalue, Expression::Operation op) { switch(op) { case Expression::noop: return 0; case Expression::addition: return lvalue + rvalue; case Expression::subtraction: return lvalue - rvalue; case Expression::multiplication: return lvalue * rvalue; case Expression::division: return lvalue / rvalue; case Expression::exponent: return lvalue ^ rvalue; case Expression::equals: return lvalue == rvalue; case Expression::notequals: return !(lvalue == rvalue); case Expression::bwand: return lvalue & rvalue; case Expression::bwor: return lvalue | rvalue; case Expression::bwxor: return lvalue ^ rvalue; case Expression::bwnot: return !rvalue; case Expression::le: return lvalue <= rvalue; case Expression::ge: return lvalue >= rvalue; case Expression::lt: return lvalue < rvalue; case Expression::gt: return lvalue > rvalue; case Expression::pin: case Expression::notpin: case Expression::function: case Expression::divbyzero: case Expression::read_keypad: // Not applicable actions. break; } return -1; } bool Parser::isLiteral( const TQString &text ) { bool ok; literalToInt( text, & ok ); return ok; } /* Literal's in form: -> 321890 -> 021348 -> 0x3C -> b'0100110' -> 0101001b -> h'43A' -> 2Ah Everything else is non-literal... */ int Parser::literalToInt( const TQString &literal, bool * ok ) { bool temp; if ( !ok ) ok = & temp; *ok = true; int value = -1; // Note when we use toInt, we don't have to worry about checking // that literal.mid() is convertible, as toInt returns this in ok anyway. // Try binary first, of form b'n...n' if( literal.left(2) == "b'" && literal.right(1) == "'" ) { value = literal.mid(2,literal.length() - 3).toInt(ok,2); return *ok ? value : -1; } // Then try hex of form h'n...n' if( literal.left(2) == "h'" && literal.right(1) == "'" ) { value = literal.mid(2,literal.length() - 3).toInt(ok,16); return *ok ? value : -1; } // otherwise, let TQString try and convert it // base 0 == automatic base guessing value = literal.toInt( ok, 0 ); return *ok ? value : -1; } void Parser::compileConditionalExpression( const TQString & expression, Code * ifCode, Code * elseCode ) const { ///HACK ///TODO this is a little improper, I don't think we should be using the pic that called us... Expression( m_pPic, mb, m_currentSourceLine, false ).compileConditional(expression,ifCode,elseCode); } TQString Parser::processConstant(const TQString &expression, bool * isConstant, bool suppressNumberTooBig) const { return Expression( m_pPic, mb, m_currentSourceLine, suppressNumberTooBig ).processConstant(expression, isConstant); } //END class Parser //BEGIN class Field Field::Field() { m_type = None; m_compulsory = false; } Field::Field( Type type, const TQString & key ) { m_type = type; m_compulsory = false; m_key = key; } Field::Field( Type type, const TQString & key, const TQString & string, bool compulsory ) { m_type = type; m_compulsory = compulsory; m_key = key; m_string = string; } //END class Field //BEGIN class OutputField OutputField::OutputField() { m_found = false; } OutputField::OutputField( const SourceLineList & bracedCode ) { m_bracedCode = bracedCode; m_found = true; } OutputField::OutputField( const TQString & string/*, int lineNumber*/ ) { m_string = string; m_found = true; } //END class OutputField #if 0 // Second pass else if( firstToken == "include" ) { // only cope with 'sane' strings a.t.m. // e.g. include "filename.extenstion" TQString filename = (*sit).content.mid( (*sit).content.find("\"") ).stripWhiteSpace(); // don't strip whitespace from within quotes as you // can have filenames composed entirely of spaces (kind of weird)... // remove quotes. filename = filename.mid(1); filename = filename.mid(0,filename.length()-1); TQFile includeFile(filename); if( includeFile.open(IO_ReadOnly) ) { TQTextStream stream( &includeFile ); TQStringList includeCode; while( !stream.atEnd() ) { includeCode += stream.readLine(); } ///TODO make includes work //output += parse(includeCode); includeFile.close(); } else mistake( Microbe::UnopenableInclude, filename ); } #endif