/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "NFAtoDFA.h"
#include "RegexParser.h"
#include <fstream>
#include <sstream>
#include <string>
/**
* Processes a .lex file and produces .h and .cpp files which implement a lexical analyzer. The .lex
* file is a text file with one token definition per line. Each line is of the form:
* <TOKEN_NAME> = <pattern>
* where <pattern> is either a regular expression (e.g [0-9]) or a double-quoted literal string.
*/
static constexpr const char* HEADER =
"/*\n"
" * Copyright 2017 Google Inc.\n"
" *\n"
" * Use of this source code is governed by a BSD-style license that can be\n"
" * found in the LICENSE file.\n"
" */\n"
"/*****************************************************************************************\n"
" ******************** This file was generated by sksllex. Do not edit. *******************\n"
" *****************************************************************************************/\n";
void writeH(const DFA& dfa, const char* lexer, const char* token,
const std::vector<std::string>& tokens, const char* hPath) {
std::ofstream out(hPath);
SkASSERT(out.good());
out << HEADER;
out << "#ifndef SKSL_" << lexer << "\n";
out << "#define SKSL_" << lexer << "\n";
out << "#include <cstddef>\n";
out << "#include <cstdint>\n";
out << "namespace SkSL {\n";
out << "\n";
out << "struct " << token << " {\n";
out << " enum Kind {\n";
for (const std::string& t : tokens) {
out << " #undef " << t << "\n";
out << " " << t << ",\n";
}
out << " };\n";
out << "\n";
out << " " << token << "()\n";
out << " : fKind(Kind::INVALID)\n";
out << " , fOffset(-1)\n";
out << " , fLength(-1) {}\n";
out << "\n";
out << " " << token << "(Kind kind, int32_t offset, int32_t length)\n";
out << " : fKind(kind)\n";
out << " , fOffset(offset)\n";
out << " , fLength(length) {}\n";
out << "\n";
out << " Kind fKind;\n";
out << " int fOffset;\n";
out << " int fLength;\n";
out << "};\n";
out << "\n";
out << "class " << lexer << " {\n";
out << "public:\n";
out << " void start(const char* text, int32_t length) {\n";
out << " fText = text;\n";
out << " fLength = length;\n";
out << " fOffset = 0;\n";
out << " }\n";
out << "\n";
out << " " << token << " next();\n";
out << "\n";
out << "private:\n";
out << " const char* fText;\n";
out << " int32_t fLength;\n";
out << " int32_t fOffset;\n";
out << "};\n";
out << "\n";
out << "} // namespace\n";
out << "#endif\n";
}
void writeCPP(const DFA& dfa, const char* lexer, const char* token, const char* include,
const char* cppPath) {
std::ofstream out(cppPath);
SkASSERT(out.good());
out << HEADER;
out << "#include \"" << include << "\"\n";
out << "\n";
out << "namespace SkSL {\n";
out << "\n";
size_t states = 0;
for (const auto& row : dfa.fTransitions) {
states = std::max(states, row.size());
}
out << "static int8_t mappings[" << dfa.fCharMappings.size() << "] = {\n ";
const char* separator = "";
for (int m : dfa.fCharMappings) {
out << separator << std::to_string(m);
separator = ", ";
}
out << "\n};\n";
out << "static int16_t transitions[" << dfa.fTransitions.size() << "][" << states << "] = {\n";
for (size_t c = 0; c < dfa.fTransitions.size(); ++c) {
out << " {";
for (size_t j = 0; j < states; ++j) {
if ((size_t) c < dfa.fTransitions.size() && j < dfa.fTransitions[c].size()) {
out << " " << dfa.fTransitions[c][j] << ",";
} else {
out << " 0,";
}
}
out << " },\n";
}
out << "};\n";
out << "\n";
out << "static int8_t accepts[" << states << "] = {";
for (size_t i = 0; i < states; ++i) {
if (i < dfa.fAccepts.size()) {
out << " " << dfa.fAccepts[i] << ",";
} else {
out << " " << INVALID << ",";
}
}
out << " };\n";
out << "\n";
out << token << " " << lexer << "::next() {\n";
out << " // note that we cheat here: normally a lexer needs to worry about the case\n";
out << " // where a token has a prefix which is not itself a valid token - for instance, \n";
out << " // maybe we have a valid token 'while', but 'w', 'wh', etc. are not valid\n";
out << " // tokens. Our grammar doesn't have this property, so we can simplify the logic\n";
out << " // a bit.\n";
out << " int32_t startOffset = fOffset;\n";
out << " if (startOffset == fLength) {\n";
out << " return " << token << "(" << token << "::END_OF_FILE, startOffset, 0);\n";
out << " }\n";
out << " int16_t state = 1;\n";
out << " while (fOffset < fLength) {\n";
out << " if ((uint8_t) fText[fOffset] >= " << dfa.fCharMappings.size() << ") {";
out << " ++fOffset;\n";
out << " break;";
out << " }";
out << " int16_t newState = transitions[mappings[(int) fText[fOffset]]][state];\n";
out << " if (!newState) {\n";
out << " break;\n";
out << " }\n";
out << " state = newState;";
out << " ++fOffset;\n";
out << " }\n";
out << " Token::Kind kind = (" << token << "::Kind) accepts[state];\n";
out << " return " << token << "(kind, startOffset, fOffset - startOffset);\n";
out << "}\n";
out << "\n";
out << "} // namespace\n";
}
void process(const char* inPath, const char* lexer, const char* token, const char* hPath,
const char* cppPath) {
NFA nfa;
std::vector<std::string> tokens;
tokens.push_back("END_OF_FILE");
std::string line;
std::ifstream in(inPath);
while (std::getline(in, line)) {
std::istringstream split(line);
std::string name, delimiter, pattern;
if (split >> name >> delimiter >> pattern) {
SkASSERT(split.eof());
SkASSERT(name != "");
SkASSERT(delimiter == "=");
SkASSERT(pattern != "");
tokens.push_back(name);
if (pattern[0] == '"') {
SkASSERT(pattern.size() > 2 && pattern[pattern.size() - 1] == '"');
RegexNode node = RegexNode(RegexNode::kChar_Kind, pattern[1]);
for (size_t i = 2; i < pattern.size() - 1; ++i) {
node = RegexNode(RegexNode::kConcat_Kind, node,
RegexNode(RegexNode::kChar_Kind, pattern[i]));
}
nfa.addRegex(node);
}
else {
nfa.addRegex(RegexParser().parse(pattern));
}
}
}
NFAtoDFA converter(&nfa);
DFA dfa = converter.convert();
writeH(dfa, lexer, token, tokens, hPath);
writeCPP(dfa, lexer, token, (std::string("SkSL") + lexer + ".h").c_str(), cppPath);
}
int main(int argc, const char** argv) {
if (argc != 6) {
printf("usage: sksllex <input.lex> <lexername> <tokenname> <output.h> <output.cpp>\n");
exit(1);
}
process(argv[1], argv[2], argv[3], argv[4], argv[5]);
return 0;
}