C++ For Beginners


monstercameron said:
ctime..............for the current time?
<ctime> is just the standard C++ library's version of the standard C library's <time.h> (same goes for <cmath>, <cassert> etc). If you want to find out what each include does,
just comment it out and let the compiler tell you what is missing afterwards :)

monstercameron said:
const string& url..?
32-bit address.....?
The address size depends on the platform you are compiling for - these days it is either 32 or 64 bit. It is a good practice
to not make any assumptions on the bit size of a type, unless it is actually part of the type's specification (such as SDL's Uint32 for example).

monstercameron said:
#ifndef RUNTIME_H_INCLUDED
#define RUNTIME_H_INCLUDED
...
#endif
was there when i created an header file with codeblocks
This construct is called a "guard" and is a good practice to do in header files. It prevents the declarations in the header file from being duplicated if the header file is
included several times in one "compilation unit" (ie a .cpp file and all .h files it includes recursively).

monstercameron said:
a. so you said its better to declare variables in teh header files?
b. and is it also better to add all functions in header also...or have each functions have its won header file?

What you generally want to do is put only declarations in your header files, and put the definitions in a source file:

runtime.h
Code:
#ifndef RUNTIME_H_INCLUDED
#define RUNTIME_H_INCLUDED

#include <fstream>
#include <string>
using namespace std;

// (Actually only variables that need to be accessed from outside runtime.cpp should be put here)
extern int running;
extern string motd;
extern string CUSinput;
extern string help;
extern string version;
extern string currentkey;
extern string statement;
extern string url;
extern ofstream myfile;
extern string opentextnow;
extern string MYbook;
extern string whatnext;

int callouts();
int runmetest2();
int readmetest();

#endif // RUNTIME_H_INCLUDED

runtime.cpp
Code:
#include "runtime.h"

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <ctime>
#include <fstream>
using namespace std;

int running = 1;
string motd = "Welcome To My Language Interpreter...English!";
string CUSinput;
string help = "not yrt integrated";
string version = "0.0.1";
string currentkey;
string statement;
string url;
ofstream myfile;
string opentextnow;
string MYbook;
string whatnext = "yes";

int callouts()
{
    if (CUSinput == "motd")
        cout << endl << motd;
    if (CUSinput == "version")
        cout << endl << version;
    /*
    if (CUSinput == "time")
        cout << endl << "the time is now:  " << ctime;
    */
    if (CUSinput == "help")
        cout << endl << help;
    system("clear");
    return 0;
}

/*
int opentext(string url)
{
    myfile.open (url.c_str());
    cout << "   opening...";
    myfile.close();
    return 0;
}
*/
/*
int runmetest()
{
    if (CUSinput == "statement")
    {
        cout << "tell me what to save:  ";
        cin >> statement;
        //ofstream myfile;
        //myfile.open ("book.eng");
        cout << statement << "   saving...";
        myfile << statement;
        myfile.close();
    }
    return 0;
}
*/

int runmetest2()
{
    if (CUSinput == "statement")
    {
        cout << "tell me what to open:  ";
        cin >> url;
        cout << "tell me what to save:  ";
        cin >> statement;
        ofstream myfile;
        myfile.open (url.c_str());
        cout << "   opening...";
        cout << statement << "   saving...";
        myfile << statement;
        myfile.close();
    }
    return 0;
}

int readmetest()
{
    if (CUSinput == "open")
    {
        cout << "tell me what to open:  ";
        cin >> url;
        ifstream myfileread;
        myfileread.open (url.c_str());
        cout << "   opening...";
        while(!myfileread.eof())
            {
                getline (myfileread,MYbook);
                while(whatnext == "yes")
                    {
                        cout << MYbook << endl;
                        cin >> whatnext;
                    }
                    whatnext = "yes";
            }
        myfileread.close();
        }
    return 0;
}

You may think of the "#include" directive as "copy and paste", so
Code:
// runtime.h
int my_function()
{
   return 123;
}

//main.cpp:
#include "runtime.h"
[...]

//other.cpp:
#include "runtime.h"
[...]

... is the same as:
Code:
//main.cpp:
int my_function()
{
   return 123;
}
[...]

//other.cpp:
int my_function()
{
   return 123;
}
[...]

You can compile both "main.cpp" and "other.cpp", but when you link both "main.o" and "other.o" into your program, you will in the best case get a linker error, because there is a name collision. In the worst case, you will not
get a linker error but strange crashes that are rather difficult to debug.

If you split the code into "runtime.h" and "runtime.cpp", the physical code produced from the definitions will only exist once after compilation - in "runtime.o". The remaining declarations in "runtime.h" are pure information, so there is no physical representation of it that could be duplicated in the compiled program.
 
Last edited by a moderator:
monstercameron said:
eventually i want to parse this text!

Code:
start.
say "hello world".
wait for keypress(return).
the end.

This is a rather ambitious project for a beginner. Your're trying to write an interpreter for a new language while still struggling with the language you're writing it in...

If you want to do such a thing right, you should learn about some computer science basics first, such as state machines and syntax trees. Then you need to build enough experience in C++ to translate these theoretical concepts into actual code... and this only covers the parsing part. After that comes part where you execute the code you just parsed, which is another science in itself...

