compiling changes for parser logic! woohoo!

This commit is contained in:
Benjamin Fry 2015-08-31 09:53:34 -07:00
parent 179be5618e
commit 376273f2f2
19 changed files with 231 additions and 54 deletions

View File

@ -16,6 +16,7 @@
use std::error::Error;
use std::fmt;
use std::num;
use std::io;
use super::DecodeError;
use super::LexerError;
@ -26,6 +27,14 @@ pub enum ParseError {
LexerError(LexerError),
DecodeError(DecodeError),
UnrecognizedToken(Token),
OriginIsUndefined,
RecordTypeNotSpecified,
RecordNameNotSpecified,
RecordClassNotSpecified,
RecordTTLNotSpecified,
RecordDataNotSpecified,
SoaAlreadySpecified,
IoError(io::Error),
}
impl fmt::Display for ParseError {
@ -34,6 +43,14 @@ impl fmt::Display for ParseError {
ParseError::LexerError(ref err) => err.fmt(f),
ParseError::DecodeError(ref err) => err.fmt(f),
ParseError::UnrecognizedToken(ref t) => write!(f, "Unrecognized Token in stream: {:?}", t),
ParseError::OriginIsUndefined => write!(f, "$ORIGIN was not specified"),
ParseError::RecordTypeNotSpecified => write!(f, "Record type not specified"),
ParseError::RecordNameNotSpecified => write!(f, "Record name not specified"),
ParseError::RecordClassNotSpecified => write!(f, "Record class not specified"),
ParseError::RecordTTLNotSpecified => write!(f, "Record ttl not specified"),
ParseError::RecordDataNotSpecified => write!(f, "Record data not specified"),
ParseError::SoaAlreadySpecified => write!(f, "SOA is already specified"),
ParseError::IoError(ref err) => err.fmt(f),
}
}
}
@ -43,13 +60,23 @@ impl Error for ParseError {
match *self {
ParseError::LexerError(ref err) => err.description(),
ParseError::DecodeError(ref err) => err.description(),
ParseError::UnrecognizedToken(..) => "Unrecognized Token"
ParseError::UnrecognizedToken(..) => "Unrecognized Token",
ParseError::OriginIsUndefined => "$ORIGIN was not specified",
ParseError::RecordTypeNotSpecified => "Record type not specified",
ParseError::RecordNameNotSpecified => "Record name not specified",
ParseError::RecordClassNotSpecified => "Record class not specified",
ParseError::RecordTTLNotSpecified => "Record ttl not specified",
ParseError::RecordDataNotSpecified => "Record data not specified",
ParseError::SoaAlreadySpecified => "SOA is already specified",
ParseError::IoError(ref err) => err.description(),
}
}
fn cause(&self) -> Option<&Error> {
match *self {
ParseError::LexerError(ref err) => Some(err),
ParseError::DecodeError(ref err) => Some(err),
ParseError::IoError(ref err) => Some(err),
_ => None,
}
}
@ -66,3 +93,9 @@ impl From<DecodeError> for ParseError {
ParseError::DecodeError(err)
}
}
impl From<io::Error> for ParseError {
fn from(err: io::Error) -> ParseError {
ParseError::IoError(err)
}
}

33
src/rr/authority.rs Normal file
View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2015 Benjamin Fry <benjaminfry@me.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use std::collections::HashMap;
use ::serialize::txt::*;
use ::error::*;
use ::rr::{RecordType, Record, Name};
/// Authority is the storage method for all
///
pub struct Authority {
origin: Name,
records: HashMap<(Name, RecordType), Vec<Record>>,
}
impl Authority {
pub fn new(origin: Name, records: HashMap<(Name, RecordType), Vec<Record>>) -> Authority {
Authority{ origin: origin, records: records }
}
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use std::convert::From;
use std::convert::From;
use ::serialize::binary::*;
use ::error::*;

View File

@ -18,7 +18,7 @@ use std::ops::Index;
use ::serialize::binary::*;
use ::error::*;
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct Name {
labels: Vec<String>
}
@ -36,6 +36,32 @@ impl Name {
self.labels.push(label);
self
}
pub fn append(&mut self, other: &Self) -> &mut Self {
for s in &other.labels {
self.add_label(s.to_string());
}
self
}
pub fn parse(local: String, origin: &Option<Self>) -> ParseResult<Self> {
let mut build = Name::new();
// split the local part
// TODO: this should be a real lexer, to varify all data is legal name...
for s in local.split('.') {
if s.len() > 0 {
build.add_label(s.to_string());
}
}
if !local.ends_with('.') {
build.append(try!(origin.as_ref().ok_or(ParseError::OriginIsUndefined)));
}
Ok(build)
}
}
impl BinSerializable for Name {

View File

@ -18,11 +18,13 @@ pub mod dns_class;
pub mod resource;
pub mod record_data;
pub mod domain;
mod authority;
pub use self::record_type::RecordType;
pub use self::resource::Record;
pub use self::domain::Name;
pub use self::dns_class::DNSClass;
pub use self::record_data::RData;
pub use self::authority::Authority;
mod rdata;

View File

@ -65,7 +65,7 @@ pub fn emit(encoder: &mut BinEncoder, a: &RData) -> EncodeResult {
}
}
pub fn parse(tokens: Vec<Token>) -> ParseResult<RData> {
pub fn parse(tokens: &Vec<Token>) -> ParseResult<RData> {
unimplemented!()
}

View File

@ -60,7 +60,7 @@ pub fn emit(encoder: &mut BinEncoder, aaaa: &RData) -> EncodeResult {
}
}
pub fn parse(tokens: Vec<Token>) -> ParseResult<RData> {
pub fn parse(tokens: &Vec<Token>) -> ParseResult<RData> {
unimplemented!()
}

View File

@ -50,7 +50,7 @@ pub fn emit(encoder: &mut BinEncoder, cname_data: &RData) -> EncodeResult {
}
}
pub fn parse(tokens: Vec<Token>) -> ParseResult<RData> {
pub fn parse(tokens: &Vec<Token>) -> ParseResult<RData> {
unimplemented!()
}

View File

@ -57,7 +57,7 @@ pub fn emit(encoder: &mut BinEncoder, mx: &RData) -> EncodeResult {
}
}
pub fn parse(tokens: Vec<Token>) -> ParseResult<RData> {
pub fn parse(tokens: &Vec<Token>) -> ParseResult<RData> {
unimplemented!()
}

View File

@ -57,6 +57,6 @@ pub fn emit(encoder: &mut BinEncoder, ns: &RData) -> EncodeResult {
}
}
pub fn parse(tokens: Vec<Token>) -> ParseResult<RData> {
pub fn parse(tokens: &Vec<Token>) -> ParseResult<RData> {
unimplemented!()
}

View File

@ -60,6 +60,6 @@ pub fn emit(encoder: &mut BinEncoder, nil: &RData) -> EncodeResult {
}
}
pub fn parse(tokens: Vec<Token>) -> ParseResult<RData> {
pub fn parse(tokens: &Vec<Token>) -> ParseResult<RData> {
unimplemented!()
}

View File

@ -50,6 +50,6 @@ pub fn emit(encoder: &mut BinEncoder, ptr: &RData) -> EncodeResult {
}
}
pub fn parse(tokens: Vec<Token>) -> ParseResult<RData> {
pub fn parse(tokens: &Vec<Token>) -> ParseResult<RData> {
unimplemented!()
}

View File

@ -112,6 +112,6 @@ pub fn emit(encoder: &mut BinEncoder, soa: &RData) -> EncodeResult {
}
}
pub fn parse(tokens: Vec<Token>) -> ParseResult<RData> {
pub fn parse(tokens: &Vec<Token>) -> ParseResult<RData> {
unimplemented!()
}

View File

@ -54,6 +54,6 @@ pub fn emit(encoder: &mut BinEncoder, txt: &RData) -> EncodeResult {
}
}
pub fn parse(tokens: Vec<Token>) -> ParseResult<RData> {
pub fn parse(tokens: &Vec<Token>) -> ParseResult<RData> {
unimplemented!()
}

View File

@ -329,7 +329,7 @@ pub enum RData {
}
impl RData {
fn parse(record_type: RecordType, tokens: Vec<Token>) -> ParseResult<Self> {
pub fn parse(record_type: RecordType, tokens: &Vec<Token>) -> ParseResult<Self> {
match record_type {
RecordType::CNAME => rdata::cname::parse(tokens),
RecordType::MX => rdata::mx::parse(tokens),

View File

@ -103,6 +103,7 @@ impl Record {
}
}
pub fn name(&mut self, name: domain::Name) -> &mut Self { self.name_labels = name; self }
pub fn add_name(&mut self, label: String) -> &mut Self { self.name_labels.add_label(label); self }
pub fn rr_type(&mut self, rr_type: RecordType) -> &mut Self { self.rr_type = rr_type; self }
pub fn dns_class(&mut self, dns_class: DNSClass) -> &mut Self { self.dns_class = dns_class; self }

View File

@ -24,6 +24,7 @@ use ::rr::RecordType;
use ::rr::Record;
use ::rr::DNSClass;
use ::rr::RData;
use ::rr::Authority;
use super::master_lex::{Lexer, Token};
@ -125,47 +126,74 @@ use super::master_lex::{Lexer, Token};
//
// ; Semicolon is used to start a comment; the remainder of
// the line is ignored.
pub struct Parser {
records: HashMap<RecordType, Record>,
origin: Option<Name>,
}
pub struct Parser;
impl Parser {
pub fn new() -> Self {
Parser { records: HashMap::new(), origin: None }
Parser
}
pub fn parse(&mut self, file: File) {
let mut lexer = Lexer::with_chars(file.chars());
let mut previous_name: Option<Name> = None;
pub fn parse(&mut self, file: File, origin: Option<Name>) -> ParseResult<Authority> {
let mut records: HashMap<(Name, RecordType), Vec<Record>> = HashMap::new();
let mut buf = String::new();
let mut file = file;
// TODO, this should really use something to read line by line or some other method to
// keep the usage down.
try!(file.read_to_string(&mut buf));
let mut lexer = Lexer::new(&buf);
let mut origin: Option<Name> = origin;
let mut current_name: Option<Name> = None;
let mut rtype: Option<RecordType> = None;
let mut ttl: Option<i32> = None;
let mut class: Option<DNSClass> = None;
let mut state = State::StartLine;
let mut tokens: Vec<Token> = Vec::new();
while let Some(t) = lexer.next_token() {
while let Some(t) = try!(lexer.next_token()) {
state = match state {
State::StartLine => {
// current_name is not reset on the next line b/c it might be needed from the previous
rtype = None;
ttl = None;
class = None;
tokens.clear();
match t {
// if Dollar, then $INCLUDE or $ORIGIN
Token::Dollar("INCLUDE") => unimplemented!(),
Token::Dollar("ORIGIN") => unimplemented!(),
Token::Include => unimplemented!(),
Token::Origin => State::Origin,
// if CharData, then Name then ttl_class_type
Token::CharData(ref data) => unimplemented!(),
Token::CharData(data) => {
current_name = Some(try!(Name::parse(data, &origin)));
State::Ttl_Class_Type
},
// @ is a placeholder for specifying the current origin
Token::At => {
current_name = origin.clone(); // TODO a COW or RC would reduce copies...
State::Ttl_Class_Type
}
// if blank, then nothing or ttl_class_type... grr...
Token::Blank => unimplemented!(),
Token::Blank => {
State::Ttl_Class_Type
},
Token::EOL => State::StartLine, // probably a comment
// _ => return Err(ParseError::UnrecognizedToken(t)),
_ => return Err(ParseError::UnrecognizedToken(t)),
}
},
State::Origin => {
match t {
Token::CharData(data) => {
// TODO an origin was specified, should this be legal? definitely confusing...
origin = Some(try!(Name::parse(data, &None)));
State::StartLine
}
_ => return Err(ParseError::UnrecognizedToken(t)),
}
}
State::Include => unimplemented!(),
State::Ttl_Class_Type => {
match t {
// if number, TTL
@ -173,27 +201,29 @@ impl Parser {
// One of Class or Type (these cannot be overlapping!)
Token::CharData(ref data) => {
// if it's a number it's a ttl
let result = i32::from_str(data);
let result = data.parse();
if result.is_ok() {
if ttl.is_some() { return Err(ParseError::UnrecognizedToken(t.clone())) } // ideally there is no copy in normal usage
ttl = result.ok();
return State::Ttl_Class_Type
State::Ttl_Class_Type
} else {
// if can parse DNSClass, then class
let result = DNSClass::from_str(data);
if result.is_ok() {
class = result.ok();
State::Ttl_Class_Type
} else {
// if can parse RecordType, then RecordType
rtype = Some(try!(RecordType::from_str(data)));
State::Record
}
}
// if can parse DNSClass, then class
let result = DNSClass::from_str(data);
if result.is_ok() {
class = Some(result);
return State::Ttl_Class_Type
}
// if can parse RecordType, then RecordType
rtype = try!(RecordType::from_str(data));
State::Record
}
// could be nothing if started with blank and is a comment, i.e. EOL
Token::EOL => {
State::StartLine // next line
}
},
_ => return Err(ParseError::UnrecognizedToken(t)),
}
},
State::Record => {
@ -206,14 +236,60 @@ impl Parser {
},
State::EndRecord => {
// call out to parsers for difference record types
let RData = try!(RData::parse(tokens));
let rdata = try!(RData::parse(try!(rtype.ok_or(ParseError::RecordTypeNotSpecified)), &tokens));
// verify that we have everything we need for the record
let mut record = Record::new();
// TODO COW or RC would reduce mem usage, perhaps Name should have an intern()...
// might want to wait until RC.weak() stabilizes, as that would be needed for global
// memory where you want
record.name(try!(current_name.clone().ok_or(ParseError::RecordNameNotSpecified)));
record.rr_type(rtype.unwrap());
record.dns_class(try!(class.ok_or(ParseError::RecordClassNotSpecified)));
// slightly annoying, need to grab the TTL, then move rdata into the record,
// then check the Type again and have custom add logic.
match rtype.unwrap() {
RecordType::SOA => {
// TTL for the SOA is set internally...
// expire is for the SOA, minimum is default for records
if let RData::SOA { ref expire, ref minimum, ..} = rdata {
record.ttl(*expire);
ttl = Some(*minimum as i32);
} else { assert!(false, "Invalid RData here, expected SOA: {:?}", rdata); }
},
_ => {
record.ttl(try!(ttl.ok_or(ParseError::RecordTTLNotSpecified)));
},
}
// move the rdata into record...
record.rdata(rdata);
// add to the map
let key = (record.get_name().clone(), record.get_rr_type());
match rtype.unwrap() {
RecordType::SOA => {
if records.insert(key, vec![record]).is_some() {
return Err(ParseError::SoaAlreadySpecified);
}
},
_ => {
// add a Vec if it's not there, then add the record to the list
let mut records = records.entry(key).or_insert(Vec::with_capacity(1));
records.push(record);
},
}
State::StartLine
},
}
}
// get the last one...
//
// build the Authority and return.
Ok(Authority::new(try!(origin.ok_or(ParseError::OriginIsUndefined)), records))
}
}
@ -221,5 +297,7 @@ enum State {
StartLine, // start of line, @, $<WORD>, Name, Blank
Ttl_Class_Type, // [<TTL>] [<class>] <type>,
Record,
Include, // $INCLUDE <filename>
Origin,
EndRecord,
}

View File

@ -2,6 +2,7 @@ use std::cell::{Cell,RefCell};
use std::iter::Peekable;
use std::str::Chars;
use std::char;
use std::fs::File;
use ::error::{LexerResult,LexerError};
@ -13,11 +14,7 @@ pub struct Lexer<'a> {
impl<'a> Lexer<'a> {
pub fn new(txt: &str) -> Lexer {
Self::with_chars(txt.chars())
}
pub fn with_chars(chars: Chars) -> Lexer {
Lexer { txt: chars.peekable(), is_first_line: true, in_list: false }
Lexer { txt: txt.chars().peekable(), is_first_line: true, in_list: false }
}
pub fn next_token(&mut self) -> LexerResult<Option<Token>> {
@ -198,7 +195,7 @@ pub enum State {
EOL, // \n or \r\n
}
#[derive(PartialEq, Debug)]
#[derive(PartialEq, Debug, Clone)]
pub enum Token {
Blank, // only if the first part of the line
StartList, // (

View File

@ -13,8 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use ::error::ParseResult;
mod master_lex;
// mod master;
//
// pub use self::master::Parser;
mod master;
pub use self::master::Parser;
pub use self::master_lex::Lexer;
pub use self::master_lex::Token;
pub trait TxtSerializable {
fn parse(lexer: &mut Lexer) -> ParseResult<Self>;
}