280 lines
8.8 KiB
Rust
280 lines
8.8 KiB
Rust
|
|
// ====--------------------------------------------------------------------------------------====//
|
|
//
|
|
// Parser for .cton files.
|
|
//
|
|
// ====--------------------------------------------------------------------------------------====//
|
|
|
|
use std::result;
|
|
use std::fmt::{self, Display, Formatter, Write};
|
|
use lexer::{self, Lexer, Token};
|
|
use cretonne::types::{FunctionName, Signature, ArgumentType, ArgumentExtension};
|
|
use cretonne::repr::Function;
|
|
|
|
pub use lexer::Location;
|
|
|
|
/// A parse error is returned when the parse failed.
|
|
#[derive(Debug)]
|
|
pub struct Error {
|
|
pub location: Location,
|
|
pub message: String,
|
|
}
|
|
|
|
impl Display for Error {
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
write!(f, "{}: {}", self.location.line_number, self.message)
|
|
}
|
|
}
|
|
|
|
pub type Result<T> = result::Result<T, Error>;
|
|
|
|
pub struct Parser<'a> {
|
|
lex: Lexer<'a>,
|
|
|
|
lex_error: Option<lexer::Error>,
|
|
|
|
// Current lookahead token.
|
|
lookahead: Option<Token<'a>>,
|
|
|
|
// Location of lookahead.
|
|
location: Location,
|
|
}
|
|
|
|
impl<'a> Parser<'a> {
|
|
/// Create a new `Parser` which reads `text`. The referenced text must outlive the parser.
|
|
pub fn new(text: &'a str) -> Parser {
|
|
Parser {
|
|
lex: Lexer::new(text),
|
|
lex_error: None,
|
|
lookahead: None,
|
|
location: Location { line_number: 0 },
|
|
}
|
|
}
|
|
|
|
/// Parse the entire string into a list of functions.
|
|
pub fn parse(text: &'a str) -> Result<Vec<Function>> {
|
|
Self::new(text).parse_function_list()
|
|
}
|
|
|
|
// Consume the current lookahead token and return it.
|
|
fn consume(&mut self) -> Token<'a> {
|
|
self.lookahead.take().expect("No token to consume")
|
|
}
|
|
|
|
// Get the current lookahead token, after making sure there is one.
|
|
fn token(&mut self) -> Option<Token<'a>> {
|
|
if self.lookahead == None {
|
|
match self.lex.next() {
|
|
Some(Ok(lexer::LocatedToken { token, location })) => {
|
|
self.lookahead = Some(token);
|
|
self.location = location;
|
|
}
|
|
Some(Err(lexer::LocatedError { error, location })) => {
|
|
self.lex_error = Some(error);
|
|
self.location = location;
|
|
}
|
|
None => {}
|
|
}
|
|
}
|
|
return self.lookahead;
|
|
}
|
|
|
|
// Generate an error.
|
|
fn error(&self, message: &str) -> Error {
|
|
Error {
|
|
location: self.location,
|
|
message:
|
|
// If we have a lexer error latched, report that.
|
|
match self.lex_error {
|
|
Some(lexer::Error::InvalidChar) => "invalid character".to_string(),
|
|
None => message.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Match and consume a token without payload.
|
|
fn match_token(&mut self, want: Token<'a>, err_msg: &str) -> Result<Token<'a>> {
|
|
if self.token() == Some(want) {
|
|
Ok(self.consume())
|
|
} else {
|
|
Err(self.error(err_msg))
|
|
}
|
|
}
|
|
|
|
// If the next token is a `want`, consume it, otherwise do nothing.
|
|
fn optional(&mut self, want: Token<'a>) -> bool {
|
|
if self.token() == Some(want) {
|
|
self.consume();
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
/// Parse a list of function definitions.
|
|
///
|
|
/// This is the top-level parse function matching the whole contents of a file.
|
|
pub fn parse_function_list(&mut self) -> Result<Vec<Function>> {
|
|
let mut list = Vec::new();
|
|
while self.token().is_some() {
|
|
list.push(try!(self.parse_function()));
|
|
}
|
|
Ok(list)
|
|
}
|
|
|
|
// Parse a whole function definition.
|
|
//
|
|
// function ::= * function-spec "{" preample function-body "}"
|
|
//
|
|
fn parse_function(&mut self) -> Result<Function> {
|
|
let (name, sig) = try!(self.parse_function_spec());
|
|
let mut func = Function::with_name_signature(name, sig);
|
|
|
|
// function ::= function-spec * "{" preample function-body "}"
|
|
try!(self.match_token(Token::LBrace, "expected '{' before function body"));
|
|
// function ::= function-spec "{" preample function-body * "}"
|
|
try!(self.match_token(Token::RBrace, "expected '}' after function body"));
|
|
|
|
Ok(func)
|
|
}
|
|
|
|
// Parse a function spec.
|
|
//
|
|
// function-spec ::= * "function" name signature
|
|
//
|
|
fn parse_function_spec(&mut self) -> Result<(FunctionName, Signature)> {
|
|
try!(self.match_token(Token::Function, "expected 'function' keyword"));
|
|
|
|
// function-spec ::= "function" * name signature
|
|
let name = try!(self.parse_function_name());
|
|
|
|
// function-spec ::= "function" name * signature
|
|
let sig = try!(self.parse_signature());
|
|
|
|
Ok((name, sig))
|
|
}
|
|
|
|
// Parse a function name.
|
|
//
|
|
// function ::= "function" * name signature { ... }
|
|
//
|
|
fn parse_function_name(&mut self) -> Result<String> {
|
|
match self.token() {
|
|
Some(Token::Identifier(s)) => {
|
|
self.consume();
|
|
Ok(s.to_string())
|
|
}
|
|
_ => Err(self.error("expected function name")),
|
|
}
|
|
}
|
|
|
|
// Parse a function signature.
|
|
//
|
|
// signature ::= * "(" [arglist] ")" ["->" retlist] [call_conv]
|
|
//
|
|
fn parse_signature(&mut self) -> Result<Signature> {
|
|
let mut sig = Signature::new();
|
|
|
|
try!(self.match_token(Token::LPar, "expected function signature: ( args... )"));
|
|
// signature ::= "(" * [arglist] ")" ["->" retlist] [call_conv]
|
|
if self.token() != Some(Token::RPar) {
|
|
sig.argument_types = try!(self.parse_argument_list());
|
|
}
|
|
try!(self.match_token(Token::RPar, "expected ')' after function arguments"));
|
|
if self.optional(Token::Arrow) {
|
|
sig.return_types = try!(self.parse_argument_list());
|
|
}
|
|
|
|
// TBD: calling convention.
|
|
|
|
Ok(sig)
|
|
}
|
|
|
|
// Parse list of function argument / return value types.
|
|
//
|
|
// arglist ::= * arg { "," arg }
|
|
//
|
|
fn parse_argument_list(&mut self) -> Result<Vec<ArgumentType>> {
|
|
let mut list = Vec::new();
|
|
|
|
// arglist ::= * arg { "," arg }
|
|
list.push(try!(self.parse_argument_type()));
|
|
|
|
// arglist ::= arg * { "," arg }
|
|
while self.optional(Token::Comma) {
|
|
// arglist ::= arg { "," * arg }
|
|
list.push(try!(self.parse_argument_type()));
|
|
}
|
|
|
|
Ok(list)
|
|
}
|
|
|
|
// Parse a single argument type with flags.
|
|
fn parse_argument_type(&mut self) -> Result<ArgumentType> {
|
|
// arg ::= * type { flag }
|
|
let mut arg = if let Some(Token::Type(t)) = self.token() {
|
|
ArgumentType::new(t)
|
|
} else {
|
|
return Err(self.error("expected argument type"));
|
|
};
|
|
self.consume();
|
|
|
|
// arg ::= type * { flag }
|
|
while let Some(Token::Identifier(s)) = self.token() {
|
|
match s {
|
|
"uext" => arg.extension = ArgumentExtension::Uext,
|
|
"sext" => arg.extension = ArgumentExtension::Sext,
|
|
"inreg" => arg.inreg = true,
|
|
_ => break,
|
|
}
|
|
self.consume();
|
|
}
|
|
|
|
Ok(arg)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use cretonne::types::{self, ArgumentType, ArgumentExtension};
|
|
|
|
#[test]
|
|
fn argument_type() {
|
|
let mut p = Parser::new("i32 sext");
|
|
let arg = p.parse_argument_type().unwrap();
|
|
assert_eq!(arg,
|
|
ArgumentType {
|
|
value_type: types::I32,
|
|
extension: ArgumentExtension::Sext,
|
|
inreg: false,
|
|
});
|
|
let Error { location, message } = p.parse_argument_type().unwrap_err();
|
|
assert_eq!(location.line_number, 1);
|
|
assert_eq!(message, "expected argument type");
|
|
}
|
|
|
|
#[test]
|
|
fn signature() {
|
|
let sig = Parser::new("()").parse_signature().unwrap();
|
|
assert_eq!(sig.argument_types.len(), 0);
|
|
assert_eq!(sig.return_types.len(), 0);
|
|
|
|
let sig2 = Parser::new("(i8 inreg uext, f32, f64) -> i32 sext, f64")
|
|
.parse_signature()
|
|
.unwrap();
|
|
assert_eq!(format!("{}", sig2),
|
|
"(i8 uext inreg, f32, f64) -> i32 sext, f64");
|
|
|
|
// `void` is not recognized as a type by the lexer. It should not appear in files.
|
|
assert_eq!(format!("{}",
|
|
Parser::new("() -> void").parse_signature().unwrap_err()),
|
|
"1: expected argument type");
|
|
assert_eq!(format!("{}", Parser::new("i8 -> i8").parse_signature().unwrap_err()),
|
|
"1: expected function signature: ( args... )");
|
|
assert_eq!(format!("{}",
|
|
Parser::new("(i8 -> i8").parse_signature().unwrap_err()),
|
|
"1: expected ')' after function arguments");
|
|
}
|
|
}
|