การเขียน lexer ใน C ++


18

แหล่งข้อมูลที่ดีในการเขียน lexer ใน C ++ คืออะไร (หนังสือบทเรียนแบบฝึกหัดเอกสาร) เทคนิคและวิธีปฏิบัติที่ดีมีอะไรบ้าง

ฉันดูบนอินเทอร์เน็ตและทุกคนบอกว่าจะใช้ตัวสร้าง lexer เช่น lex ฉันไม่ต้องการทำอย่างนั้นฉันต้องการเขียน lexer ด้วยมือ


โอเคทำไมไฟแนนเชี่ยลไม่ดีสำหรับวัตถุประสงค์ของคุณ?
CarneyCode

13
ฉันต้องการเรียนรู้ว่า lexers ทำงานอย่างไร ฉันไม่สามารถทำได้ด้วยเครื่องกำเนิด lexer
Rightfold

11
Lex สร้างรหัส C ที่น่ารังเกียจ ใครก็ตามที่ต้องการ lexer ที่ดีไม่ใช้ Lex
DeadMG

5
@Giorgio: รหัสที่สร้างขึ้นเป็นรหัสที่คุณต้องเชื่อมต่อกับที่น่าขยะแขยงตัวแปรทั่วโลกที่ไม่ปลอดภัยเช่นด้ายและมันเป็นรหัสที่มีข้อบกพร่องการเลิกจ้าง NULL คุณแนะนำในใบสมัครของคุณ
DeadMG

1
@Giorgio: คุณเคยมี debug เอาท์พุทโค้ดโดย Lex หรือไม่?
mattnz

คำตอบ:


7

โปรดจำไว้ว่าเครื่องสถานะ จำกัด ทุกตัวนั้นสอดคล้องกับนิพจน์ทั่วไปซึ่งสอดคล้องกับโปรแกรมที่มีโครงสร้างโดยใช้ifและwhileคำสั่ง

ตัวอย่างเช่นในการรับรู้จำนวนเต็มคุณอาจมีเครื่องสถานะ:

0: digit -> 1
1: digit -> 1

หรือการแสดงออกปกติ:

digit digit*

หรือรหัสที่มีโครงสร้าง:

if (isdigit(*pc)){
  while(isdigit(*pc)){
    pc++;
  }
}

โดยส่วนตัวฉันมักจะใช้ lexers เขียนหลังเพราะ IMHO มันไม่ชัดเจนและไม่มีอะไรเร็วขึ้น


ฉันคิดว่าถ้านิพจน์ปกติมีความซับซ้อนมากรหัสที่สอดคล้องกันก็คือ นั่นเป็นสาเหตุที่ lexer generator นั้นดี: ปกติแล้วฉันจะเขียนโค้ด lexer ด้วยตัวเองถ้าภาษานั้นง่ายมาก
Giorgio

1
@Giorgio: อาจเป็นเรื่องของรสนิยม แต่ฉันได้สร้าง parsers มากมายด้วยวิธีนี้ lexer ไม่ต้องจัดการอะไรเกินกว่าตัวเลขเครื่องหมายวรรคตอนคำหลักตัวระบุค่าคงที่สตริงช่องว่างและความคิดเห็น
Mike Dunlavey

ฉันไม่เคยเขียนโปรแกรมแยกวิเคราะห์ที่ซับซ้อนและ lexers และ parsers ทั้งหมดที่ฉันเขียนก็เขียนด้วยมือเช่นกัน ฉันแค่สงสัยว่าขนาดนี้สำหรับภาษาปกติที่ซับซ้อนมากขึ้นได้อย่างไร: ฉันไม่เคยลองเลย แต่ฉันจินตนาการว่าการใช้เครื่องกำเนิดไฟฟ้า (เช่น lex) จะมีขนาดเล็กลง ฉันยอมรับว่าฉันไม่มีประสบการณ์กับ lex หรือผู้สร้างรายอื่นนอกเหนือจากตัวอย่างของเล่น
Giorgio

