sourceafPegger::Grammar.fan


** (Advanced) 
** Use to programmatically define PEG rules.
** 
** Rules may be used before they, themselves are defined:
** 
**   syntax: fantom
**   grammar := Grammar()
**   grammar["a"] := Rules.sequence { grammar["b"], grammar["c"], }
**   grammar["b"] := Rules.zeroOrMore(Rules.spaceChar)
**   grammar["c"] := Rules.oneOrMore(Rules.alphaNumChar)
** 
** Sometimes it's easier to hold rules in local variables:
** 
**   syntax: fantom
**   grammar := Grammar()
**   
**   a            := grammar["a"]
**   b            := grammar["b"]
**   c            := grammar["c"]
** 
**   grammar["a"] := Rules.sequence { b, c, }
**   grammar["b"] := Rules.zeroOrMore(Rules.spaceChar)
**   grammar["c"] := Rules.oneOrMore(Rules.alphaNumChar)
** 
** All named rules must be defined before the grammar is used.
** 
** It is not mandatory to use the 'Grammar' class to create rules and grammars, but it is usually easier.
@Js
class Grammar {
    private Str:Rule        _rules      := Str:Rule[:]      { it.ordered = true } 
    private Str:ProxyRule   _proxies    := Str:ProxyRule[:] { it.ordered = true }
    
    ** Parses grammar definitions, and returns the root rule (if given) or the first rule parsed.
    ** For example:
    ** 
    **   syntax: fantom 
    **   Grammar.fromDefs("a <- [abc] / [xyz] / b
    **                     b <- \space+ [^abc]")
    ** 
    ** See `Peg.parseGrammar`
    static Grammar parseGrammar(Str grammar) {
        PegGrammar().parseGrammar(grammar)
    }
    
    ** Returns the first declared rule.
    @NoDoc  // only really used / needed for automation / testing 
    Rule firstRule() {
        _rules[_rules.keys.first]
    }
    
    ** Returns the named rule, or creates a proxy if it doesn't exist.
    @Operator
    Rule get(Str name) {
        if (_rules.containsKey(name))
            return _rules[name]
        if (_proxies.containsKey(name))
            return _proxies[name]
        _proxies[name] = ProxyGrammarRule(this, name) { it.name = name }
        return _proxies[name]
    }
    
    ** Sets the real implementation / definition of the named rule.
    @Operator
    Rule set(Str name, Rule rule) {
        if (rule.name != null && rule isnot RuleRef)
            throw Err("Rule '${rule.name}' is already a definition (${rule.typeof})")
        if (rule.label != null && rule isnot RuleRef)
            throw Err("Definitions can NOT have labels: ${name}:${rule.label}")
        if (_rules.containsKey(name))
            throw Err("Definition already defined: ${name}")
        
        rule.name = name
        _rules[name] = rule
        return rule
    }
    
    ** Returns all the named rules.
    Rule[] rules() {
        validate._rules.vals
    }
    
    ** Pretty prints the grammar definition.
    Str definition() {
        validate
        max := (Int) _rules.vals.reduce(0) |Int max, rule| {
            nameSize := rule.name.size
            if (!rule.useInResult)  nameSize++
            if (!rule.debug)        nameSize++
            return max.max(nameSize)
        }
        buf := StrBuf()
        _rules.each |rule| {
            name := rule.name
            if (!rule.useInResult)  name = "-" + name
            if (!rule.debug)        name = name + "-"
            buf.add(name.justl(max)).add(" <- ").add(rule.expression).addChar('\n')
        }
        return buf.toStr
    }
    
    ** Dumps the definition to std-out.
    @NoDoc
    This dump(OutStream out := Env.cur.out) {
        out.printLine(debugStr)
        return this
    }
    
    ** Returns the definition.
    @NoDoc
    Str debugStr() {
        definition
    }
    
    ** Validates that all named rules have been defined. 
    ** 
    ** After validation, Grammars and their Rules should be thread safe - use 'Unsafe()' if required.
    This validate() {
        _proxies.vals.each { it.rule(true) }
        // no need to warn of un-used rules - it may be irritating
        return this
    }
    
    internal Rule? getForReal(Str name, Bool checked) {
        _rules[name] ?: (checked ? throw ArgNotFoundErr("Rule not defined in grammar: $name", _rules.keys) : null)
    }
    
    @NoDoc
    override Str toStr() { debugStr }
}

@Js
internal abstract class ProxyRule : Rule {
    private Str?    _name
    private Str?    _label
    private Bool?   _debug
    private Bool?   _useInResult
    
    override Str? name {
        get { rule?.name ?: _name}
        set { if (rule == null) _name = it; else rule.name = it }
    }

    override Str? label {
        get { rule?.label ?: _label}
        set { throw Err("ProxyRules (${name}) should NOT have their label set: $it") }
    }
    
    override Bool debug {
        get { rule?.debug ?: _debug }
        set { if (rule == null) _debug = it; else rule.debug = it }
    }

    override Bool useInResult {
        get { rule?.useInResult ?: _useInResult }
        set { if (rule == null) _useInResult = it; else rule.useInResult = it }
    }

    override Bool doProcess(RuleCtx ctx) {
        rule(true).doProcess(ctx)
    }
    
    override Str _expression() {
        rule(true)._expression
    }
    
    override Str toStr() {
        rule(true).toStr
    }
    
    abstract Rule? rule(Bool checked := false)
    
    Void onRule(Rule rule) {
        if (_name        != null)   rule.name        = _name
        if (_label       != null)   rule.label       = _label
        if (_debug       != null)   rule.debug       = _debug
        if (_useInResult != null)   rule.useInResult = _useInResult
    }
    
    Bool hasIdentityCrisis(Rule realRule) {
        // labels should NOT be inherited
        if (realRule.label != null)
            return true
        // names are allowed to be the same
        if (realRule.name  != null && realRule.name != _name)
            return true

        // phew! we know who we are!
        return false
    }
}

@Js
internal class ProxyGrammarRule : ProxyRule {
    private Grammar     rules
    private Rule?       realRule
    private const Str   realName
    
    new make(Grammar rules, Str? realName) {
        // let the actual name be whatever, just don't change who we point to!
        this.realName   = realName
        this.rules      = rules
    }
    
    override Rule? rule(Bool checked := false) {
        if (realRule == null) {
            realRule = rules.getForReal(realName, checked)
            if (realRule != null) {
                
                if (hasIdentityCrisis(realRule))
                    realRule = SequenceRule([realRule])
                
                onRule(realRule)
            }
        }
        return realRule
    }
}

** A simple implementation of `NotFoundErr`.
** This class can not extend 'ArgErr' due to [Cannot extend sys Errs in Javascript]`http://fantom.org/forum/topic/2468`.
@Js
internal const class ArgNotFoundErr : Err {
    const Str?[] availableValues
    
    new make(Str msg, Obj?[] availableValues, Err? cause := null) : super.make(msg, cause) {
        this.availableValues = availableValues.map { it?.toStr }.sort
    }
    
    ** Pre-pends the list of available values to the stack trace.
    override Str toStr() {
        buf := StrBuf()
        buf.add("${typeof.qname}: ${msg}\n")
        buf.add("\nAvailable values:\n")
        availableValues.each { buf.add("  $it\n")}
        buf.add("\nStack Trace:")
        return buf.toStr
    }
}