diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/CMakeLists.txt | 22 | ||||
-rw-r--r-- | core/KicadNetLexer.g4 | 38 | ||||
-rw-r--r-- | core/KicadNetParser.g4 | 89 | ||||
-rw-r--r-- | core/include-priv/trygvis/antlr.h | 67 | ||||
-rw-r--r-- | core/include-priv/trygvis/string_utils.h | 23 | ||||
-rw-r--r-- | core/include/trygvis/kicad.h | 102 | ||||
-rw-r--r-- | core/kicad.cpp | 194 |
7 files changed, 535 insertions, 0 deletions
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt new file mode 100644 index 0000000..cbf8e78 --- /dev/null +++ b/core/CMakeLists.txt @@ -0,0 +1,22 @@ +find_package(Antlr4) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +antlr4_add_target( + TARGET KicadNet + LEXER KicadNetLexer.g4 + PARSER KicadNetParser.g4 + STATIC) + +add_library(kicad-utils-core + kicad.cpp) +target_sources(kicad-utils-core PRIVATE $<TARGET_OBJECTS:KicadNet>) + +get_target_property(KicadNet_includes KicadNet INCLUDE_DIRECTORIES) + +target_include_directories(kicad-utils-core + PUBLIC include + PRIVATE include-priv ${KicadNet_includes}) + +target_link_libraries(kicad-utils-core + Antlr4::antlr4_shared) diff --git a/core/KicadNetLexer.g4 b/core/KicadNetLexer.g4 new file mode 100644 index 0000000..e10f7e7 --- /dev/null +++ b/core/KicadNetLexer.g4 @@ -0,0 +1,38 @@ +lexer grammar KicadNetLexer; + +LPAREN: '('; +RPAREN: ')'; + +QUOTE: '"'; + +CODE: 'code'; +COMP: 'comp'; +FIELD: 'field'; +LIBSOURCE: 'libsource'; +LIBPART: 'libpart'; +LIB: 'lib'; +NAME: 'name'; +NET: 'net'; +NODE: 'node'; +NUM: 'num'; +PART: 'part'; +PIN: 'pin'; +REF: 'ref'; +TYPE: 'type'; +VALUE: 'value'; + +STRING: '"' ~["]* '"'; + +INTEGER: [0-9]+; + +ID + : [/+~\_\-\.\*\?/a-zA-Z0-9]+ + ; + +BlockComment + : '/*' .*? '*/' -> skip + ; + +WS + : [ \t\r\n]+ -> skip + ; diff --git a/core/KicadNetParser.g4 b/core/KicadNetParser.g4 new file mode 100644 index 0000000..3971111 --- /dev/null +++ b/core/KicadNetParser.g4 @@ -0,0 +1,89 @@ +parser grammar KicadNetParser; + +options { + tokenVocab = KicadNetLexer; +} + +file: + form+ + ; + +form: + name #formName + | component #formComponent + | field #formField + | net #formNet + | node #formNode + | pinDecl #formDecl + | ref #formRef + | value #formValue + | libpart #formLibpart + | keyValue #formKeyValue + | string #formString + ; + +code: + '(' 'code' INTEGER ')' + ; + +component: + '(' 'comp' ref value libsource keyValue* ')' + ; + +field: + '(' 'field' name string ')' + ; + +name: + '(' 'name' string ')' + ; + +net: + '(' 'net' code name node+ ')' + ; + +node: + '(' 'node' ref pinRef ')' + ; + +pinRef: + '(' 'pin' INTEGER ')' + ; + +pinDecl: + '(' 'pin' '(' 'num' INTEGER ')' '(' 'name' string ')' '(' 'type' string ')' ')' + ; + +ref: + '(' 'ref' string ')' + ; + +value: + '(' 'value' string ')' + ; + +lib: + '(' 'lib' string ')' + ; + +part: + '(' 'part' string ')' + ; + +libpart: + '(' 'libpart' lib part keyValue* ')' + ; + +libsource: + '(' 'libsource' lib part ')' + ; + +keyValue: + '(' string form* ')' + ; + +string: + ID #stringId + | INTEGER #stringInt + | STRING #stringText + ; diff --git a/core/include-priv/trygvis/antlr.h b/core/include-priv/trygvis/antlr.h new file mode 100644 index 0000000..f5656ea --- /dev/null +++ b/core/include-priv/trygvis/antlr.h @@ -0,0 +1,67 @@ +#pragma once + +#include "antlr4-runtime.h" + +namespace trygvis { +namespace antlr { + +// This namespace is shared copied code + +using ParseTree = antlr4::tree::ParseTree; + +class MissingParseTreeProperty : public std::out_of_range { +public: + explicit MissingParseTreeProperty(const std::string &what) : out_of_range(what) { } +}; + +template<typename V, bool debug = false> +class ParseTreeProperty { +public: + virtual V get(Ref<ParseTree> node) { + return get(node.get()); + } + + virtual V get(ParseTree *const node) { + if (!debug) { + return _annotations.at(node); + } + + try { +// cerr << "node = " << node->getText() << endl; + return _annotations.at(node); + } catch (std::out_of_range &e) { + std::cerr << "get(" << node << "), text=" << node->getText() << std::endl; + std::stringstream buf; + buf << "out of range: " << node << ", text=" << node->getText(); + auto msg = buf.str(); + std::cerr << msg << std::endl; + throw MissingParseTreeProperty(msg); + } + } + + virtual void put(ParseTree *const node, V value) { + if (debug) { + std::cerr << "put(" << node << ", " << value << "), text: " << node->getText() << std::endl; + } + _annotations[node] = value; + } + + virtual V removeFrom(ParseTree *const node) { + auto it = _annotations.find(node); + + if (it == _annotations.end()) { + throw MissingParseTreeProperty(node->getText()); + } + + return it->second; + } + +protected: + std::map<ParseTree *, V> _annotations; + +private: +}; + +} // namespace antlr + +} diff --git a/core/include-priv/trygvis/string_utils.h b/core/include-priv/trygvis/string_utils.h new file mode 100644 index 0000000..9902a3f --- /dev/null +++ b/core/include-priv/trygvis/string_utils.h @@ -0,0 +1,23 @@ +#pragma once + +#include <string> + +namespace trygvis { +namespace string_utils { + +/** + * Check if a starts with b. + */ +static bool startsWith(const std::string &a, const std::string &b) { + return b.length() <= a.length() && a.compare(0, b.length(), b) == 0; +} + +/** + * Check if a ends with b. + */ +static bool endsWith(const std::string &a, const std::string &b) { + return b.length() <= a.length() && a.compare(a.length() - b.length(), b.length(), b) == 0; +} + +} // namespace string_utils +} // namespace trygvis diff --git a/core/include/trygvis/kicad.h b/core/include/trygvis/kicad.h new file mode 100644 index 0000000..0c5005d --- /dev/null +++ b/core/include/trygvis/kicad.h @@ -0,0 +1,102 @@ +#pragma once + +#include <experimental/optional> +#include <ostream> +#include <stdexcept> +#include <string> +#include <vector> + +namespace trygvis { +namespace kicad { +template<typename T> +using opt = std::experimental::optional<T>; +using std::experimental::nullopt; +} // namespace kicad +} // namespace trygvis + +namespace trygvis { +namespace kicad { +namespace netlist { + +struct lib_source { + const std::string lib; + const std::string part; + + lib_source(const std::string &lib, const std::string &part) : lib(lib), part(part) {} +}; + +struct component { + const std::string ref; + const std::string value; + const lib_source _lib_source; + + component(const std::string &ref, const std::string &value, const lib_source &_lib_source) : + ref(ref), value(value), _lib_source(_lib_source) {} +}; + +struct pin { + int num; + std::string name; + std::string type; +}; + +struct part { + std::vector<pin> pins; +}; + +class node { +public: + const std::string ref; + const int pin; + + node(const std::string &ref, int pin) : ref(ref), pin(pin) {} +}; + +class net { +public: + const int code; + const std::string name; + const std::vector<node> nodes; + + net(int code, const std::string &name, const std::vector<node> &nodes) : code(code), name(name), nodes(nodes) {} + + const node *node_for_ref(const std::string &ref) const; +}; + +struct netlist { + std::vector<component> components; + std::vector<part> parts; + std::vector<net> nets; + + opt<const component *> find_component(const std::string &ref) const; + + std::vector<const net *> find_usage_of(const std::string &ref) const; +}; + +class kicad_parse_exception : public std::runtime_error { +public: + explicit kicad_parse_exception(const std::vector<std::string> &messages) : + runtime_error("Parse error"), messages(messages) {} + + ~kicad_parse_exception() {} + + const std::vector<std::string> messages; +}; + +class kicad_net_loader { +public: + kicad_net_loader(); + + virtual ~kicad_net_loader(); + + netlist load(std::string path, std::ostream &err); + + void setDebug(bool debug); + +private: + bool debug_; +}; + +} // namespace netlist +} // namespace kicad +} // namespace trygvis diff --git a/core/kicad.cpp b/core/kicad.cpp new file mode 100644 index 0000000..1a6ca2f --- /dev/null +++ b/core/kicad.cpp @@ -0,0 +1,194 @@ +#include "trygvis/kicad.h" +#include "trygvis/antlr.h" +#include "trygvis/string_utils.h" +#include <KicadNetLexer.h> +#include <KicadNetParser.h> +#include <KicadNetParserBaseListener.h> +#include <exception> +#include <stdexcept> + +using namespace std; +using namespace trygvis::antlr; +using namespace trygvis::string_utils; + +namespace trygvis { +namespace kicad { +namespace netlist { + +const node *net::node_for_ref(const std::string &ref) const { + for (auto &node: nodes) { + if (node.ref == ref) { + return &node; + } + } + + return nullptr; +} + +opt<const component *> netlist::find_component(const string &ref) const { + for (const component &c :components) { + int x = c.ref.compare(ref); + if (x == 0) { + return &c; + } + } + return std::experimental::nullopt; +} + +vector<const net *> netlist::find_usage_of(const string &ref) const { + vector<const net *> usage; + + for (auto &net : nets) { + if (net.nodes.size() <= 1) { + continue; + } + + for (auto &node: net.nodes) { + if (node.ref == ref) { + usage.push_back(&net); + } + } + } + + return usage; +} + +static +int parse(const Ref<antlr4::tree::TerminalNode> &integer) { + unsigned long long i = strtoull(integer->getText().c_str(), NULL, 10); + + return static_cast<int>(i); +} + +class KicadErrorListener : public BaseErrorListener { +public: + vector<string> messages; + + void syntaxError(IRecognizer *recognizer, Token *offendingSymbol, size_t line, int charPositionInLine, + const string &msg, exception_ptr e) override { + static_cast<void>(recognizer); + static_cast<void>(offendingSymbol); + static_cast<void>(e); + messages.push_back("line " + to_string(line) + ":" + to_string(charPositionInLine) + ": " + msg); + } +}; + +class kicad_main_listener : public KicadNetParserBaseListener { + +public: + vector<component> components; + vector<part> parts; + vector<net> nets; + vector<node> nodes; + + ParseTreeProperty<string> strings; + + virtual void exitNet(KicadNetParser::NetContext *ctx) override { + auto code = parse(ctx->code()->INTEGER()); + auto name = strings.get(ctx->name()->string()); + + if (startsWith(name, "/")) { + name = name.substr(1); + } + +// cerr << "exitNet: " << "code=" << code << ", name=" << name << ", nodes=" << nodes.size() << endl; + +// if (nodes.size() > 1) { +// cerr << "Net#" << code << ": " << name << endl; +// for (auto &node: nodes) { +// cerr << " Node: " << node.ref << "#" << node.pin << endl; +// } +// } + nets.emplace_back(code, name, nodes); + nodes.clear(); + } + + virtual void exitNode(KicadNetParser::NodeContext *ctx) override { + auto ref = strings.get(ctx->ref()->string()); + auto pin = parse(ctx->pinRef()->INTEGER()); + +// cerr << "exitNode: " << "ref=" << ref << ", pin=" << pin << endl; + + nodes.emplace_back(ref, pin); + } + + virtual void exitComponent(KicadNetParser::ComponentContext *ctx) override { + auto ref = strings.get(ctx->ref()->string()); + auto value = strings.get(ctx->value()->string()); + + lib_source ls{strings.get(ctx->libsource()->lib()->string()), + strings.get(ctx->libsource()->part()->string())}; + + components.emplace_back(ref, value, ls); + } + + virtual void exitStringId(KicadNetParser::StringIdContext *ctx) override { + strings.put(ctx, ctx->getText()); + } + + virtual void exitStringInt(KicadNetParser::StringIntContext *ctx) override { + strings.put(ctx, ctx->getText()); + } + + virtual void exitStringText(KicadNetParser::StringTextContext *ctx) override { + auto s = ctx->getText(); + strings.put(ctx, s.substr(1, s.length() - 2)); + } +}; + +netlist kicad_net_loader::load(string path, ostream &err) { + + ANTLRFileStream input(path); + KicadNetLexer lexer(&input); + CommonTokenStream tokens(&lexer); + + tokens.fill(); + + if (debug_) { + for (auto token : tokens.getTokens()) { + err << token->toString() << endl; + } + } + + KicadNetParser parser(&tokens); + parser.removeErrorListeners(); + KicadErrorListener errorListener; + parser.addErrorListener(&errorListener); + + parser.file(); + + if (!errorListener.messages.empty()) { + throw kicad_parse_exception(errorListener.messages); + } + + parser.reset(); + + kicad_main_listener mainListener; + parser.addParseListener(&mainListener); + + auto file = parser.file(); + + if (debug_ && parser.getNumberOfSyntaxErrors() == 0) { + err << file->toStringTree(&parser) << endl; + } + + return netlist { + mainListener.components, + mainListener.parts, + mainListener.nets, + }; +} + +kicad_net_loader::kicad_net_loader() : debug_(false) { +} + +kicad_net_loader::~kicad_net_loader() { +} + +void kicad_net_loader::setDebug(bool debug) { + debug_ = debug; +} + +} // namespace netlist +} // namespace trygvis +} // namespace kicad |