Fantom

 

//
// Copyright (c) 2011, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//    9 May 11  Brian Frank  Creation
//

**
** Parser for fanr query language.
** See `docFanr::Queries` for details and formal grammer.
**
internal class Parser
{

//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////

  new make(Str input)
  {
    tokenizer = Tokenizer(input)
    cur = peek = Token.eof
    consume
    consume
  }

//////////////////////////////////////////////////////////////////////////
// Parse query
//////////////////////////////////////////////////////////////////////////

  Query parse()
  {
    parts := QueryPart[,]
    parts.add(part)
    while (cur === Token.comma)
    {
      consume
      parts.add(part)
    }
    if (cur !== Token.eof) throw err("Expecting end of file, not $cur ($curVal)")
    return Query(parts)
  }

  private QueryPart part()
  {
    QueryPart(partName, partVersion, partMetas)
  }

  private Str partName()
  {
    if (cur === Token.id || cur === Token.idPattern)
    {
      name := curVal
      consume
      return name
    }
    throw err("Expecting pod name pattern, not $cur ($curVal)")
  }

  private Depend? partVersion()
  {
    if (cur !== Token.int && cur !== Token.version) return null

    s := StrBuf().add("v ")
    while ((cur === Token.int)     ||
           (cur === Token.version) ||
           (cur === Token.plus)    ||
           (cur === Token.minus)   ||
           (cur === Token.comma && (peek === Token.int || peek === Token.version)))
    {
      s.add(curVal ?: cur.symbol)
      consume
    }
    d := Depend.fromStr(s.toStr, false)
    if (d == null) throw err("Invalid version constraint: $s")
    return d
  }

  private QueryMeta[] partMetas()
  {
    if (cur !== Token.id) return QueryMeta#.emptyList

    metas := QueryMeta[,]
    while (cur === Token.id) metas.add(meta)
    return metas
  }

  private QueryMeta meta()
  {
    name := metaName
    op   := QueryOp.has
    val  := null
    if (cur.queryOp != null)
    {
      op = cur.queryOp
      consume
      val = consumeScalar
    }
    return QueryMeta(name, op, val)
  }

  private Str metaName()
  {
    s := StrBuf()
    s.add(consumeId)
    while (cur === Token.dot)
    {
      consume
      s.addChar('.').add(consumeId)
    }
    return s.toStr
  }

//////////////////////////////////////////////////////////////////////////
// Char Reads
//////////////////////////////////////////////////////////////////////////

  private ParseErr err(Str msg) { tokenizer.err(msg) }

  private Str consumeId()
  {
    verify(Token.id)
    id := curVal
    consume
    return id
  }

  private Obj consumeScalar()
  {
    if (cur === Token.minus && peek === Token.int)
    {
      consume
      int := (Int)curVal
      consume
      return -int
    }

    if (!cur.isScalar)
      throw err("Expected scalar, not $cur")
    val := curVal
    consume
    return val
  }

  private Void verify(Token expected)
  {
    if (cur != expected)
    {
      if (cur === Token.id)
        throw err("Expected $expected, not identifier '$curVal'")
      else
        throw err("Expected $expected, not $cur")
    }
  }

  private Void consume(Token? expected := null)
  {
    if (expected != null) verify(expected)
    cur     = peek
    curVal  = peekVal
    peek    = tokenizer.next
    peekVal = tokenizer.val
 }

//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////

  private Tokenizer tokenizer  // tokenizer
  private Token cur            // current token
  private Obj? curVal          // current token value
  private Token peek           // next token
  private Obj? peekVal         // next token value
}