// Copyright (c) 2025 Redglyph (@gmail.com). All Rights Reserved.

use std::io::Write;
use lexigram_lib::file_utils::{get_tagged_source, replace_tagged_source, SrcTagError};
use lexigram_lib::lexergen::LexigramCrate;
use lexigram_lib::parsergen::NTValue;
/// Action performed by the source generator: generates the source or verifies it
/// (verification is only possible with some [CodeLocation] options).
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Action { Generate, Verify }

/// Specification of the lexer or parser (lexicon or grammar content or location)
#[derive(Clone, PartialEq, Debug)]
pub enum Specification {
    /// No source
    None,
    /// Source is in a string
    String(String),
    /// Source is an existing file
    File { filename: String },
    /// Source is between tags in an existing file
    FileTag { filename: String, tag: String },
}

impl Specification {
    pub fn is_none(&self) -> bool {
        self == &Specification::None
    }

    pub fn get_type(&self) -> String {
        match self {
            Specification::None => "no content".to_string(),
            Specification::String(_) => "String text".to_string(),
            Specification::File { filename } => format!("file '{filename}'"),
            Specification::FileTag { filename, tag } => format!("file '{filename}' / tag '{tag}'"),
        }
    }

    pub fn get(self) -> Result<Option<String>, SrcTagError> {
        match self {
            Specification::None => Ok(None),
            Specification::String(s) => Ok(Some(s)),
            Specification::File { filename } => Ok(Some(std::fs::read_to_string(filename)?)),
            Specification::FileTag { filename, tag } => get_tagged_source(&filename, &tag).map(Some),
        }
    }
}

/// Location of the code to be generated or verified
#[derive(Clone, PartialEq, Debug)]
pub enum CodeLocation {
    /// * Generate mode: doesn't write anything.
    /// * Verify mode: returns `None`.
    None,
    /// * Generate mode: creates a new file or overwrites an existing one.
    /// * Verify mode: reads the expected code from an existing file.
    File { filename: String },
    /// * Generate mode: inserts the code between tags into an existing file.
    /// * Verify mode: reads the expected code between tags of an existing file.
    FileTag { filename: String, tag: String },
    /// * Generate mode: writes the code to stdout.
    /// * not a valid option in verify mode
    StdOut,
}

impl CodeLocation {
    pub fn is_none(&self) -> bool {
        self == &CodeLocation::None
    }

    pub fn get_type(&self) -> String {
        match self {
            CodeLocation::None => "no content".to_string(),
            CodeLocation::File { filename } => format!("file '{filename}'"),
            CodeLocation::FileTag { filename, tag } => format!("file '{filename}' / tag '{tag}'"),
            CodeLocation::StdOut => "stdout".to_string(),
        }
    }

    pub fn read(&self) -> Result<Option<String>, SrcTagError> {
        match self {
            CodeLocation::None => Ok(None),
            CodeLocation::File { filename } => Ok(Some(std::fs::read_to_string(filename)?)),
            CodeLocation::FileTag { filename, tag } => get_tagged_source(filename, tag).map(Some),
            CodeLocation::StdOut => {
                Err(SrcTagError::Io(std::io::Error::new(std::io::ErrorKind::InvalidInput, "stdout can only be used as output")))
            }
        }
    }

    pub fn write(&self, source: &str) -> Result<(), SrcTagError> {
        match self {
            CodeLocation::None => Ok(()),
            CodeLocation::File { filename } => {
                Ok(std::fs::write(filename, source)?)
            }
            CodeLocation::FileTag { filename, tag } => replace_tagged_source(filename, tag, source),
            CodeLocation::StdOut => {
                Ok(std::io::stdout().write_all(source.as_bytes())?)
            }
        }
    }
}

// ---------------------------------------------------------------------------------------------

