diff --git a/.gitignore b/.gitignore index f15b582..2a371bb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .vscode builddir *.kb +*.obk diff --git a/meson.build b/meson.build index dae4ed9..b8b534f 100644 --- a/meson.build +++ b/meson.build @@ -11,19 +11,20 @@ project('obelisk', llvm = dependency('llvm', version: '>= 14.0.0', modules : ['core', 'target', 'mcjit', 'nativecodegen'], required : true, method: 'config-tool') -doxygen = find_program('doxygen', required : false) - cdata = configuration_data() cdata.set('VERSION', meson.project_version()) -if find_program('dot', required : false).found() - cdata.set('HAVE_DOT', 'YES') -else - cdata.set('HAVE_DOT', 'NO') -endif - -if doxygen.found() - subdir('doc') +docs_enabled = get_option('docs') +if docs_enabled + doxygen = find_program('doxygen', required : false) + if doxygen.found() + if find_program('dot', required : false).found() + cdata.set('HAVE_DOT', 'YES') + else + cdata.set('HAVE_DOT', 'NO') + endif + subdir('doc') + endif endif subdir('src') diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..4dff45a --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,5 @@ +option('docs', + type: 'boolean', + value: true, + description: 'Build documentation for obelisk' +) diff --git a/src/lexer.cpp b/src/lexer.cpp index 5627689..32509b1 100644 --- a/src/lexer.cpp +++ b/src/lexer.cpp @@ -2,20 +2,33 @@ #include +obelisk::Lexer::Lexer(const std::string& sourceFile) +{ + fileStream_.open(sourceFile, std::ifstream::in); + if (!fileStream_) + { + throw obelisk::LexerException("could not open source file " + sourceFile); + } +} + +obelisk::Lexer::~Lexer() +{ + fileStream_.close(); + fileStream_.clear(); +} + int obelisk::Lexer::getToken() { - static int lastChar = ' '; - while (isspace(lastChar)) { - lastChar = std::getc(stdin); + lastChar = fileStream_.get(); } if (isalpha(lastChar)) { eraseIdentifier(); appendIdentifier(lastChar); - while (isalnum((lastChar = getchar()))) + while (isalnum((lastChar = fileStream_.get()))) { appendIdentifier(lastChar); } @@ -63,7 +76,7 @@ int obelisk::Lexer::getToken() firstPeriod = true; } numberStr += lastChar; - lastChar = getchar(); + lastChar = fileStream_.get(); } while (isdigit(lastChar) || lastChar == '.'); @@ -83,7 +96,7 @@ int obelisk::Lexer::getToken() } else if (lastChar == '/') { - lastChar = getchar(); + lastChar = fileStream_.get(); if (lastChar == '/') { commentLine(&lastChar); @@ -101,7 +114,7 @@ int obelisk::Lexer::getToken() } int thisChar = lastChar; - lastChar = getchar(); + lastChar = fileStream_.get(); return thisChar; } @@ -109,7 +122,7 @@ void obelisk::Lexer::commentLine(int* lastChar) { do { - *lastChar = getchar(); + *lastChar = fileStream_.get(); } while (*lastChar != EOF && *lastChar != '\n' && *lastChar != '\r'); } diff --git a/src/lexer.h b/src/lexer.h index 83ec706..4562611 100644 --- a/src/lexer.h +++ b/src/lexer.h @@ -1,6 +1,7 @@ #ifndef OBELISK_LEXER_H #define OBELISK_LEXER_H +#include #include namespace obelisk @@ -12,6 +13,12 @@ namespace obelisk class Lexer { private: + int lastChar = ' '; + /** + * @brief The stream of the source file being read. + * + */ + std::ifstream fileStream_; /** * @brief The last found identifier. * @@ -21,7 +28,7 @@ namespace obelisk * @brief The last found number. * */ - double numberValue_; + double numberValue_ = 0; /** * @brief Set the identifier. @@ -113,6 +120,19 @@ namespace obelisk kTokenString = -9 }; + /** + * @brief Construct a new Lexer object. + * + * @param[in] sourceFile The source file to read. + */ + Lexer(const std::string& sourceFile); + + /** + * @brief Destroy the Lexer object. + * + */ + ~Lexer(); + /** * @brief Gets the next token in the source code. * diff --git a/src/meson.build b/src/meson.build index 0f4d7b2..81adf96 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,3 +1,10 @@ +conf_data = configuration_data() +conf_data.set('version', meson.project_version()) +configure_file(input : 'version.h.in', + output : 'version.h', + configuration : conf_data +) + obelisk_sources = files( 'obelisk.cpp', 'lexer.cpp', diff --git a/src/models/fact.cpp b/src/models/fact.cpp index d517286..8011bcc 100644 --- a/src/models/fact.cpp +++ b/src/models/fact.cpp @@ -7,8 +7,8 @@ const char* obelisk::Fact::createTable() CREATE TABLE "fact" ( "id" INTEGER NOT NULL UNIQUE, "left_entity" INTEGER NOT NULL, - "right_entity" INTEGER NOT NULL, "verb" INTEGER NOT NULL, + "right_entity" INTEGER NOT NULL, "is_true" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY("id" AUTOINCREMENT), UNIQUE("left_entity", "right_entity", "verb") diff --git a/src/obelisk.cpp b/src/obelisk.cpp index 8a110df..8d64044 100644 --- a/src/obelisk.cpp +++ b/src/obelisk.cpp @@ -2,20 +2,20 @@ #include "lexer.h" #include "obelisk.h" #include "parser.h" +#include "version.h" #include #include #include #include -static int obelisk::mainLoop() +int obelisk::mainLoop(const std::vector& sourceFiles, const std::string& kbFile) { - auto parser = std::unique_ptr {new obelisk::Parser()}; std::unique_ptr kb; try { - kb = std::unique_ptr {new obelisk::KnowledgeBase("cromer.kb")}; + kb = std::unique_ptr {new obelisk::KnowledgeBase(kbFile.c_str())}; } catch (obelisk::KnowledgeBaseException& exception) { @@ -23,8 +23,20 @@ static int obelisk::mainLoop() return EXIT_FAILURE; } - // Prime the first token. - fprintf(stderr, "ready> "); + size_t file = 0; + std::shared_ptr lexer; + try + { + lexer = std::shared_ptr {new obelisk::Lexer(sourceFiles[file++])}; + } + catch (obelisk::LexerException& exception) + { + std::cout << exception.what() << std::endl; + return EXIT_FAILURE; + } + auto parser = std::unique_ptr {new obelisk::Parser(lexer)}; + + // prime the first token try { parser->getNextToken(); @@ -37,14 +49,29 @@ static int obelisk::mainLoop() while (true) { - fprintf(stderr, "ready> "); switch (parser->getCurrentToken()) { case obelisk::Lexer::kTokenEof : - return EXIT_SUCCESS; - case ';' : // ignore top-level semicolons. - std::cout << "Identifier: " << parser->getLexer()->getIdentifier() << std::endl; - std::cout << "Num: " << parser->getLexer()->getNumberValue() << std::endl; + // end of source file found, create a new lexer and pass it to the parser to use + if (file >= sourceFiles.size()) + { + return EXIT_SUCCESS; + } + try + { + lexer = std::shared_ptr {new obelisk::Lexer(sourceFiles[file++])}; + parser->setLexer(lexer); + // prime the first token in the parser + parser->getNextToken(); + } + catch (obelisk::LexerException& exception) + { + std::cout << exception.what() << std::endl; + return EXIT_FAILURE; + } + break; + case ';' : + // semicolon found, the end of a statement try { parser->getNextToken(); @@ -73,38 +100,57 @@ static int obelisk::mainLoop() return EXIT_SUCCESS; } +static void obelisk::showUsage() +{ + std::cout << obelisk::usageMessage << std::endl; +} + int main(int argc, char** argv) { - for (int i = 1; i < argc; i++) + std::vector sourceFiles; + std::string kbFile = "obelisk.kb"; + + while (true) { - std::cout << argv[i] << std::endl; + int option_index = 0; + switch (getopt_long(argc, argv, "k:hv", obelisk::long_options, &option_index)) + { + case 'k' : + kbFile = std::string(optarg); + continue; + case 'h' : + obelisk::showUsage(); + return EXIT_SUCCESS; + break; + case 'v' : + std::cout << "obelisk " << obelisk::version << std::endl; + return EXIT_SUCCESS; + break; + default : + obelisk::showUsage(); + return EXIT_FAILURE; + break; + + case -1 : + break; + } + + break; } - // This can be used to store a double as 2 floats in the database, then restore it back to a double. - // Inspired by Godot's double precision on the GPU to render large worlds. - /*try + if (optind < argc) { - float first; - float second; - double var = 0.123456789012345; - - obelisk::KnowledgeBase* kb = new obelisk::KnowledgeBase("cromer.kb"); - kb->getFloat(first, second, var); - std::cout << std::setprecision(std::numeric_limits::digits10) - << "Double: " << var << std::endl - << "First: " << first << std::endl - << "Second: " << second << std::endl; - var = 0.0; - kb->getDouble(var, first, second); - std::cout << std::setprecision(std::numeric_limits::digits10) - << "Double: " << var << std::endl - << "First: " << first << std::endl - << "Second: " << second << std::endl; + while (optind < argc) + { + sourceFiles.push_back(argv[optind++]); + } } - catch (obelisk::KnowledgeBaseException& exception) + + if (sourceFiles.size() == 0) { + obelisk::showUsage(); return EXIT_FAILURE; - }*/ + } - return obelisk::mainLoop(); + return obelisk::mainLoop(sourceFiles, kbFile); } diff --git a/src/obelisk.h b/src/obelisk.h index 588182e..e3abfcb 100644 --- a/src/obelisk.h +++ b/src/obelisk.h @@ -1,15 +1,46 @@ +#include + /** - * @brief The obelisk namespace contains everything needed to compile obelisk + * @brief The obelisk namespace contains everything needed to compile obelisk. * code. * */ namespace obelisk { + /** + * @brief The usage messsage displayed during help or incorrect usage. + * + */ + std::string usageMessage = R"(Usage: obelisk [OPTION]... [FILE]... +Compile the obelisk source FILE(s) into knoweldge base and library. + +Options: + -h, --help shows this help/usage message + -k, --kb=FILENAME output knowldege base filename + -v, --version shows the version of obelisk)"; + + /** + * @brief The command line arguments that obelisk accepts. + * + */ + static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"kb", required_argument, 0, 'k'}, + {"version", no_argument, 0, 'v'}, + {0, 0, 0, 0 } + }; + + /** + * @brief Prints out the usage of obelisk to the stdin. + * + */ + static void showUsage(); + /** * @brief This is the main loop for obelisk. * This loop handles lexing and parsing of obelisk source code. * * @return int Returns EXIT_SUCCESS or EXIT_FAILURE. */ - static int mainLoop(); + int mainLoop(const std::vector &sourceFiles, const std::string &kbFile); } // namespace obelisk diff --git a/src/parser.cpp b/src/parser.cpp index 41431db..c420a70 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -11,16 +11,17 @@ #include #include -obelisk::Parser::Parser() -{ - lexer_ = std::unique_ptr {new obelisk::Lexer()}; -} - -std::unique_ptr& obelisk::Parser::getLexer() +std::shared_ptr obelisk::Parser::getLexer() { return lexer_; } +void obelisk::Parser::setLexer(std::shared_ptr lexer) +{ + lexer_ = lexer; + currentToken_ = 0; +} + int obelisk::Parser::getNextToken() { try diff --git a/src/parser.h b/src/parser.h index 00aa208..19b4a8b 100644 --- a/src/parser.h +++ b/src/parser.h @@ -15,8 +15,8 @@ namespace obelisk class Parser { private: - std::unique_ptr lexer_; - int currentToken_; + std::shared_ptr lexer_; + int currentToken_ = 0; void setCurrentToken(int currentToken); @@ -37,9 +37,13 @@ namespace obelisk void parseFact(std::vector& facts); public: - Parser(); + Parser(std::shared_ptr lexer) : + lexer_(lexer) + { + } - std::unique_ptr& getLexer(); + std::shared_ptr getLexer(); + void setLexer(std::shared_ptr lexer); int getCurrentToken(); diff --git a/src/version.h.in b/src/version.h.in new file mode 100644 index 0000000..7e82845 --- /dev/null +++ b/src/version.h.in @@ -0,0 +1,8 @@ +namespace obelisk +{ + /** + * @brief The current version of obelisk. + * + */ + std::string version = "@version@"; +} // namespace obelisk