1
จะมีสตริงที่คุณต่อท้าย*pcใช่ไหม กดwhile(isdigit(*pc)) { value += pc; pc++; }ไลค์ จากนั้นหลังจากที่}คุณแปลงค่าเป็นตัวเลขและกำหนดให้กับโทเค็น
rightfold

@WTP: n = n * 10 + (*pc++ - '0');สำหรับหมายเลขฉันเพียงแค่พวกเขาคำนวณได้ทันทีเพื่อที่คล้ายกัน มันซับซ้อนกว่าเล็กน้อยสำหรับจุดลอยตัวและสัญกรณ์ 'e' แต่ไม่เลว ฉันแน่ใจว่าฉันสามารถบันทึกรหัสเล็กน้อยโดยการบรรจุอักขระลงในบัฟเฟอร์และการโทรatofหรืออะไรก็ตาม มันจะไม่ทำงานเร็วขึ้น
Mike Dunlavey

9

Lexers เป็นเครื่องจักรสถานะ จำกัด ดังนั้นจึงสามารถสร้างได้โดยไลบรารี FSM ที่มีวัตถุประสงค์ทั่วไป อย่างไรก็ตามสำหรับวัตถุประสงค์ของการศึกษาของฉันฉันเขียนด้วยตัวเองโดยใช้เทมเพลตการแสดงออก นี่คือ lexer ของฉัน:

static const std::unordered_map<Unicode::String, Wide::Lexer::TokenType> reserved_words(
    []() -> std::unordered_map<Unicode::String, Wide::Lexer::TokenType>
    {
        // Maps reserved words to TokenType enumerated values
        std::unordered_map<Unicode::String, Wide::Lexer::TokenType> result;

        // RESERVED WORD
        result[L"dynamic_cast"] = Wide::Lexer::TokenType::DynamicCast;
        result[L"for"] = Wide::Lexer::TokenType::For;
        result[L"while"] = Wide::Lexer::TokenType::While;
        result[L"do"] = Wide::Lexer::TokenType::Do;
        result[L"continue"] = Wide::Lexer::TokenType::Continue;
        result[L"auto"] = Wide::Lexer::TokenType::Auto;
        result[L"break"] = Wide::Lexer::TokenType::Break;
        result[L"type"] = Wide::Lexer::TokenType::Type;
        result[L"switch"] = Wide::Lexer::TokenType::Switch;
        result[L"case"] = Wide::Lexer::TokenType::Case;
        result[L"default"] = Wide::Lexer::TokenType::Default;
        result[L"try"] = Wide::Lexer::TokenType::Try;
        result[L"catch"] = Wide::Lexer::TokenType::Catch;
        result[L"return"] = Wide::Lexer::TokenType::Return;
        result[L"static"] = Wide::Lexer::TokenType::Static;
        result[L"if"] = Wide::Lexer::TokenType::If;
        result[L"else"] = Wide::Lexer::TokenType::Else;
        result[L"decltype"] = Wide::Lexer::TokenType::Decltype;
        result[L"partial"] = Wide::Lexer::TokenType::Partial;
        result[L"using"] = Wide::Lexer::TokenType::Using;
        result[L"true"] = Wide::Lexer::TokenType::True;
        result[L"false"] = Wide::Lexer::TokenType::False;
        result[L"null"] = Wide::Lexer::TokenType::Null;
        result[L"int"] = Wide::Lexer::TokenType::Int;
        result[L"long"] = Wide::Lexer::TokenType::Long;
        result[L"short"] = Wide::Lexer::TokenType::Short;
        result[L"module"] = Wide::Lexer::TokenType::Module;
        result[L"dynamic"] = Wide::Lexer::TokenType::Dynamic;
        result[L"reinterpret_cast"] = Wide::Lexer::TokenType::ReinterpretCast;
        result[L"static_cast"] = Wide::Lexer::TokenType::StaticCast;
        result[L"enum"] = Wide::Lexer::TokenType::Enum;
        result[L"operator"] = Wide::Lexer::TokenType::Operator;
        result[L"throw"] = Wide::Lexer::TokenType::Throw;
        result[L"public"] = Wide::Lexer::TokenType::Public;
        result[L"private"] = Wide::Lexer::TokenType::Private;
        result[L"protected"] = Wide::Lexer::TokenType::Protected;
        result[L"friend"] = Wide::Lexer::TokenType::Friend;
        result[L"this"] = Wide::Lexer::TokenType::This;

        return result;
    }()
);