/// Options used to generate the source code of the lexer, parser, wrapper, and listener from a lexicon and a grammar
/// (the grammar is optional if only the lexer must be generated).
///
/// See [OptionsBuilder] for the accompanying builder.
#[derive(Clone, PartialEq, Debug)]
pub struct Options {
    /// Specification of the lexer (lexicon)
    pub lexer_spec: Specification,
    /// Location of the generated/verified lexer code
    pub lexer_code: CodeLocation,
    /// Indentation of lexer source code
    pub lexer_indent: usize,
    /// Specification of the parser (grammar)
    pub parser_spec: Specification,
    /// Location of the generated/verified parser code
    pub parser_code: CodeLocation,
    /// Indentation of parser source code
    pub parser_indent: usize,
    /// Extra headers before the lexer code
    pub lexer_headers: Vec<String>,
    /// Custom headers to insert before the parser code
    pub parser_headers: Vec<String>,
    /// Custom `use` libraries to include in the parser code (only if `parser_code` isn't `None`)
    pub libs: Vec<String>,
    /// Name of the start nonterminal, which defaults to the first one defined in the grammar.
    ///
    /// Default: `None`.
    pub start_nt: Option<String>,
    /// Includes the definitions of the alternatives in the parser, for debugging purposes
    ///
    /// Default: `false`
    pub gen_parser_alts: bool,
    /// Generates the wrapper, which is necessary to interface a listener (only if `parser_code` isn't `None`)
    ///
    /// Default: `true`
    pub gen_wrapper: bool,
    /// Generates the span parameters in the listener methods, to get the position of the terminals/nonterminals (only if `gen_wrapper` is `true`)
    ///
    /// Default: `false`
    pub gen_span_params: bool,
    /// Generates enums for the terminal and nonterminal values.
    pub gen_token_enums: bool,
    /// Uses the full library instead of the core library in the generated code. Use this option for a lexer / parser that
    /// needs `lexigram_lib` instead of the smaller `lexigram_core` crate.
    ///
    /// Default: `false`
    pub lib_crate: LexigramCrate,
    /// Sets the nonterminals that have a value.
    pub nt_value: NTValue,
    /// Location of the generated template for the user types
    pub types_code: CodeLocation,
    /// Indentation of the generated template for the user types
    pub types_indent: usize,
    /// Location of the template for the listener implementation
    pub listener_code: CodeLocation,
    /// Indentation of the template for the listener implementation
    pub listener_indent: usize,
}

impl Options {
    pub fn new() -> Self {
        Self::default()
    }

    fn has_lexer_spec(&self) -> bool {
        self.lexer_spec != Specification::None
    }

    fn has_lexer_code(&self) -> bool {
        self.lexer_code != CodeLocation::None
    }

    fn has_parser_spec(&self) -> bool {
        self.parser_spec != Specification::None
    }

    fn has_parser_code(&self) -> bool {
        self.parser_code != CodeLocation::None
    }

    fn has_types_code(&self) -> bool {
        self.types_code != CodeLocation::None
    }

    fn has_listener_code(&self) -> bool {
        self.listener_code != CodeLocation::None
    }
}

impl Default for Options {
    fn default() -> Self {
        Options {
            lexer_spec: Specification::None,
            lexer_code: CodeLocation::None,
            lexer_indent: 0,
            parser_spec: Specification::None,
            parser_code: CodeLocation::None,
            parser_indent: 0,
            lexer_headers: vec![],
            parser_headers: vec![],
            libs: vec![],
            start_nt: None,
            gen_parser_alts: false,
            gen_wrapper: true,
            gen_span_params: false,
            gen_token_enums: false,
            lib_crate: LexigramCrate::Core,
            nt_value: NTValue::Default,
            types_code: CodeLocation::None,
            types_indent: 0,
            listener_code: CodeLocation::None,
            listener_indent: 0,
        }
    }
}

#[derive(Clone, Copy, PartialEq, Debug)]
enum BuilderState { Start, Lexer, Parser, Types, Listener, Error }

