Learnings

February was my last post. Wow. Has it been that long?

I started this blog with the idea it was just for me, a way of recording for myself what I'd learned. I tried to write a post for every major milestone I made, but that became a lot of work. I've usually gone quiet on my Arduino stuff during each uni semester as I had enough work on.

This uni break I'm changing jobs and I've been reading The C++ Programming Language (Special Edition) by Bjarne Stroustrup, The Creator of C++. As my coding time has reduced my learning has increased. I'm spending more time learning the how and why of things from reading, and am surprised to find when I exercise my new knowledge in real code, it works almost immediately. The "black magic" is disappearing and I'm starting to see exactly where each bit is moving. Feels like Neo looking at the Matrix encoded and understanding it. I know there are many levels of knowledge yet to gain, but I'm loving the journey; loving it so much that I would be devastated if I thought it was possible to "know it all" some day.

I always knew Arduino was just a vehicle for me to learn C++, now I'm doing a lot of my travelling without that particular vehicle. I'm not abandoning Arduino for something else. I'm just so deep into C++ that I'm not likely to complete a "physical computing" project any time soon. Take an example. Bjarne recommends that casting, using the C method char* modifiablePointer = (char*) constPointer is dangerous. In C++ the same statement should be written char* modifiablePointer = const_cast(constPointer) but, in all the Arduino code I've read, I've never seen this syntax. Why? I've tested this format on my Arduino tonight, and its sure is supported. It also allows me to catch some simple programming mistakes at compile-time that the C method would miss.

Here's another example. I've use the String class (WString file) on my Arduino. It didn't have all the string functions I needed and I looked at extending it. But looking at it, it was so ugly I thought it prudent to start again. So I'm working on my Text class. Which, funnily enough, Bjarne suggests is a good exercise for any serious programmer.

So now I'm working down in the weeds with pointers and memory management and learning about things such as default copy constructors and assignment operators and trying to ensure every edge case is covered and every tiny detail is tested to death. I'll paste in my code as it is just for fun. So I'm learning heaps and my progress, in terms of completing a project, is even slower than before. I feel I'm closing in on an inflection point where all this knowledge will produce great power. I have already rehashed old programs and found I've halved the code. Then halved it again. Each time the program is faster and more efficient. A big deal when you have an 8-bit microcontroller with a 20MHz clock.

I'm enjoying things more and more and at this rate will start learning assembler soon. I also have a book on compiler design and would like, one day, to implement my own language from scratch. Hey, I'm doing a subject on operating systems this semester coming up. It's all happening. Is this is why working as a "programmer" is boring me to death? I certainly hope my new job has some challenge to it. At least I'll add another language to my arsenal - C#. I might buy a NetDuino to help me with my learning.

Anyway. Here's some code for my Text class. I'm trying to stay loyal to C++ and eradicate bad habits I've learned from all the C code I've seen for Arduino. As I said, fixing my casts is on the list, as is declaring and scoping variables more tightly. This code was written and tested with Code::Blocks. It may not compile for Arduino in its current state.

Text.h file:


#ifndef TEXT_H
#define TEXT_H


#include // malloc()


#if defined(AVR)
#include // Keeps Arduino compiler happy
#else
#include  // Don't use for Arduino
#include // Allows stdout for Code::Blocks
typedef uint8_t byte; // Mimic Arduino byte type
#endif


using namespace std;
const int null = 0;


class Text
{
public:
// Constructors
Text();
Text(const char& character);
Text(const char* string);
Text(const Text& text);
Text(const Text* text);
// Destructor
~Text();


// OPERATORS - Assignment
Text& operator = (const char& character);
Text& operator = (const char* string);
Text& operator = (const Text& text);
Text& operator = (const Text* text);


// OPERATORS - Compound Assignment
Text& operator += (const char& character);
Text& operator += (const char* string);
Text& operator += (const Text& text);
Text& operator += (const Text* text);


// OPERATORS - Addition
const Text operator + (const char& character);
const Text operator + (const char* string);
const Text operator + (const Text& text);
const Text operator + (const Text* text);


// OPERATORS - Comparison
bool operator == (const char& character);
bool operator == (const char* string);
bool operator == (const Text& text);
bool operator == (const Text* text);


// OPERATORS - Not Comparison
bool operator != (const char& character);
bool operator != (const char* string);
bool operator != (const Text& text);
bool operator != (const Text* text);


// OPERATORS - Stream
#if !defined(AVR) // Don't use for Arduino
friend ostream& operator << (ostream& out, Text& input);
#endif


// OPERATORS - Assignment


// Property getters and setters
const char* getHead() const;
const char* getTail() const;
const int getLength() const;
const bool isEmpty() const;
const bool isValid(const char* pointer);


// Static members
static int stringLength(const char* string);


// Temporary code
void debug();


protected:


private:
char* head; // first byte of text
char* tail; // byte after last


char* allocate(const int& bytes);
void deallocate();
void create(const char* pointer, const int length);
    void copy(char* fromStart, const char* fromEnd, char* toStart);
void concatenate(const char* pointer, const int length);
void point(char* head, const int& length);
void point(char* head, char* tail);


// work line
bool compare(const char* left, const char* right, const int& length);
//    char* create(char* source1, int& length1, char* source2, int& length2);
};


#endif // TEXT_H

Text.cpp file:

#include "Text.h"

/*
 * TO DO:
 * replace (cast) with const_cast<> etc.
 */