Having said all that, that stuff can be simplified if your language and execution model is simple enough. Consider this simplified version of your example:

Code:
start .
say "hello world" .
wait_for keypress return .
end .

Let's analyze the structure of this simplified language.
  • the program is a list of statements
  • a statement is a keyword ("start", "say", "wait_for", "end"), followed by zero or more arguments
  • a statement is terminated by a dot
  • all arguments are constants (not expressions)
  • there are no variables/loops/functions calls
  • there is no syntax tree - only a "flat" list of statements
  • the execution model can be simple batch processing, no jumps, loops, concurrency etc.

The parser could then work like this:
  • You read one character at a time from a file or a string
  • You try to find "tokens" - i.e. blocks of characters that contain no spaces (such as "wait_for"), or blocks of characters that are enclosed in quotation marks (such as "hello world").
  • You collect these tokens in a list
  • When you found a token ".", you know that you have read a complete statement.
  • For example if you finished the second line, your have a list of tokens that looks like this: [ "say", "hello world" ]
  • You look at the first element of the list. If the list is empty, or if the first elemenent is not one of the known keywords, you found a syntax error.
  • Now for every known keyword, you can check the remaining elements of the list - for example, the "say" keyword needs exactly one additional element
  • If the statement checks out ok, you append it to a list of statements and try to parse the next statement
  • After all statements have been read, the list of statements you built is the program's representation in memory

Finally, in an interpreter loop, you iterate over the list of statements you got from the parser.

Here is an example implementation of this concept:
Code:
#include <fstream>
#include <iostream>
#include <map>
#include <string>
#include <vector>

#include <stdlib.h>

// A utility routine to show the program as it was parsed into memory.
// The program is represented in memory as a "list of a list of strings"
// (each statement is a list of strings and the program is a list of statements).
static void dumpProgram( const std::vector< std::vector< std::string > >& program )
{
  for( unsigned i = 0; i < program.size(); ++i ) {

    std::cout << "Statement " << i << std::endl;
    for( unsigned j = 0; j < program[i].size(); ++j ) {

      std::cout << "  <" << program[i][j] << ">" << std::endl;
    }
  }
}