/// Builder of the [Options] object.
///
/// There are 3 types of options:
/// * options related to the lexer: [lexer](OptionsBuilder::lexer), [indent](OptionsBuilder::indent), [headers](OptionsBuilder::headers)
/// * options related to the parser: [parser](OptionsBuilder::parser), [indent](OptionsBuilder::indent), [headers](OptionsBuilder::headers)
/// * general options: [libs](OptionsBuilder::libs), [parser_alts](OptionsBuilder::parser_alts),
///   [wrapper](OptionsBuilder::wrapper), [span_params](OptionsBuilder::span_params)
///
/// Initially, the default option settings corresponds to [Options]'s defaults. The builder offers a convenient way to chain method
/// in order to set custom options.
///
/// The builder offers a way to use some of those options differently, depending on their position in the method chain.
/// The options [indent](OptionsBuilder::indent) and [headers](OptionsBuilder::headers) are:
/// * applied to both the lexer and parser if placed before [lexer](OptionsBuilder::lexer) and [parser](OptionsBuilder::parser)
/// * applied to the lexer if placed after [lexer](OptionsBuilder::lexer) and before [parser](OptionsBuilder::parser)
/// * applied to the parser if placed after [parser](OptionsBuilder::parser).
///
/// The option [parser](OptionsBuilder::parser) mustn't be used before [lexer](OptionsBuilder::lexer).
///
/// The [build](OptionsBuilder::build) method creates the resulting [Options] object. It doesn't check the object is properly set, however,
/// since the user may want to modify it later. For instance, it doesn't verify that the lexer is defined as something else than 'none'.
/// The current configuration is cleared after that point, and a new object can be created again with the same builder.
///
/// The [options](OptionsBuilder::options) method moves the builder to create the resulting [Options] object, so it can't be reused
/// (unless cloned before callind the method).
#[derive(Clone, Debug)]
pub struct OptionsBuilder {
    options: Options,
    state: BuilderState,
    message: Option<String>,
}

pub(crate) static ERR_LEXER_SPEC_ALREADY_SET: &str = "lexer lexicon specification already set";
pub(crate) static ERR_LEXER_CODE_ALREADY_SET: &str = "lexer code location already set";
pub(crate) static ERR_COMBINED_SPEC_GIVEN_TOO_LATE: &str = "combined lexicon/grammar specification set after other lexer/parser options";
pub(crate) static ERR_COMBINED_SPEC_ALREADY_SET: &str = "combined lexicon/grammar specification already set";
pub(crate) static ERR_LEXER_AFTER_PARSER: &str = "lexer option set after parser options";
pub(crate) static ERR_LEXER_AFTER_TEMPLATES: &str = "lexer option set after template options";
pub(crate) static ERR_LEXER_SPEC_OR_CODE_ALREADY_SET: &str = "lexer code location and/or specification already set";
pub(crate) static ERR_PARSER_SET_BEFORE_LEXER_NOT_SET: &str = "parser option set before any lexer options has been set";
pub(crate) static ERR_MISSING_LEXER_OPTION: &str = "lexer is missing option(s)";
pub(crate) static ERR_PARSER_SPEC_ALREADY_SET: &str = "parser grammar specifications already set";
pub(crate) static ERR_PARSER_AFTER_TEMPLATES: &str = "parser option set after template options";
pub(crate) static ERR_PARSER_CODE_ALREADY_SET: &str = "parser code location already set";
pub(crate) static ERR_PARSER_SPEC_OR_CODE_ALREADY_SET: &str = "parser code location and/or specification already set";
pub(crate) static ERR_MISSING_PARSER_OPTION: &str = "parser is missing option(s)";
pub(crate) static ERR_MISSING_PARSER_CODE: &str = "parser code isn't set yet";
pub(crate) static ERR_TYPES_CODE_ALREADY_SET: &str = "location of template code for types already set";
pub(crate) static ERR_LISTENER_CODE_ALREADY_SET: &str = "location of template code for listener implementation already set";


impl OptionsBuilder {
    /// Creates a builder for [Options]
    pub fn new() -> Self {
        OptionsBuilder { state: BuilderState::Start, options: Options::new(), message: None }
    }

    /// Checks if the builder has encountered an error
    pub fn has_error(&self) -> bool {
        self.state == BuilderState::Error
    }

    /// Gets the current error message, if any
    pub fn get_error_message(&self) -> Option<&str> {
        self.message.as_deref()
    }

    pub fn reset(&mut self) {
        self.state = BuilderState::Start;
        self.message = None;
        self.options = Options::new();
    }

    fn set_error(&mut self, method: &str, message: &str) {
        if self.state != BuilderState::Error {
            self.state = BuilderState::Error;
            self.message = Some(format!("{}{}", method, message));
        }
    }