std::vector<Wide::Lexer::Token*> Lexer::Context::operator()(Unicode::String* filename, Memory::Arena& arena) {

    Wide::IO::TextInputFileOpenArguments args;
    args.encoding = Wide::IO::Encoding::UTF16;
    args.mode = Wide::IO::OpenMode::OpenExisting;
    args.path = *filename;

    auto str = arena.Allocate<Unicode::String>(args().AsString());
    const wchar_t* begin = str->c_str();
    const wchar_t* end = str->c_str() + str->size();

    int line = 1;
    int column = 1;

    std::vector<Token*> tokens;

    // Some variables we'll need for semantic actions
    Wide::Lexer::TokenType type;

    auto multi_line_comment 
        =  MakeEquality(L'/')
        >> MakeEquality(L'*')
        >> *( !(MakeEquality(L'*') >> MakeEquality(L'/')) >> eps)
        >> eps >> eps;

    auto single_line_comment
        =  MakeEquality(L'/')
        >> MakeEquality(L'/')
        >> *( !MakeEquality(L'\n') >> eps);

    auto punctuation
        =  MakeEquality(L',')[[&]{ type = Wide::Lexer::TokenType::Comma; }]
        || MakeEquality(L';')[[&]{ type = Wide::Lexer::TokenType::Semicolon; }]
        || MakeEquality(L'~')[[&]{ type = Wide::Lexer::TokenType::BinaryNOT; }]
        || MakeEquality(L'(')[[&]{ type = Wide::Lexer::TokenType::OpenBracket; }]
        || MakeEquality(L')')[[&]{ type = Wide::Lexer::TokenType::CloseBracket; }]
        || MakeEquality(L'[')[[&]{ type = Wide::Lexer::TokenType::OpenSquareBracket; }]
        || MakeEquality(L']')[[&]{ type = Wide::Lexer::TokenType::CloseSquareBracket; }]
        || MakeEquality(L'{')[[&]{ type = Wide::Lexer::TokenType::OpenCurlyBracket; }]
        || MakeEquality(L'}')[[&]{ type = Wide::Lexer::TokenType::CloseCurlyBracket; }]

        || MakeEquality(L'>') >> (
               MakeEquality(L'>') >> (
                   MakeEquality(L'=')[[&]{ type = Wide::Lexer::TokenType::RightShiftEquals; }]
                || opt[[&]{ type = Wide::Lexer::TokenType::RightShift; }]) 
            || MakeEquality(L'=')[[&]{ type = Wide::Lexer::TokenType::GreaterThanOrEqualTo; }]
            || opt[[&]{ type = Wide::Lexer::TokenType::GreaterThan; }])
        || MakeEquality(L'<') >> (
               MakeEquality(L'<') >> (
                      MakeEquality(L'=')[[&]{ type = Wide::Lexer::TokenType::LeftShiftEquals; }]
                   || opt[[&]{ type = Wide::Lexer::TokenType::LeftShift; }] ) 
            || MakeEquality(L'=')[[&]{ type = Wide::Lexer::TokenType::LessThanOrEqualTo; }] 
            || opt[[&]{ type = Wide::Lexer::TokenType::LessThan; }])

        || MakeEquality(L'-') >> (
               MakeEquality(L'-')[[&]{ type = Wide::Lexer::TokenType::Decrement; }]
            || MakeEquality(L'=')[[&]{ type = Wide::Lexer::TokenType::MinusEquals; }]
            || MakeEquality(L'>')[[&]{ type = Wide::Lexer::TokenType::PointerAccess; }]
            || opt[[&]{ type = Wide::Lexer::TokenType::Minus; }])

        || MakeEquality(L'.')
            >> (MakeEquality(L'.') >> MakeEquality(L'.')[[&]{ type = Wide::Lexer::TokenType::Ellipsis; }] 
            || opt[[&]{ type = Wide::Lexer::TokenType::Dot; }])

        || MakeEquality(L'+') >> (  
               MakeEquality(L'+')[[&]{ type = Wide::Lexer::TokenType::Increment; }] 
            || MakeEquality(L'=')[[&]{ type = Wide::Lexer::TokenType::PlusEquals; }]
            || opt[[&]{ type = Wide::Lexer::TokenType::Plus; }])
        || MakeEquality(L'&') >> (
               MakeEquality(L'&')[[&]{ type = Wide::Lexer::TokenType::LogicalAnd; }]
            || MakeEquality(L'=')[[&]{ type = Wide::Lexer::TokenType::BinaryANDEquals; }] 
            || opt[[&]{ type = Wide::Lexer::TokenType::BinaryAND; }])
        || MakeEquality(L'|') >> (
               MakeEquality(L'|')[[&]{ type = Wide::Lexer::TokenType::LogicalOr; }]
            || MakeEquality(L'=')[[&]{ type = Wide::Lexer::TokenType::BinaryOREquals; }]
            || opt[[&]{ type = Wide::Lexer::TokenType::BinaryOR; }])

        || MakeEquality(L'*') >> (MakeEquality(L'=')[[&]{ type = Wide::Lexer::TokenType::MulEquals; }] 
            || opt[[&]{ type = Wide::Lexer::TokenType::Multiply; }])
        || MakeEquality(L'%') >> (MakeEquality(L'=')[[&]{ type = Wide::Lexer::TokenType::ModulusEquals; }] 
            || opt[[&]{ type = Wide::Lexer::TokenType::Modulus; }])
        || MakeEquality(L'=') >> (MakeEquality(L'=')[[&]{ type = Wide::Lexer::TokenType::EqualTo; }] 
            || opt[[&]{ type = Wide::Lexer::TokenType::Assignment; }])
        || MakeEquality(L'!') >> (MakeEquality(L'=')[[&]{ type = Wide::Lexer::TokenType::NotEquals; }] 
            || opt[[&]{ type = Wide::Lexer::TokenType::LogicalNOT; }])
        || MakeEquality(L'/') >> (MakeEquality(L'=')[[&]{ type = Wide::Lexer::TokenType::DivEquals; }] 
            || opt[[&]{ type = Wide::Lexer::TokenType::Divide; }])
        || MakeEquality(L'^') >> (MakeEquality(L'=')[[&]{ type = Wide::Lexer::TokenType::BinaryXOREquals; }] 
            || opt[[&]{ type = Wide::Lexer::TokenType::BinaryXOR; }])
        || MakeEquality(L':') >> (MakeEquality(L'=')[[&]{ type = Wide::Lexer::TokenType::VarAssign; }] 
            || opt[[&]{ type = Wide::Lexer::TokenType::Colon; }]);

    auto string
        =  L'"' >> *( L'\\' >> MakeEquality(L'"') >> eps || !MakeEquality(L'"') >> eps) >> eps;

    auto character
        =  L'\'' >> *( L'\\' >> MakeEquality(L'\'') >> eps || !MakeEquality(L'\'') >> eps);

    auto digit
        =  MakeRange(L'0', L'9');

    auto letter
        =  MakeRange(L'a', L'z') || MakeRange(L'A', L'Z');

    auto number
        =  +digit >> ((L'.' >> +digit) || opt);

    auto new_line
        = MakeEquality(L'\n')[ [&] { line++; column = 0; } ];

    auto whitespace
        =  MakeEquality(L' ')
        || L'\t'
        || new_line
        || L'\n'
        || L'\r'
        || multi_line_comment
        || single_line_comment;

    auto identifier 
        =  (letter || L'_') >> *(letter || digit || (L'_'));
        //=  *( !(punctuation || string || character || whitespace) >> eps );

    bool skip = false;

    auto lexer 
        =  whitespace[ [&]{ skip = true; } ] // Do not produce a token for whitespace or comments. Just continue on.
        || punctuation[ [&]{ skip = false; } ] // Type set by individual punctuation
        || string[ [&]{ skip = false; type = Wide::Lexer::TokenType::String; } ]
        || character[ [&]{ skip = false; type = Wide::Lexer::TokenType::Character; } ]
        || number[ [&]{ skip = false; type = Wide::Lexer::TokenType::Number; } ]
        || identifier[ [&]{ skip = false; type = Wide::Lexer::TokenType::Identifier; } ];

    auto current = begin;
    while(current != end) {
        if (!lexer(current, end)) {
            throw std::runtime_error("Failed to lex input.");
        }
        column += (current - begin);
        if (skip) {
            begin = current;
            continue;
        }
        Token t(begin, current);
        t.columnbegin = column - (current - begin);
        t.columnend = column;
        t.file = filename;
        t.line = line;
        if (type == Wide::Lexer::TokenType::Identifier) { // check for reserved word
            if (reserved_words.find(t.Codepoints()) != reserved_words.end())
                t.type = reserved_words.find(t.Codepoints())->second;
            else
                t.type = Wide::Lexer::TokenType::Identifier;
        } else {
            t.type = type;
        }
        begin = current;
        tokens.push_back(arena.Allocate<Token>(t));
    }
    return tokens;
}