int main( int argc, char** argv )
{
  // Possible states of the tokenizer state machine
  // The tokenizer (also called lexer or scanner)
  // detects "tokens" (words) in an input text.
  // The parser then takes these tokens and tries to 
  // build "statements" out of them.
  // In this simple example, the parser logic is embedded
  // in the tokenizer.
  enum TokenizerState {

    StateFindToken,
    StateInIdentifier,
    StateInString,
  };

  TokenizerState tokenizerState = StateFindToken;

  // A dictionary of known keywords.
  // Holds the number of arguments required for each keyword.
  std::map< std::string, int > keywordArgCount;
  keywordArgCount[ "start" ] = 0;
  keywordArgCount[ "end" ] = 0;
  keywordArgCount[ "say" ] = 1;
  keywordArgCount[ "wait_for" ] = 2;

  // Utility variables used during parsing
  std::string currentToken;
  std::vector< std::string > currentStatement;
  
  // This is where the program will be stored
  std::vector< std::vector< std::string > > program;

  // Load the program from a file
  std::ifstream file;
  file.open( "program.txt" );
  while( !file.eof() ) {

    // Iterate through every character
    char c;
    file.get( c );

    // Trace the tokenizer state...
    std::cout << "State " << tokenizerState << ": '" << c << "'" << std::endl;
   
    // State machine
    switch( tokenizerState ) {

    case StateFindToken:
           
      // In this state we try to find the next token.
      // A token is either a string literal, which is a sequence 
      // of characters enclosed by quotation marks,
      // or an "identifier", which is a sequence of non-whitespace characters,
      // or a dot.

      if( c == ' ' || c == '\t' || c == '\r' || c == '\n' ) {
	
        // Skip whitespace characters (ie do nothing)...
      }
      else if( c == '"' ) {

        // Start of string literal, switch to state that reads in string...
	tokenizerState = StateInString;
      }
      else if( c == '.' ) {

        // The dot indicates the end of a statement. Lets check if a complete statement was read...
	if( currentStatement.size() == 0 ) {

	  std::cout << "Error: Empty statement!" << std::endl;
	  exit( 1 );
	}

        // Lets see if the statement starts with a known keyword...
	std::map< std::string, int >::const_iterator it = keywordArgCount.find( currentStatement[0] );
	if( it == keywordArgCount.end() ) {

	  std::cout << "Error: Unknown keyword:" << currentStatement[0] << std::endl;
	  exit( 1 );
	}
	  
        // Lets see if the statement has the correct number of arguments for the keyword...
	if( ( (*it).second + 1 ) != (int) currentStatement.size() ) {

	  std::cout << "Error: Invalid argument count " << currentStatement.size() << " for keyword " << currentStatement[0] << std::endl;
	  exit( 1 );
	}

        // The statement is valid, append it to the program
	program.push_back( currentStatement );

        // Cleanup and start searching for the next token...
	currentStatement = std::vector< std::string >();
	tokenizerState = StateFindToken;
      }
      else {

        // Found the beginning of an "identifier". Store it and 
        // switch to the state that reads in the rest of the identifier
	currentToken += c;
	tokenizerState = StateInIdentifier;
      }
      break;

    case StateInIdentifier:
      {
	// In this state we read in the rest of the identifier

	if( c == ' ' || c == '\t' || c == '\r' || c == '\n' ) {

          // The identifier has ended, append it to the current statement
	  currentStatement.push_back( currentToken );

          // Clean up and try to find the next token...
	  currentToken = "";
	  tokenizerState = StateFindToken;
	}
	else {

          // Still in identifier, keep reading it in...
	  currentToken += c;
	}
      }
      break;

    case StateInString:
      {
	// In this state we read in a string literal

	if( c == '"' ) {

          // String literal has ended, append it to the current statement
	  currentStatement.push_back( currentToken );

          // Clean up and try to find the next token 
	  currentToken = "";
	  tokenizerState = StateFindToken;
	}
	else {

          // Still in string, keep reading it in...
	  currentToken += c;
	}
      }
    }
  }

  // At this point we are at the end of the file.
  // Lets see if the parser was left in a correct state:
  // The state must be "FindToken", otherwise it would mean that 
  // the file ended while the parser was still doing something.
  // Also the current statement must be empty, because otherwise
  // it would mean that the last statement was not complete 
  // when the file ended (as it is cleared when a statement is complete).
  if( ( tokenizerState != StateFindToken ) ||
      ( currentStatement.size() != 0 ) ) {

    std::cout << "Error: Unfinished statement" << std::endl;
    exit( 1 );
  }

  // Now lets do some sanity checks on the parsed program:
  // We need at least two statements ("start"/"end"), 
  // also the first statement needs to be "start" and the 
  // last statement needs to be "end".
  if( ( program.size() < 2 ) ||
      ( program.front()[0] != "start" ) ||
      ( program.back()[0] != "end" ) ) {
    
    std::cout << "Error: Invalid start/end statements" << std::endl;
    exit( 1 );
  }

  std::cout << "Parsing complete" << std::endl;

  // Show parsed program     
  dumpProgram( program );

 
  std::cout << "Running program:" << std::endl;
 
  // Since we have no real runtime with input checking
  // in this example, we use this to simulate the return key 
  // being pressed... 
  int fakeReturnKeyCount = 0;

  // Index of the statement the interpreter is at
  int programCounter = 0;
 
  // Interpreter loop...
  while( true ) {

    // Load the current statement
    const std::vector< std::string >& statement = program[ programCounter ];
    const std::string& keyword = statement[0];

    // Execute the keyword...
    if( keyword == "start" ) {

      // Nothing to be done, just go to the next statement...
      programCounter++;
    }
    else if( keyword == "end" ) {

      // End of program, exit the while-loop...
      break;
    }
    else if( keyword == "say" ) {

      // Print the argument, go to next statement...
      std::cout << statement[1] << std::endl;
      programCounter++;
    }
    else if( keyword == "wait_for" ) {

      if( statement[1] == "keypress" ) {

	if( statement[2] == "return" ) {
	  
          // Here we fake the return key...
	  fakeReturnKeyCount++;
	  if( fakeReturnKeyCount >= 10 ) {
	    
	    fakeReturnKeyCount = 0;

            // The return key was pressed, continue with the next statement 
	    programCounter++;
	    continue;
	  }
	}
      }

      // No event was detected, do nothing (ie do NOT go the next statement)      
      std::cout << "(waiting...)" << std::endl;
    }
    else {
      
      std::cout << "Error: Unknown keyword " << keyword << std::endl;
      exit( 1 );
    }
  }

  std::cout << "Done" << std::endl;

  return 0;
}

Edit: Cleaned up the example and added comments.
 
Last edited by a moderator:
thanks for the comments but your answers have made me ask more:
1. whats life without ambition
2. an interpreter is pretty intense!
3. thanks for the advice!
4. what should i do to rapidly gain knowledge?
5. computer science is a couple courses away
6. anyone working on a project and dont mind bringing a noob along for the ride?
7. any noobs wanna band together to make a project?
8. whats the *.o file for?
9. how do i compile my sdl project to work on caanoo?(i have dl the sdk and set it up but the gp2x config wizard wont work in code blocks, what do i put in the .ini file, what do i put in the .gpe file, where does executable go?)
10. as always useful links are good(yes i know how to use google)
 
monstercameron said:
4. what should i do to rapidly gain knowledge?
Study. Study tutorials, study books, study source code. Unterstand the material. If you don't - ask questions.
But try to figure it out by yourself or from your reference material first - after all, programming is about finding
and implementing solutions to "problems".

Also be patient. Depending on where you want to end up, you'll need to spend weeks, months or years learning
this stuff.

monstercameron said:
8. whats the *.o file for?
They contain the binary code compiled from a .cpp file. Every .cpp file in your project gets compiled individually. The
result is an .o file. Finally the linker takes all .o files and puts them together as the executable file.
 
Last edited by a moderator:
Back
Top