/* RULES:
* head/tail may refer to:
* - NULL/NULL (0/0) in which case the object is empty
* - SameValidAddress/SameValidAddress in which case the object
* is empty and indicates a place in memory such as
* the location of an empty token.
* - ValidStartAddress/ValidEndAddress: in which case the object
* points to a memory range in which character data is stored.
*/
// PUBLIC
Text::Text() {
point(null, 0);
}
Text::Text(const char& character) {
point(null, 0);
create(&character, 1);
}
Text::Text(const char* string) {
point(null, 0);
create(string, Text::stringLength(string));
}
Text::Text(const Text& text) {
point(null, 0);
create(text.getHead(), text.getLength());
}
Text::Text(const Text* text) {
point(null, 0);
create(text->getHead(), text->getLength());
}
Text::~Text(){
deallocate();
}

Text& Text::operator = (const char& character) {
create(&character, 1);
return *this;
}
Text& Text::operator = (const char* string) {
create(string, Text::stringLength(string));
return *this;
}
Text& Text::operator = (const Text& text) {
if(this != &text)
create(text.getHead(), text.getLength());
return *this;
}
Text& Text::operator = (const Text* text) {
create(text->getHead(), text->getLength());
return *this;
}

Text& Text::operator += (const char& character) {
concatenate(&character, 1);
return *this;
}
Text& Text::operator += (const char* string) {
concatenate(string, Text::stringLength(string));
return *this;
}
Text& Text::operator += (const Text& text) {
concatenate(text.getHead(), text.getLength());
return *this;
}
Text& Text::operator += (const Text* text) {
concatenate(text->getHead(), text->getLength());
return *this;
}

const Text Text::operator + (const char& character) {
return Text(this) += character;
}
const Text Text::operator + (const char* string) {
return Text(this) += string;
}
const Text Text::operator + (const Text& text) {
return Text(this) += text;
}
const Text Text::operator + (const Text* text) {
return Text(this) += text;
}

// Operators
#if !defined(AVR) // Don't use for Arduino
ostream& operator << (ostream& out, Text& input) {
if(!input.isEmpty())
for(char* iter = (char*) input.getHead(); iter < input.getTail(); iter++)
out << *iter;
return out;
}
#endif

bool Text::operator == (const char& character) {
if(this->getLength() != 1) return false;
return compare(this->getHead(), &character, 1);
}
bool Text::operator == (const char* string) {
// OPTIMISE: Makes two runs down the string.
if(this->isEmpty() && (*string == '\0')) return true;
if(this->isEmpty() || (*string == '\0')) return false;
if(this->getLength() != Text::stringLength(string)) return false;
return compare(this->getHead(), string, this->getLength());
}
bool Text::operator == (const Text& other) {
if(this->isEmpty() && other.isEmpty()) return true;
if(this->isEmpty() || other.isEmpty()) return false;
if(this->getLength() != other.getLength()) return false;
return compare(this->getHead(), other.getHead(), this->getLength());
}
bool Text::operator == (const Text* other) {
if(this->isEmpty() && other->isEmpty()) return true;
if(this->isEmpty() || other->isEmpty()) return false;
if(this->getLength() != other->getLength()) return false;
return compare(this->getHead(), other->getHead(), this->getLength());
}

bool Text::operator != (const char& character) {
return !(*this == character);
}
bool Text::operator != (const char* string) {
return !(*this == string);
}
bool Text::operator != (const Text& other) {
return !(*this == other);
}
bool Text::operator != (const Text* other) {
return !(*this == other);
}

const char* Text::getHead() const {
return head;
}
const char* Text::getTail() const {
return tail;
}
const int   Text::getLength() const {
return tail - head;
}
const bool  Text::isEmpty() const {
return(head == tail);
}
const bool  Text::isValid(const char* pointer) {
if((pointer >= this->head) && (pointer <= this->tail)) return true;
return false;
}

// Static
int Text::stringLength(const char* string) {
int length = 0;
while(*string++) length++;
return length;
}

// Temporary
void  Text::debug() {
printf("OBJECT:%p [%p-%p]", this, this->getHead(), this->getTail());
cout << " \"" << *this << "\"" << endl;
}

// PRIVATE
char* Text::allocate(const int& bytes) {
if(bytes < 1) return null;
return (char*) malloc(bytes);
}
void Text::deallocate() {
if(head) free(head);
}
void  Text::create(const char* pointer, const int length) {
char* source = (char*) pointer;
deallocate();
this->head = allocate(length);
if(this->head) // allocation succeeded
{
point(this->head, length);
copy(source, (source + length), this->head);
}
else point(null, 0);
}
void Text::concatenate(const char* pointer, const int length) {
int oldLength = this->getLength();
char* newHead = allocate(oldLength + length);
char* oldPointer = (char*) pointer;
if(!newHead) return; // can't allocate, append fails.
// if the old length is zero, don't try to concatenate it!
if(!this->isEmpty()) copy((char*) this->getHead(), this->getTail(), newHead);
copy(oldPointer, (oldPointer + length), newHead + oldLength);
deallocate();
point(newHead, length + oldLength);
}
void  Text::copy(char* sourceStart, const char* sourceEnd, char* destinationStart) {
// Copies exclusive of sourceEnd.
// Assumes destination has sufficient length.
  while(sourceStart < sourceEnd) *destinationStart++ = *sourceStart++;
}
void  Text::point(char* head, const int& length) {
this->head = head;
this->tail = (head + length);
}
void  Text::point(char* head, char* tail) {
this->head = head;
this->tail = tail;
}
bool  Text::compare(const char* left, const char* right, const int& length) {
for(int i = 0; i < length; i++)
if(*left++ != *right++) return false;
return true;
}

Comments

  1. Good to see your enjoying it.

    Far nicer than either the code at the old job or Arduino's odd syntax :)

    ReplyDelete

Post a Comment