มันได้รับการสนับสนุนโดยไลบราเดอร์, การติดตามกลับ, ไลบรารี่สเตทของเครื่องจักรซึ่งมีความยาวประมาณ 400 บรรทัด แต่มันเป็นเรื่องง่ายที่จะเห็นว่าสิ่งที่ผมต้องทำคือสร้างง่ายการดำเนินงานแบบบูลเช่นand, orและnot, และคู่ของผู้ประกอบการ regex สไตล์เช่น*สำหรับศูนย์หรือมากขึ้นepsหมายถึง "การแข่งขันอะไร" และoptหมายถึง "การจับคู่ อะไรก็ได้ แต่อย่ากิน " ไลบรารีเป็นแบบทั่วไปอย่างสมบูรณ์และขึ้นอยู่กับตัววนซ้ำ สิ่งที่ MakeEquality เป็นการทดสอบอย่างง่ายสำหรับความเท่าเทียมกันระหว่าง*itและค่าที่ส่งผ่านและ MakeRange เป็นการ<= >=ทดสอบอย่างง่าย

ในที่สุดฉันวางแผนที่จะย้ายจากการย้อนรอยไปสู่การทำนาย


2
ฉันเห็น lexers หลายตัวที่เพิ่งอ่านโทเค็นถัดไปเมื่อ parser ร้องขอให้ทำเช่นนั้น คุณดูเหมือนจะผ่านทั้งไฟล์และทำรายการโทเค็น มีวิธีการนี้โดยเฉพาะหรือไม่?
user673679

