I had to write a small library for a calculator in arduino, and I did, but woe is me I actually made a C++ library, not an arduino one, so I tried to port it to arduino the best I could. After a long fight with the compiler I managed to make it recognize the correctness of the code (it compiled, alongside the sketch, the library files, it even recognized its functions when I invoked one incorrectly) but in the end it looks like the linker refuses to recognize my library.
My project directory structure has an include directory with every source file, and outside this include directory (the root of the sketch) theres my sketch main file. In it I #include my library via #include "include/parser.hpp" (parser.hpp being the header file I wish to use).
Main sketch file:
#include "include/parser.hpp"
void setup(){
Serial.begin(9600);
}
void loop(){
String input = Serial.readString(); // Leemos la entrada.
Parser myParser(input.c_str());
AST *Tree = myParser.parse();
Serial.println(Tree->eval(Tree->getRoot())); // Imprimimos.
delete Tree;
}
myParser is a Parser type object which has a parse function that, when invoked, it returns a pointer to an AST object (if it didn't encounter any errors, that is).
parser.hpp:
#include <Arduino.h>
#include <Vector.h>
#include "lexer.hpp"
#include "ast.hpp"
#ifndef PARSER_H
#define PARSER_H
/* La pila que guardará los nodos del árbol tendrá una capacidad inicial de 4
nodos, al final terminaremos con un solo nodo en la pila (la raíz del árbol)
y regularmente tendremos al menos 2 nodos. (dos operandos). */
#define INITIAL_STACK_SIZE 5
class ParserException{
private:
String error;
public:
ParserException(String error_msg);
String getError() const;
};
class Parser{
private:
Lexer tokenizer;
char *text;
Token current_token;
Vector< Node * >node_stack;
void nextToken();
Node *popStack();
bool parseExpression();
bool parsePrimeExpression();
bool parseTerm();
bool parsePrimeTerm();
bool parseFactor();
public:
Parser(char *text);
AST *parse();
};
#endif /* PARSER_H */
Parser.cpp:
#include <Arduino.h>
#include <Vector.h>
#include "parser.hpp"
#include "lexer.hpp"
#include "ast.hpp"
ParserException::ParserException(String error_msg)
: error{error_msg}{}
String ParserException::getError() const{
return error;
}
Parser::Parser(char *text)
: tokenizer(text){
Node node_array[INITIAL_STACK_SIZE];
nextToken();
node_stack(node_array);
}
AST *Parser::parse(){
if(!parseExpression())
throw ParserException("SYNTAX ERROR");
/* De haber salido todo bien, habrá un solo
nodo en la pila: la raíz del árbol. */
AST *syntaxTree = new AST(popStack());
return syntaxTree;
}
void Parser::nextToken(){
current_token = tokenizer.nextToken();
}
Node *Parser::popStack(){
Node *new_node = node_stack.back();
node_stack.pop_back();
return new_node;
}
bool Parser::parseExpression(){
if(!parseTerm())
return false;
return parsePrimeExpression();
}
bool Parser::parsePrimeExpression(){
if(current_token.type == sum || current_token.type == substraction)
{
Token current_operator = current_token;
nextToken();
if(!parseTerm())
return false;
node_stack.push_back(new Node{popStack(), popStack(), current_operator});
return parsePrimeExpression();
}
return true;
}
bool Parser::parseTerm(){
if(!parseFactor())
return false;
return parsePrimeTerm();
}
bool Parser::parsePrimeTerm(){
if(current_token.type == multiplication || current_token.type == division)
{
Token current_operator = current_token;
nextToken();
if(!parseFactor())
return false;
node_stack.push_back(new Node{popStack(), popStack(), current_operator});
return parsePrimeTerm();
}
return true;
}
bool Parser::parseFactor(){
Token current_operator;
switch(current_token.type)
{
case substraction:
current_operator = current_token; // Nodo de negación.
nextToken();
if(!parseExpression())
return false;
node_stack.push_back(new Node{popStack(), NULL, current_operator});
break;
case left_parens:
nextToken();
if(!parseExpression())
return false;
if(current_token.type != right_parens)
return false;
break;
case number:
node_stack.push_back(new Node{NULL, NULL, current_token});
nextToken();
break;
default:
return false;
}
return true;
}
ast.hpp:
#include <Arduino.h>
#include "lexer.hpp"
#ifndef AST_H
#define AST_H
struct Node {
Node *left {};
Node *right {};
Token value {};
};
class AST{
private:
Node *root;
void postOrderDelete(Node *current); // Recorrido del árbol en postorden.
public:
AST(Node *tree_root);
~AST();
Node *getRoot() const;
float eval(const Node *current);
};
class ZeroDivisionException{
private:
String error_msg;
public:
ZeroDivisionException(String msg);
String getErrorMsg() const;
};
class invalid_argument{
private:
String error_msg;
public:
invalid_argument(String msg);
String getErrorMsg() const;
};
#endif /* AST_H */
ast.cpp:
#include <stdio.h>
#include <Arduino.h>
#include "ast.hpp"
#include "lexer.hpp"
AST::AST(Node *tree_root)
: root{tree_root}{}
void AST::postOrderDelete(Node *current) {
if(current->left)
postOrderDelete(current->left);
if(current->right)
postOrderDelete(current->right);
if(current->value.type == number)
delete [] current->value.value; // Borramos también la cadena de carácteres asignada.
delete current;
}
AST::~AST() {
postOrderDelete(root);
}
Node *AST::getRoot() const {
return root;
}
float AST::eval(const Node *current){
float result;
// Checamos si tenemos un nodo de negación.
if(current->left != NULL && current->right == NULL)
return -1 * eval(current->left);
switch(current->value.type)
{
case sum:
return eval(current->left) + eval(current->right);
case substraction:
return eval(current->left) - eval(current->right);
case multiplication:
return eval(current->left) * eval(current->right);
case division:
{
float denominator = eval(current->left);
if(denominator == 0)
throw ZeroDivisionException("División entre 0.");
return eval(current->right) / denominator;
}
default: // Es un número.
if(sscanf(current->value.value, "%f", &result) == EOF)
throw invalid_argument("Conversión fallida.");
return result;
}
}
ZeroDivisionException::ZeroDivisionException(String msg)
: error_msg{msg} {}
String ZeroDivisionException::getErrorMsg() const{
return error_msg;
}
invalid_argument::invalid_argument(String msg)
: error_msg{msg} {}
String invalid_argument::getErrorMsg() const{
return error_msg;
}
Compiler error message:
arduino-compile
/tmp/ccYGJyKY.ltrans0.ltrans.o: In function `loop':
/home/sebastian/Documentos/LCC/6to Semestre/Microprocesadores/parser_example/parser_example.ino:9: undefined reference to `Parser::Parser(char*)'
/home/sebastian/Documentos/LCC/6to Semestre/Microprocesadores/parser_example/parser_example.ino:10: undefined reference to `Parser::parse()'
/home/sebastian/Documentos/LCC/6to Semestre/Microprocesadores/parser_example/parser_example.ino:12: undefined reference to `AST::getRoot() const'
/home/sebastian/Documentos/LCC/6to Semestre/Microprocesadores/parser_example/parser_example.ino:12: undefined reference to `AST::eval(Node const*)'
/home/sebastian/Documentos/LCC/6to Semestre/Microprocesadores/parser_example/parser_example.ino:14: undefined reference to `AST::~AST()'
Note: I'm not using the IDE, I'm using arduino-cli for both compilation and uploading.