Files
ngr/src/syntax/ast.rs

164 lines
4.9 KiB
Rust

use std::fmt;
use std::hash::Hash;
use internment::ArcIntern;
pub use crate::syntax::tokens::ConstantType;
use crate::syntax::Location;
/// A structure represented a parsed program.
///
/// One `Program` is associated with exactly one input file, and the
/// vector is arranged in exactly the same order as the parsed file.
/// Because this is the syntax layer, the program is guaranteed to be
/// syntactically valid, but may be nonsense. There could be attempts
/// to use unbound variables, for example, until after someone runs
/// `validate` and it comes back without errors.
#[derive(Clone, Debug, PartialEq)]
pub struct Program {
pub items: Vec<TopLevel>,
}
/// A thing that can sit at the top level of a file.
///
/// For the moment, these are statements and functions. Other things
/// will likely be added in the future, but for now: just statements
/// and functions
#[derive(Clone, Debug, PartialEq)]
pub enum TopLevel {
Statement(Statement),
Function(Option<Name>, Vec<Name>, Expression),
}
/// A Name.
///
/// This is basically a string, but annotated with the place the string
/// is in the source file.
#[derive(Clone, Debug)]
pub struct Name {
pub name: String,
pub location: Location,
}
impl Name {
pub fn new<S: ToString>(n: S, location: Location) -> Name {
Name {
name: n.to_string(),
location,
}
}
pub fn manufactured<S: ToString>(n: S) -> Name {
Name {
name: n.to_string(),
location: Location::manufactured(),
}
}
pub fn intern(self) -> ArcIntern<String> {
ArcIntern::new(self.name)
}
}
impl PartialEq for Name {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl Eq for Name {}
impl Hash for Name {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state)
}
}
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.name.fmt(f)
}
}
/// A parsed statement.
///
/// Statements are guaranteed to be syntactically valid, but may be
/// complete nonsense at the semantic level. Which is to say, all the
/// print statements were correctly formatted, and all the variables
/// referenced are definitely valid symbols, but they may not have
/// been defined or anything.
///
/// Note that equivalence testing on statements is independent of
/// source location; it is testing if the two statements say the same
/// thing, not if they are the exact same statement.
#[derive(Clone, Debug)]
pub enum Statement {
Binding(Location, Name, Expression),
Print(Location, Name),
}
impl PartialEq for Statement {
fn eq(&self, other: &Self) -> bool {
match self {
Statement::Binding(_, name1, expr1) => match other {
Statement::Binding(_, name2, expr2) => name1 == name2 && expr1 == expr2,
_ => false,
},
Statement::Print(_, name1) => match other {
Statement::Print(_, name2) => name1 == name2,
_ => false,
},
}
}
}
/// An expression in the underlying syntax.
///
/// Like statements, these expressions are guaranteed to have been
/// formatted correctly, but may not actually make any sense. Also
/// like Statements, the [`PartialEq`] implementation does not take
/// source positions into account.
#[derive(Clone, Debug)]
pub enum Expression {
Value(Location, Value),
Reference(Location, String),
Cast(Location, String, Box<Expression>),
Primitive(Location, String, Vec<Expression>),
}
impl PartialEq for Expression {
fn eq(&self, other: &Self) -> bool {
match self {
Expression::Value(_, val1) => match other {
Expression::Value(_, val2) => val1 == val2,
_ => false,
},
Expression::Reference(_, var1) => match other {
Expression::Reference(_, var2) => var1 == var2,
_ => false,
},
Expression::Cast(_, t1, e1) => match other {
Expression::Cast(_, t2, e2) => t1 == t2 && e1 == e2,
_ => false,
},
Expression::Primitive(_, prim1, args1) => match other {
Expression::Primitive(_, prim2, args2) => prim1 == prim2 && args1 == args2,
_ => false,
},
}
}
}
/// A value from the source syntax
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Value {
/// The value of the number, an optional base that it was written in, and any
/// type information provided.
///
/// u64 is chosen because it should be big enough to carry the amount of
/// information we need, and technically we interpret -4 as the primitive unary
/// operation "-" on the number 4. We'll translate this into a type-specific
/// number at a later time.
Number(Option<u8>, Option<ConstantType>, u64),
}