Commit 34f59163 authored by jan.koester's avatar jan.koester
Browse files

new flex

parent bb6556d0
Loading
Loading
Loading
Loading
+81 −19
Changes for commands/text/flex.cpp: 81 added lines, 19 removed lines.
Original line number Diff line number Diff line
@@ -37,15 +37,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// meaning: "flex -oscripts/kconfig/lexer.lex.c -L scripts/kconfig/lexer.l"
// (scripts/Makefile.host: "cmd_flex = $(LEX) -o$@ -L $<").
//
// Only the CLI surface real-world callers actually use is supported:
// "flex [-L] -o OUTFILE INFILE" (attached "-oFILE", as the kernel invokes
// it, or separate "-o FILE"). "-L" (suppress #line directives) is accepted
// and is a no-op -- this generator never emits #line directives at all.
// Switches that select tables or diagnostics are accepted and change nothing
// (-C..., -b, -d, -f, -p, -v, -w, ...); this generator always builds one kind
// of table, and never emits #line directives (-L is implied).

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

#include "shell.h"
#include "flex/flex.h"
@@ -57,42 +58,91 @@ namespace fresh { namespace cmd {
        FlexCommand():ShellCommand("flex"){}

        int runCommand(int argc,const char **argv){
            std::string outfile, infile;
            fresh::flex::FlexOptions cli;
            std::vector<std::pair<std::string,std::string>> settings;
            std::vector<std::string> infiles;
            bool toStdout = false;

            for(int i=1;i<argc;++i){
                std::string a=argv[i];
                if(a=="-L") continue;
                if(a=="-o"){
                    if(i+1>=argc){ std::cerr << "flex: -o requires an argument" << std::endl; return 2; }
                    outfile=argv[++i];
                if(a=="--version" || a=="-V"){ std::cout << "flex 2.6.4" << std::endl; return 0; }
                if(a=="--help" || a=="-h" || a=="-?"){
                    std::cout << "usage: flex [-LiItRsBIl78] [-o FILE] [-P PREFIX] [--header-file=FILE] [FILE...]" << std::endl;
                    return 0;
                }
                if(a=="-" ){ infiles.push_back(a); continue; }
                if(a.compare(0,2,"--")==0){
                    std::string name=a.substr(2), value;
                    size_t eq=name.find('=');
                    if(eq!=std::string::npos){ value=name.substr(eq+1); name=name.substr(0,eq); }
                    if(name=="stdout"){ toStdout=true; continue; }
                    if(name=="outfile" || name=="prefix" || name=="header-file"){
                        if(eq==std::string::npos){
                            if(i+1>=argc){ std::cerr << "flex: " << a << " requires an argument" << std::endl; return 2; }
                            value=argv[++i];
                        }
                    }
                    if(!cli.set(name,value)){ std::cerr << "flex: unknown option: " << a << std::endl; return 2; }
                    settings.emplace_back(name,value);
                    continue;
                }
                if(a.size()>2 && a[0]=='-' && a[1]=='o'){ outfile=a.substr(2); continue; }
                if(!a.empty() && a[0]=='-'){
                    std::cerr << "flex: unknown option: " << a << std::endl;
                if(a.size()>1 && a[0]=='-'){
                    bool stop=false;
                    for(size_t k=1;k<a.size() && !stop;++k){
                        char c=a[k];
                        auto argument=[&](std::string &to){
                            if(k+1<a.size()) to=a.substr(k+1);
                            else if(i+1<argc) to=argv[++i];
                            else return false;
                            stop=true;
                            return true;
                        };
                        std::string value;
                        switch(c){
                        case 'o': if(!argument(value)){ std::cerr << "flex: -o requires an argument" << std::endl; return 2; }
                                  settings.emplace_back("outfile",value); break;
                        case 'P': if(!argument(value)){ std::cerr << "flex: -P requires an argument" << std::endl; return 2; }
                                  settings.emplace_back("prefix",value); break;
                        case 'S': case 'T': case 'n': if(c=='S') argument(value); break;
                        case 'C': stop=true; break;
                        case 'i': settings.emplace_back("case-insensitive",""); break;
                        case 'I': settings.emplace_back("always-interactive",""); break;
                        case 'B': settings.emplace_back("never-interactive",""); break;
                        case 'R': settings.emplace_back("reentrant",""); break;
                        case 's': settings.emplace_back("nodefault",""); break;
                        case 't': toStdout=true; break;
                        case 'L': case 'b': case 'd': case 'f': case 'F': case 'l': case 'p': case 'v': case 'w':
                        case 'c': case '7': case '8': case 'X': case 'D': case '+':
                            if(c=='+'){ std::cerr << "flex: C++ scanners are not supported" << std::endl; return 2; }
                            break;
                        default:
                            std::cerr << "flex: unknown option: -" << c << std::endl;
                            return 2;
                        }
                infile=a;
                    }

            if(infile.empty() || outfile.empty()){
                std::cerr << "flex: usage: flex [-L] -o OUTFILE INFILE" << std::endl;
                return 2;
                    continue;
                }
                infiles.push_back(a);
            }

            std::stringstream ss;
            if(infiles.empty() || (infiles.size()==1 && infiles[0]=="-")) ss << std::cin.rdbuf();
            else for(auto &infile: infiles){
                std::ifstream in(infile.c_str());
                if(!in.is_open()){
                    std::cerr << "flex: " << infile << ": cannot open" << std::endl;
                    return 1;
                }
            std::stringstream ss; ss<<in.rdbuf();
                ss << in.rdbuf();
            }

            fresh::flex::LexReader reader(ss.str(), infile);
            fresh::flex::LexReader reader(ss.str(), infiles.empty() ? "<stdin>" : infiles[0]);
            fresh::flex::LexSpec spec;
            if(!reader.read(spec)){
                for(auto &e: reader.errors()) std::cerr << "flex: " << e << std::endl;
                return 1;
            }
            for(auto &s: settings) spec.options.set(s.first,s.second);

            fresh::flex::CodeGenerator codegen(spec);
            std::string source = codegen.generateSource();
@@ -101,6 +151,18 @@ namespace fresh { namespace cmd {
                return 1;
            }

            if(!spec.options.headerFile.empty()){
                std::ofstream outH(spec.options.headerFile.c_str());
                if(!outH.is_open()){
                    std::cerr << "flex: " << spec.options.headerFile << ": cannot open for writing" << std::endl;
                    return 1;
                }
                outH << codegen.generateHeader();
            }

            if(toStdout){ std::cout << source; return 0; }
            std::string outfile=spec.options.outfile;
            if(outfile.empty()) outfile="lex."+spec.options.prefix+".c";
            std::ofstream outC(outfile.c_str());
            if(!outC.is_open()){
                std::cerr << "flex: " << outfile << ": cannot open for writing" << std::endl;
+1524 −394

File changed.

Preview size limit exceeded, changes collapsed.

+45 −27
Changes for lib/flex/flex.h: 45 added lines, 27 removed lines.
Original line number Diff line number Diff line
@@ -38,15 +38,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// everything that actually understands .l files lives here, same division
// of labor as lib/bison.
//
// Scope is deliberately bounded to what the Linux kernel's real
// scripts/kconfig/lexer.l (the actual target -- see kernel.spec's %build)
// uses: %x exclusive start conditions, <SC>{ ... } block scoping, <<EOF>>
// per-state rules, named definitions ({name}), POSIX bracket expressions
// incl. [:blank:]-style classes, trailing context (r1/r2), the yy_*
// buffer-stack API (yy_create_buffer/yy_switch_to_buffer/yy_delete_buffer),
// input()/unput(), BEGIN(), yylineno. NOT supported (not used by the
// target): %array, REJECT, yymore(), yy_scan_string/yy_scan_buffer, multiple
// input files via -f/--stdout skeleton customization, %top.
// Compatible with flex 2.6 for C scanners: reentrant ones, %option prefix,
// bison-bridge/-locations, the whole buffer API, yytext inside the input
// buffer, yymore(), REJECT, yyless()/unput()/input(), %array, start-condition
// stacks, trailing context, group flags and class set operators. Tested on
// the kernel's and PostgreSQL's scanners. Not there: C++ scanners (%option
// c++), which are an error, and serialized tables (--tables-file).

#pragma once

@@ -77,10 +74,31 @@ struct Rule {
    int line = 0;
};

// %option settings; command line switches set the same ones.
struct FlexOptions {
    std::string prefix = "yy";
    std::string outfile, headerFile, extraType;
    bool reentrant = false;
    bool bisonBridge = false, bisonLocations = false;
    bool yywrap = true;
    bool stack = false;
    bool main = false;
    bool caseless = false;
    bool nodefault = false;
    bool array = false;
    bool noUnistd = false;
    bool noYyalloc = false, noYyrealloc = false, noYyfree = false;
    int interactive = -1;                   // -1: ask isatty()

    // One option by name, "no" prefix included. False if it isn't known.
    bool set(const std::string &name, const std::string &value);
};

struct LexSpec {
    std::vector<StartCondition> conditions; // conditions[0] is always INITIAL
    std::map<std::string,std::string> defs; // {name} -> raw pattern text (definitions section)
    std::vector<std::string> options;       // %option tokens, lowercased, as-seen (informational only)
    FlexOptions options;
    std::string top;                        // %top{ ... } block(s), verbatim, concatenated
    std::string prologue;                   // definitions-section %{ ... %} block(s), verbatim, concatenated
    std::string rulesPreamble;               // indented code appearing before the first real rule (verbatim)
    std::vector<Rule> rules;
@@ -127,6 +145,8 @@ private:
    // the inner text (braces excluded); pos_ is left just past the closing
    // '}'.
    std::string readBraceBlock();
    std::string readComment();  // pos_ at "/*"; leaves it just past "*/"
    void parseOptions(const std::string &text);

    void parseDefinitionsSection();
    void parseRulesSection();
@@ -142,11 +162,8 @@ private:
    // whitespace before the action): either a brace-delimited block or the
    // rest of the line. Consumes through end of line either way.
    void readActionInto(Rule &r);
    // Handles the rule(s) scoped to a "<SC1,SC2>" (or "<*>") prefix already
    // consumed by the caller: either a "<SC>{ ... }" block of multiple rules
    // (terminated by a line whose only content is "}"), or a single rule on
    // the same line.
    void applyConditionBlock(const std::vector<int> &conditions);
    bool atConditionPrefix() const;
    void parseScoped(const std::vector<int> &outer);
};

// ---- Pattern (regex) model ---------------------------------------------------
@@ -189,26 +206,20 @@ struct ParsedPattern {

// ---- Bytecode ("Pike VM") ----------------------------------------------------

// A tiny Thompson-NFA bytecode, simulated at scan time via the classic
// "thread list" algorithm (Pike VM / Russ Cox's construction) rather than
// precompiled into a minimized DFA table the way real flex does it: with a
// handful of rules and kilobyte-scale Kconfig input files, raw NFA
// simulation is already far faster than the actual C compilation this feeds
// into, and it sidesteps having to implement DFA subset construction +
// equivalence-class minimization + table compression correctly. Emitted
// once as generic interpreter C code (FRESH_FLEX_RUNTIME_C in flex.cpp),
// shared by every compiled rule in the output file; each rule contributes
// only its own flat `fx_instr[]` array.
// A tiny Thompson-NFA bytecode. CodeGenerator subset-constructs a DFA from
// the rules' programs; those with trailing context also keep theirs in the
// output, where a Pike VM finds the split inside a finished match.
enum class Op { Char, Class, Any, Bol, Split, Jmp, Mark, Match };

struct Instr {
    Op op;
    int x = 0, y = 0; // Char: x=byte value. Class: x=class table index. Split: x,y=targets. Jmp: x=target.
    int x = 0, y = 0; // Char: x=byte value. Class: x=class table index. Any: x=matches newline too. Split: x,y=targets. Jmp: x=target. Match: x=rule.
};

struct CompiledProgram {
    std::vector<Instr> code;
    bool isEofRule = false;
    bool hasTrailing = false;
};

// Parses one already-isolated pattern token's text (e.g. "[ \t]*\n/[^ \t\n]")
@@ -219,7 +230,7 @@ class PatternCompiler {
public:
    // `defs` is LexSpec::defs (name -> raw pattern text, for {name}
    // expansion); `errors` collects diagnostics the same way LexReader does.
    PatternCompiler(const std::map<std::string,std::string> &defs, std::vector<std::string> &errors);
    PatternCompiler(const std::map<std::string,std::string> &defs, std::vector<std::string> &errors, bool caseless);

    // Parses `text` (no surrounding whitespace) into `out`. Returns false on
    // a malformed pattern (records an error).
@@ -243,6 +254,8 @@ private:
    size_t p_ = 0;
    std::string context_;
    bool failed_ = false;
    bool defaultCaseless_ = false;
    bool caseless_ = false, dotAll_ = false, freeSpacing_ = false; // (?i (?s (?x

    void failHere(const std::string &msg);
    char cpeek(size_t ahead=0) const;
@@ -256,6 +269,10 @@ private:
    ReNode *parseBracketClass();
    ReNode *parseQuotedString();
    ReNode *parseNamedDef();
    ReNode *parseGroup();
    ReNode *makeChar(unsigned char ch);
    void foldCase(CharClass &cc);
    void skipFreeSpacing();
    unsigned char parseEscapedChar(); // consumes a backslash escape, returns the resulting byte
    int internClass(CharClass &&cc);
    void addPosixClass(CharClass &cc, const std::string &name); // "alpha","digit","blank", etc.
@@ -273,6 +290,7 @@ public:
    // Returns the generated .c file content, or "" if a pattern failed to
    // compile (check errors() in that case).
    std::string generateSource();
    std::string generateHeader();           // for --header-file
    const std::vector<std::string> &errors() const { return errors_; }

private: