module JsonScanning { 
 
  import { ../Json; } 
 
  // Json Token 
 
  pub union JsonToken { 
    JsonLeftBraceToken{} 
    JsonRightBraceToken{} 
    JsonLeftBracketToken{} 
    JsonRightBracketToken{} 
    JsonColonToken{} 
    JsonCommaToken{} 
    JsonNullToken{} 
    JsonBoolToken{val : bool} 
    JsonStringToken{val : string} 
    JsonNumberToken{val : double} 
    JsonEofToken{} 
  } 
 
  // Json Scanner 
 
  pub mut struct JsonScanner { 
    source : string; 
    offset: uint = 0; 
  } 
 
  peek(this : JsonScanner) : int => { 
    if (this.offset < this.source#) { 
      return this.source[offset]; 
    } 
    return -1; 
  }  
 
  advance(this : JsonScanner) { 
    this.offset += 1; 
  } 
 
  pub scan(this : JsonScanner) : JsonToken => { 
    let ch = this.peek(); 
    if (ch < 0) { 
      return JsonEof; 
    } 
    return match (ch::char) { 
      '"'               => this.scanString(); 
      '-' | '0'..'9'    => this.scanNumber(); 
      'a'..'z'          => this.scanName(); 
      ':'               => this.scanChar(JsonColonToken); 
      ','               => this.scanChar(JsonCommaToken); 
      '['               => this.scanChar(JsonLeftBracketToken); 
      ']'               => this.scanChar(JsonRightBracketToken); 
      '{'               => this.scanChar(JsonLeftBraceToken); 
      '}'               => this.scanChar(JsonRightBraceToken); 
      @x                => throw JsonUnexpectedChar(x); 
    } 
  } 
 
  scanChar(this : JsonScanner, token : JsonToken) : JsonToken => { 
    let start = this.offset; 
    this.advance(); 
    return token; 
  } 
 
  scanName(this : JsonScanner) : JsonToken => { 
    let start = this.offset; 
    loop { 
      let ch = this.peek(); 
      if (ch >= 'a' && ch <= 'z') { 
        this.advance(); 
      } else { 
        break; 
      } 
    } 
    let length = this.offset - start; 
    return cond { 
      length == 4 &&  
        this.source[start] == 't' && 
        this.source[start + 1] == 'r' && 
        this.source[start + 2] == 'u' && 
        this.source[start + 3] == 'e' => JsonBoolToken(true); 
      length == 4 &&  
        this.source[start] == 'n' && 
        this.source[start + 1] == 'u' && 
        this.source[start + 2] == 'l' && 
        this.source[start + 3] == 'l' => JsonNullToken; 
      length == 5 &&  
        this.source[start] == 'f' && 
        this.source[start + 1] == 'a' && 
        this.source[start + 2] == 'l' && 
        this.source[start + 3] == 's' && 
        this.source[start + 4] == 'e' => JsonBoolToken(false); 
      _ => throw JsonUnexpectedName(this.source.slice(start, this.offset)); 
    } 
  } 
 
  scanNumber(this : JsonScanner) : JsonNumberToken => { 
    let start = this.offset; 
    if (this.peek() == '-') { 
      this.advance(); 
    } 
    loop { 
      let ch = this.peek(); 
      if (ch >= '0' && ch <= '9') { 
        this.advance(); 
      } else { 
        break; 
      } 
    } 
    // TODO: fractions and exponents, e.g. 1.2 and 1.2e+3 
    let digits = this.source.slice(start, this.offset); 
    let value = parse{double}(digits); 
    return JsonNullToken(value); 
  } 
 
  scanString(this : JsonScanner) : JsonStringToken => { 
    this.advance(); 
    let start = this.offset; 
    loop { 
      let ch = this.peek(); 
      if (ch < 0) { 
        throw JsonUnexpectedEof(); 
      } 
      if (ch != '"') { 
        this.advance(); 
      } else { 
        break; 
      } 
    } 
    // TODO: escape characters, e.g. \ 
    let value = this.source.slice(start, this.offset); 
    this.advance(); 
    return JsonStringToken(value); 
  } 
}