    /// Sets the location of the combined lexicon and grammar specification (default is none)
    pub fn combined_spec(&mut self, combined_spec: Specification) -> &mut Self {
        match self.state {
            BuilderState::Start => {
                if !self.options.has_lexer_spec() {
                    self.options.lexer_spec = combined_spec.clone();
                    self.options.parser_spec = combined_spec;
                } else {
                    self.set_error("combined spec: ", ERR_COMBINED_SPEC_ALREADY_SET);
                }
            }
            BuilderState::Lexer | BuilderState::Parser | BuilderState::Types | BuilderState::Listener => {
                self.set_error("combined spec: ", ERR_COMBINED_SPEC_GIVEN_TOO_LATE);
            }
            BuilderState::Error => {}
        }
        self
    }

    /// Sets the location of the lexer's lexicon specification (default is none)
    pub fn lexer_spec(&mut self, lexer_spec: Specification) -> &mut Self {
        match self.state {
            BuilderState::Start | BuilderState::Lexer => {
                if !self.options.has_lexer_spec() {
                    self.state = BuilderState::Lexer;
                    self.options.lexer_spec = lexer_spec;
                } else {
                    self.set_error("lexer spec: ", ERR_LEXER_SPEC_ALREADY_SET);
                }
            }
            BuilderState::Parser => {
                self.set_error("lexer spec: ", ERR_LEXER_AFTER_PARSER);
            }
            BuilderState::Types | BuilderState::Listener => {
                self.set_error("lexer spec: ", ERR_LEXER_AFTER_TEMPLATES);
            }
            BuilderState::Error => {}
        }
        self
    }

    /// Sets the location of the lexer's generated code (default is none)
    pub fn lexer_code(&mut self, lexer_code: CodeLocation) -> &mut Self {
        match self.state {
            BuilderState::Start | BuilderState::Lexer => {
                if !self.options.has_lexer_code() {
                    self.state = BuilderState::Lexer;
                    self.options.lexer_code = lexer_code;
                } else {
                    self.set_error("lexer code: ", ERR_LEXER_CODE_ALREADY_SET);
                }
            }
            BuilderState::Parser => {
                self.set_error("lexer code: ", ERR_LEXER_AFTER_PARSER);
            }
            BuilderState::Types | BuilderState::Listener => {
                self.set_error("lexer code: ", ERR_LEXER_AFTER_TEMPLATES);
            }
            BuilderState::Error => {}
        }
        self
    }

    /// Sets the location of the lexer's lexicon specification and generated code (default is none for both)
    pub fn lexer(&mut self, lexer_spec: Specification, lexer_code: CodeLocation) -> &mut Self {
        match self.state {
            BuilderState::Start if !self.options.has_lexer_spec() && !self.options.has_lexer_code() => {
                self.state = BuilderState::Lexer;
                self.options.lexer_spec = lexer_spec;
                self.options.lexer_code = lexer_code;
            }
            BuilderState::Start | BuilderState::Lexer => {
                self.set_error("lexer: ", ERR_LEXER_SPEC_OR_CODE_ALREADY_SET);
            }
            BuilderState::Parser => {
                self.set_error("lexer: ", ERR_LEXER_AFTER_PARSER);
            }
            BuilderState::Types | BuilderState::Listener => {
                self.set_error("lexer: ", ERR_LEXER_AFTER_TEMPLATES);
            }
            BuilderState::Error => {}
        }
        self
    }

    /// Sets the location the parser's grammar specification (default is none)
    pub fn parser_spec(&mut self, parser_spec: Specification) -> &mut Self {
        match self.state {
            BuilderState::Start => {
                self.set_error("parser spec: ", ERR_PARSER_SET_BEFORE_LEXER_NOT_SET);
            }
            BuilderState::Lexer | BuilderState::Parser => {
                if !self.options.has_parser_spec() {
                    self.state = BuilderState::Parser;
                    self.options.parser_spec = parser_spec;
                } else {
                    self.set_error("parser spec: ", ERR_PARSER_SPEC_ALREADY_SET);
                }
            }
            BuilderState::Types | BuilderState::Listener => {
                self.set_error("parser specs: ", ERR_PARSER_AFTER_TEMPLATES);
            }
            BuilderState::Error => {}
        }
        self
    }

    /// Sets the location the parser's generated code (default is none)
    pub fn parser_code(&mut self, parser_code: CodeLocation) -> &mut Self {
        match self.state {
            BuilderState::Start => {
                self.set_error("parser code: ", ERR_PARSER_SET_BEFORE_LEXER_NOT_SET);
            }
            BuilderState::Lexer | BuilderState::Parser => {
                if !self.options.has_parser_code() {
                    self.state = BuilderState::Parser;
                    self.options.parser_code = parser_code;
                } else {
                    self.set_error("parser code: ", ERR_PARSER_CODE_ALREADY_SET);
                }
            }
            BuilderState::Types | BuilderState::Listener => {
                self.set_error("parser code: ", ERR_PARSER_AFTER_TEMPLATES);
            }
            BuilderState::Error => {}
        }
        self
    }