2
@DeadMG: ต้องการแบ่งปันMakeEqualityข้อมูลหรือไม่ วัตถุที่ส่งคืนโดยฟังก์ชันนั้นโดยเฉพาะ ดูน่าสนใจมาก ๆ
Deathicon

3

ก่อนอื่นมีสิ่งต่าง ๆ เกิดขึ้นที่นี่:

  • แยกรายชื่อตัวละครเปลือยออกเป็นโทเค็น
  • รู้จักโทเค็นเหล่านั้น (การระบุคำหลัก, ตัวอักษร, วงเล็บ, ... )
  • การตรวจสอบโครงสร้างไวยากรณ์ทั่วไป

โดยทั่วไปเราคาดว่า lexer จะทำทั้ง 3 ขั้นตอนในคราวเดียวอย่างไรก็ตามหลังนี้ยากกว่ามากและมีปัญหาบางอย่างเกี่ยวกับระบบอัตโนมัติ (เพิ่มเติมในภายหลัง)

lexer ที่น่าตื่นตาตื่นใจที่สุดที่ฉันรู้คือBoost.Spirit.Qi มันใช้เทมเพลตนิพจน์เพื่อสร้างการแสดงออกของ lexer ของคุณและเมื่อคุ้นเคยกับไวยากรณ์แล้วโค้ดจะรู้สึกเรียบร้อยดี มันรวบรวมช้ามาก (เทมเพลตหนัก) ดังนั้นจึงเป็นการดีที่สุดที่จะแยกส่วนต่าง ๆ ในไฟล์เฉพาะเพื่อหลีกเลี่ยงการคอมไพล์ใหม่เมื่อไม่ได้สัมผัส

มีข้อผิดพลาดบางอย่างในการทำงานของผู้เขียนของคอมไพเลอร์ยุคอธิบายว่าเขามี 1000x ความเร็วขึ้นโปรไฟล์เข้มข้นและการตรวจสอบในวิธีการทำงานของฉีมีและในบทความ

ในที่สุดก็มีรหัสที่สร้างขึ้นโดยเครื่องมือภายนอก (Yacc, Bison, ... )


แต่ฉันสัญญาว่าจะเขียนสิ่งที่ผิดกับการตรวจสอบไวยากรณ์อัตโนมัติ

ตัวอย่างเช่นหากคุณตรวจสอบ Clang คุณจะรู้ว่าแทนที่จะใช้ parser ที่สร้างขึ้นและสิ่งที่เรียกว่า Boost.Spirit พวกเขาจะออกไปตรวจสอบไวยากรณ์ด้วยตนเองโดยใช้เทคนิค Descent Parsing ทั่วไป แน่นอนว่าสิ่งนี้ดูเหมือนว่าย้อนหลัง?

ในความเป็นจริงมีเหตุผลที่ง่ายมาก: การกู้คืนข้อผิดพลาด

ตัวอย่างทั่วไปใน C ++:

struct Immediate { } instanceOfImmediate;

struct Foo {}

void bar() {
}

สังเกตเห็นข้อผิดพลาด? Fooหายไปทางขวากึ่งลำไส้ใหญ่หลังจากการประกาศของ

มันเป็นข้อผิดพลาดทั่วไปและ Clang กู้คืนอย่างเรียบร้อยโดยตระหนักว่ามันหายไปและvoidไม่ใช่ตัวอย่างFooแต่เป็นส่วนหนึ่งของการประกาศครั้งต่อไป หลีกเลี่ยงปัญหานี้อย่างหนักในการวิเคราะห์ข้อความผิดพลาดที่เป็นความลับ

เครื่องมืออัตโนมัติส่วนใหญ่ไม่มีวิธีที่ชัดเจน (อย่างน้อยที่สุด) ในการระบุข้อผิดพลาดที่น่าจะเป็นและวิธีการกู้คืนจากพวกเขา บ่อยครั้งที่การกู้คืนจำเป็นต้องมีการวิเคราะห์วากยสัมพันธ์เล็กน้อยดังนั้นจึงยังไม่ชัดเจน


ดังนั้นจึงมีการแลกเปลี่ยนที่เกี่ยวข้องในการใช้เครื่องมืออัตโนมัติ: คุณได้รับ parser ของคุณอย่างรวดเร็ว แต่มันเป็นมิตรกับผู้ใช้น้อย


3

เนื่องจากคุณต้องการเรียนรู้วิธีการทำงานของ lexers ฉันคิดว่าคุณต้องการทราบว่าเครื่องกำเนิด lexer ทำงานอย่างไร

ตัวสร้าง lexer ใช้ข้อมูลจำเพาะของ lexical ซึ่งเป็นรายการของกฎ (คู่นิพจน์ทั่วไป - โทเค็น) และสร้าง lexer lexer ที่เป็นผลลัพธ์นี้สามารถแปลงสตริงอินพุต (อักขระ) เป็นสตริงโทเค็นตามรายการกฎนี้

วิธีการที่ใช้กันมากที่สุดส่วนใหญ่ประกอบด้วยการเปลี่ยนนิพจน์ปกติเป็นออโต จำกัด (DFA) ที่กำหนดขึ้นผ่านทางออโตเมติก nondeterministic (NFA) บวกกับรายละเอียดเล็กน้อย