    /// Sets the location the parser's grammar specification and generated code (default is none for both)
    pub fn parser(&mut self, parser_spec: Specification, parser_code: CodeLocation) -> &mut Self {
        match self.state {
            BuilderState::Start => {
                self.set_error("parser: ", ERR_PARSER_SET_BEFORE_LEXER_NOT_SET);
            }
            BuilderState::Lexer if !self.options.has_parser_spec() && !self.options.has_parser_code() => {
                self.state = BuilderState::Parser;
                self.options.parser_spec = parser_spec;
                self.options.parser_code = parser_code;
            }
            BuilderState::Lexer | BuilderState::Parser => {
                self.set_error("parser: ", ERR_PARSER_SPEC_OR_CODE_ALREADY_SET);
            }
            BuilderState::Types | BuilderState::Listener => {
                self.set_error("parser: ", ERR_PARSER_AFTER_TEMPLATES);
            }
            BuilderState::Error => {}
        }
        self
    }

    /// Sets the location the generated template for the user types (default is none)
    pub fn types_code(&mut self, types_code: CodeLocation) -> &mut Self {
        match self.state {
            BuilderState::Start | BuilderState::Lexer | BuilderState::Parser
            | BuilderState::Types | BuilderState::Listener => {
                if !self.options.has_parser_code() {
                    self.set_error("types code: ", ERR_MISSING_PARSER_CODE);
                } else if !self.options.has_types_code() {
                    self.state = BuilderState::Types;
                    self.options.types_code = types_code;
                } else {
                    self.set_error("types code: ", ERR_TYPES_CODE_ALREADY_SET);
                }
            }
            BuilderState::Error => {}
        }
        self
    }

    /// Sets the location the generated template for the listener implementation (default is none)
    pub fn listener_code(&mut self, listener_code: CodeLocation) -> &mut Self {
        match self.state {
            BuilderState::Start | BuilderState::Lexer | BuilderState::Parser
            | BuilderState::Types | BuilderState::Listener => {
                if !self.options.has_parser_code() {
                    self.set_error("listener code: ", ERR_MISSING_PARSER_CODE);
                } else if !self.options.has_listener_code() {
                    self.state = BuilderState::Listener;
                    self.options.listener_code = listener_code;
                } else {
                    self.set_error("listener code: ", ERR_LISTENER_CODE_ALREADY_SET);
                }
            }
            BuilderState::Error => {}
        }
        self
    }

    /// Sets the indentation of the generated code, in number of space characters (default is 0)
    pub fn indent(&mut self, indent: usize) -> &mut Self {
        match self.state {
            BuilderState::Start => {
                self.options.lexer_indent = indent;
                self.options.parser_indent = indent;
                self.options.types_indent = indent;
                self.options.listener_indent = indent;
            }
            BuilderState::Lexer => self.options.lexer_indent = indent,
            BuilderState::Parser => self.options.parser_indent = indent,
            BuilderState::Types => self.options.types_indent = indent,
            BuilderState::Listener => self.options.listener_indent = indent,
            BuilderState::Error => {}
        }
        self
    }

    /// **Adds** optional headers, which will be placed in front of the code (even before the `use`
    /// clauses). This can be used to place inner attributes like `#![allow(unused)]` or `#![cfg(...)]`.
    ///
    /// This method can be called several times to add more headers.
    pub fn headers<I: IntoIterator<Item=T>, T: Into<String>>(&mut self, headers: I) -> &mut Self {
        let hdr: Vec<String> = headers.into_iter().map(|s| s.into()).collect();
        match self.state {
            BuilderState::Start => {
                self.options.lexer_headers.extend(hdr.clone());
                self.options.parser_headers.extend(hdr);
            }
            BuilderState::Lexer => self.options.lexer_headers.extend(hdr),
            BuilderState::Parser => self.options.parser_headers.extend(hdr),
            BuilderState::Types | BuilderState::Listener => {
                // ignored; no headers in templates
            }
            BuilderState::Error => {}
        }
        self
    }

    /// **Adds** user crates and modules to the list of `use` dependencies for the parser / wrapper.
    /// This can be used to define the user types needed in the wrapper / listener
    /// (those types can be initially copied from the generated code; they're commented out near the
    /// beginning, after the context type definitions).
    ///
    /// This method can be called several times to add more dependencies.
    pub fn libs<I: IntoIterator<Item=T>, T: Into<String>>(&mut self, libs: I) -> &mut Self {
        self.options.libs.extend(libs.into_iter().map(|s| s.into()));
        self
    }

    /// Defines the start nonterminal. The default, if `name_opt` is `None` or if this method isn't called,
    /// is the first one defined in the grammar.
    ///
    /// Default: `None`
    pub fn start_nt<T: Into<String>>(&mut self, name_opt: Option<T>) -> &mut Self {
        self.options.start_nt = name_opt.map(|s| s.into());
        self
    }

    /// Sets the boolean option that generates more explicit debug messages in the parser when a parsing error
    /// is encountered. It requires to generate additional information.
    ///
    /// Default: `false`
    pub fn parser_alts(&mut self, parser_alts: bool) -> &mut Self {
        self.options.gen_parser_alts = parser_alts;
        self
    }

    /// Sets the boolean option that generates the wrapper.
    ///
    /// Default: `true`
    pub fn wrapper(&mut self, wrapper: bool) -> &mut Self {
        self.options.gen_wrapper = wrapper;
        self
    }

    /// Sets the boolean option that generates the extra `span` parameters in the listener callback methods.
    /// These parameters locate the terminals and nonterminals in the source text, so they can be used
    /// for instance to generate report messages with the precise location of symbols that caused an
    /// error.
    ///
    /// Default: `false`
    pub fn span_params(&mut self, span_params: bool) -> &mut Self {
        self.options.gen_span_params = span_params;
        self
    }

    /// Generates enums for the terminal and nonterminal values. They may be helpful in the optional
    /// listener trait methods like `hook()` and `intercept_token()` when they are used.
    ///
    /// # Example
    ///
    /// ```ignore
    /// #[derive(Clone, Copy, PartialEq, Debug)]
    /// #[repr(u16)]
    /// pub enum Term {
    ///     #[doc = "','"]        Comma = 0,
    ///     #[doc = "';'"]        SemiColon = 1,
    /// }
    ///
    /// #[derive(Clone, Copy, PartialEq, Debug)]
    /// #[repr(u16)]
    /// pub enum NTerm {
    ///     #[doc = "`program`"]                   Program = 0,
    ///     #[doc = "`stmt_i`, parent: `program`"] StmtI = 1,
    /// }
    /// ```
    pub fn token_enums(&mut self, token_enums: bool) -> &mut Self {
        self.options.gen_token_enums = token_enums;
        self
    }

    /// Uses the full [lexigram_lib] crate in the generated code if `use_full_lib` is true, or the
    /// smaller [lexigram_core](lexigram_lib::lexigram_core) if false.
    /// Use this option for a lexer / parser that needs to access the code generation features in [lexigram_lib].
    ///
    /// See also [set_crate](OptionsBuilder::set_crate) for a custom name or path.
    ///
    /// ##Example
    ///
    /// ```ignore
    /// let options = OptionsBuilder::new()
    ///     .lexer(genspec!(filename: LEXICON), gencode!(filename: LEXER))
    ///     .parser(genspec!(filename: GRAMMAR), gencode!(filename: PARSER))
    ///     .use_full_lib(false)
    ///     .build()
    ///     .expect("should have no error");
    /// try_gen_parser(action, options)?;
    /// ```
    /// -> `use lexigram_core::parser::Parser;` and so on
    pub fn use_full_lib(&mut self, use_full_lib: bool) -> &mut Self {
        self.options.lib_crate = if use_full_lib { LexigramCrate::Full } else { LexigramCrate::Core };
        self
    }