คู่มือรายละเอียดของการทำแปลงนี้สามารถพบได้ที่นี่ โปรดทราบว่าฉันยังไม่ได้อ่านด้วยตัวเอง แต่มันก็ค่อนข้างดี นอกจากนี้หนังสือเกี่ยวกับการสร้างคอมไพเลอร์จะมีการเปลี่ยนแปลงนี้ในบทแรก ๆ

หากคุณสนใจสไลด์บรรยายของหลักสูตรในหัวข้อไม่ต้องสงสัยเลยว่าหลักสูตรเหล่านี้มีจำนวน จำกัด ตั้งแต่หลักสูตรการสร้างคอมไพเลอร์ จากมหาวิทยาลัยของฉันคุณสามารถหาภาพนิ่งดังกล่าวที่นี่และที่นี่

มีบางสิ่งที่ไม่ได้ใช้กันทั่วไปใน lexers หรือปฏิบัติในตำรา แต่มีประโยชน์ค่อนข้างมาก:

ประการแรกการจัดการ Unicode นั้นค่อนข้างไม่สำคัญ ปัญหาคืออินพุต ASCII กว้างเพียง 8 บิตซึ่งหมายความว่าคุณสามารถมีตารางการเปลี่ยนแปลงสำหรับทุกสถานะใน DFA ได้อย่างง่ายดายเนื่องจากมีเพียง 256 รายการเท่านั้น อย่างไรก็ตาม Unicode ที่มีความกว้าง 16 บิต (ถ้าคุณใช้ UTF-16) ต้องใช้ตาราง 64k สำหรับทุกรายการใน DFA หากคุณมีไวยากรณ์ที่ซับซ้อนการทำเช่นนี้อาจเริ่มใช้พื้นที่ว่างพอสมควร การกรอกตารางเหล่านี้ก็เริ่มใช้เวลาสักครู่

หรือคุณสามารถสร้างต้นไม้ช่วงเวลา ทรีช่วงอาจมีสิ่งอันดับ ('a', 'z'), ('A', 'Z') ซึ่งเป็นหน่วยความจำที่มีประสิทธิภาพมากกว่าการมีตารางเต็ม หากคุณรักษาช่วงเวลาที่ไม่ทับซ้อนกันคุณสามารถใช้แผนภูมิไบนารีแบบสมดุลเพื่อวัตถุประสงค์นี้ เวลาทำงานเป็นเส้นตรงในจำนวนบิตที่คุณต้องการสำหรับตัวละครทุกตัวดังนั้น O (16) ในกรณี Unicode อย่างไรก็ตามในกรณีที่ดีที่สุดมันมักจะน้อยลงเล็กน้อย

อีกประเด็นหนึ่งคือ lexers ที่สร้างขึ้นตามความเป็นจริงมีประสิทธิภาพเป็นกำลังสองกรณีที่เลวร้ายที่สุด แม้ว่าพฤติกรรมกรณีที่เลวร้ายที่สุดนี้จะไม่ได้เห็นกันโดยทั่วไป แต่ก็อาจกัดคุณ ถ้าคุณทำงานเป็นปัญหาและต้องการที่จะแก้มัน, กระดาษอธิบายถึงวิธีการเพื่อให้บรรลุเส้นเวลาสามารถพบได้ที่นี่

คุณอาจต้องการอธิบายนิพจน์ทั่วไปในรูปแบบสตริงตามที่ปรากฏ อย่างไรก็ตามการแยกคำอธิบายการแสดงออกปกติเหล่านี้ลงใน NFAs (หรืออาจเป็นโครงสร้างกลางซ้ำก่อน) เป็นบิตของปัญหาไก่ไข่ เพื่อแยกคำอธิบายการแสดงออกปกติอัลกอริทึม Shunting Yard เหมาะมาก วิกิพีเดียดูเหมือนว่าจะมีหน้าอย่างกว้างขวางในขั้นตอนวิธี

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.