    /// Sets the `use` path to the lexigram core library.
    ///
    /// See also [use_full_lib](OptionsBuilder::set_crate) for the most common situations.
    ///
    /// ##Example
    ///
    /// ```ignore
    /// use lexigram_core as core;
    ///
    /// let options = OptionsBuilder::new()
    ///     .lexer(genspec!(filename: LEXICON), gencode!(filename: LEXER))
    ///     .parser(genspec!(filename: GRAMMAR), gencode!(filename: PARSER))
    ///     .set_crate(LexigramCrate::Custom("core".to_string()))
    ///     .build()
    ///     .expect("should have no error");
    /// try_gen_parser(action, options)?;
    /// ```
    /// -> `use core::parser::Parser;` and so on
    pub fn set_crate(&mut self, lcrate: LexigramCrate) -> &mut Self {
        self.options.lib_crate = lcrate;
        self
    }

    pub fn set_nt_value(&mut self, nt_value: NTValue) -> &mut Self {
        self.options.nt_value = nt_value;
        self
    }

    fn check_sanity(&mut self) {
        if !self.has_error() {
            if !self.options.has_lexer_spec() || !self.options.has_lexer_code() {
                self.set_error("", ERR_MISSING_LEXER_OPTION)
            } else if self.options.has_parser_spec() ^ self.options.has_parser_code() {
                self.set_error("", ERR_MISSING_PARSER_OPTION)
            }
        }
    }

    /// Creates an [Options] object with the current options defined earlier by [OptionsBuilder]'s methods.
    ///
    /// **The builder resets the options to their default values** after creating and returning that object,
    /// so it can be reused to generate other [Options] objects, but the options must be set again.
    /// If you want the builder to keep the options, consider cloning it and using [options()](OptionsBuilder::options)
    /// instead.
    pub fn build(&mut self) -> Result<Options, String> {
        self.check_sanity();
        let error = self.state == BuilderState::Error;
        let result = if error {
            Err(self.message.take().expect("error without message"))
        } else {
            Ok(std::mem::take(&mut self.options))
        };
        self.reset();
        result
    }

    /// Creates an [Options] object with the current options defined earlier by [OptionsBuilder]'s methods.
    ///
    /// This method moves the builder. If you want to reuse the builder, consider cloning it (if you want
    /// to keep the same options) or using [build()](OptionsBuilder::build) instead.
    ///
    /// **Important note**: once [lexer](OptionsBuilder::lexer) has been called, it's not possible to call
    /// it again. Similarly, once [parser](OptionsBuilder::parser) has been called, it's not possible to
    /// call it or [lexer](OptionsBuilder::lexer) again (see the [Options] doc for further details).
    /// It means that **if you clone the builder and generate an option object with this method, the
    /// remaining builder will only be partially reconfigurable**.
    pub fn options(self) -> Options {
        self.options
    }
}

impl Default for OptionsBuilder {
    fn default() -> Self {
        Self::new()
    }
}

// ---------------------------------------------------------------------------------------------
// Macros

pub mod macros {
    /// Generates a [Specification](crate::options::Specification) object:
    /// ```ignore
    /// genspec!(none)
    /// genspec!(string: expr)
    /// genspec!(filename: expr)
    /// genspec!(filename: expr, tag: expr)
    /// ```
    /// where `expr.to_string()` are valid strings
    #[macro_export]
    macro_rules! genspec {
        (none) => {
            $crate::options::Specification::None
        };
        (string: $string: expr) => {
            $crate::options::Specification::String($string.to_string())
        };
        (filename: $file: expr) => {
            $crate::options::Specification::File { filename: $file.to_string() }
        };
        (filename: $file: expr, tag: $tag: expr) => {
            $crate::options::Specification::FileTag { filename: $file.to_string(), tag: $tag.to_string() }
        };
    }

    /// Generates a [CodeLocation](crate::options::CodeLocation) object:
    /// ```ignore
    /// gencode!(none)
    /// gencode!(string: expr)
    /// gencode!(filename: expr)
    /// gencode!(filename: expr, tag: expr)
    /// ```
    /// where `expr.to_string()` are valid strings
    #[macro_export]
    macro_rules! gencode {
        (none) => {
            $crate::options::CodeLocation::None
        };
        (filename: $file: expr) => {
            $crate::options::CodeLocation::File { filename: $file.to_string() }
        };
        (filename: $file: expr, tag: $tag: expr) => {
            $crate::options::CodeLocation::FileTag { filename: $file.to_string(), tag: $tag.to_string() }
        };
        (stdout) => {
            $crate::options::CodeLocation::StdOut
        };
    }
}
