Type Annotations

- Add type annotations: [type] statement
- Add type definitions: [[name] type], [[name] := statement]
- Add type annotations example (08)
- add Quickstart.md, reference it from README
This commit is contained in:
Mark
2023-11-21 22:10:58 +01:00
parent b6d708db3d
commit 4144d6cf71
21 changed files with 756 additions and 63 deletions

View File

@@ -56,6 +56,17 @@ impl Source {
let content = std::fs::read_to_string(&path)?;
Ok(Self::new(SourceFrom::File(path), content))
}
pub fn new_from_string_raw(source: String) -> Self {
Self {
src_from: SourceFrom::Unspecified,
src_raw_len: source.len(),
src_og: source.clone(),
src: source,
comments: vec![],
i: 0,
sections: vec![],
}
}
pub fn new_from_string(source: String) -> Self {
Self::new(SourceFrom::Unspecified, source)
}
@@ -167,7 +178,7 @@ impl Source {
Some(ch)
}
fn word_splitter(ch: char) -> bool {
ch.is_whitespace() || ".,;[](){}".contains(ch)
ch.is_whitespace() || ".,;[](){}/<".contains(ch)
}
pub fn peek_word(&self) -> &str {
self.src[self.i..]

View File

@@ -4,13 +4,107 @@ use super::{Source, SourcePos};
use crate::{
data::Data,
errors::{error_colors, CheckError},
program::{self, parsed::MersStatement},
program::{
self,
parsed::{as_type::AsType, MersStatement},
},
};
pub fn parse(
src: &mut Source,
) -> Result<Option<Box<dyn program::parsed::MersStatement>>, CheckError> {
src.section_begin("statement".to_string());
src.skip_whitespace();
// type annotation:
// [type] statement // force output type to be `type`
// [[name] type] // define `name` as `type`
// [[name] := statement] // define `name` as the type of `statement` (`statement` is never executed)
if matches!(src.peek_char(), Some('[')) {
let pos_in_src = src.get_pos();
src.next_char();
return Ok(Some(if matches!(src.peek_char(), Some('[')) {
src.next_char();
// [[...
let name = src.next_word();
let name = name.trim().to_owned();
src.skip_whitespace();
if !matches!(src.next_char(), Some(']')) {
return Err(
CheckError::new().msg(format!("Expected ']' after type name in [[type_name]]"))
);
}
src.skip_whitespace();
if src.peek_word() == ":=" {
src.next_word();
// [[name] := statement]
let statement = match parse(src) {
Ok(Some(v)) => v,
Ok(None) => {
return Err(CheckError::new()
.src(vec![((pos_in_src, src.get_pos()).into(), None)])
.msg(format!("EOF after `[...]` type annotation")))
}
Err(e) => return Err(e),
};
if !matches!(src.next_char(), Some(']')) {
return Err(CheckError::new().msg(format!(
"Expected ']' after statement in [[type_name] := statement]"
)));
}
Box::new(program::parsed::custom_type::CustomType {
pos_in_src: (pos_in_src, src.get_pos()).into(),
name,
source: Err(statement),
})
} else {
// [[name] type]
src.skip_whitespace();
let as_type = super::types::parse_type(src)?;
src.skip_whitespace();
if !matches!(src.next_char(), Some(']')) {
return Err(CheckError::new().msg(format!(
"Expected ']' after type definition in [[type_name] type_definition]"
)));
}
Box::new(program::parsed::custom_type::CustomType {
pos_in_src: (pos_in_src, src.get_pos()).into(),
name,
source: Ok(as_type),
})
}
} else {
// [type] statement
src.skip_whitespace();
let type_pos_in_src = src.get_pos();
let as_type = super::types::parse_type(src)?;
let type_pos_in_src = (type_pos_in_src, src.get_pos()).into();
src.skip_whitespace();
if !matches!(src.next_char(), Some(']')) {
return Err(CheckError::new()
.src(vec![(
(pos_in_src, src.get_pos()).into(),
Some(error_colors::TypeAnnotationNoClosingBracket),
)])
.msg(format!("Missing closing bracket ']' after type annotation")));
}
let statement = match parse(src) {
Ok(Some(v)) => v,
Ok(None) => {
return Err(CheckError::new()
.src(vec![((pos_in_src, src.get_pos()).into(), None)])
.msg(format!("EOF after `[...]` type annotation")))
}
Err(e) => return Err(e),
};
Box::new(AsType {
pos_in_src: (pos_in_src, src.get_pos()).into(),
statement,
as_type,
type_pos_in_src,
expand_type: true,
})
}));
}
let mut first = if let Some(s) = parse_no_chain(src)? {
s
} else {
@@ -128,8 +222,8 @@ pub fn parse_multiple(
pub fn parse_no_chain(
src: &mut Source,
) -> Result<Option<Box<dyn program::parsed::MersStatement>>, CheckError> {
src.section_begin("statement no chain".to_string());
src.skip_whitespace();
src.section_begin("statement no chain".to_string());
match src.peek_char() {
Some('#') => {
let pos_in_src = src.get_pos();
@@ -339,6 +433,14 @@ pub fn parse_no_chain(
/// expects to be called *after* a " character is consumed from src
pub fn parse_string(src: &mut Source, double_quote: SourcePos) -> Result<String, CheckError> {
parse_string_custom_end(src, double_quote, '"', '"')
}
pub fn parse_string_custom_end(
src: &mut Source,
opening: SourcePos,
opening_char: char,
closing_char: char,
) -> Result<String, CheckError> {
let mut s = String::new();
loop {
if let Some(ch) = src.next_char() {
@@ -350,6 +452,7 @@ pub fn parse_string(src: &mut Source, double_quote: SourcePos) -> Result<String,
Some('n') => '\n',
Some('t') => '\t',
Some('"') => '"',
Some(c) if c == closing_char || c == opening_char => c,
Some(o) => {
return Err(CheckError::new()
.src(vec![(
@@ -367,7 +470,7 @@ pub fn parse_string(src: &mut Source, double_quote: SourcePos) -> Result<String,
.msg(format!("EOF in backslash escape")));
}
});
} else if ch == '"' {
} else if ch == closing_char {
break;
} else {
s.push(ch);
@@ -375,11 +478,27 @@ pub fn parse_string(src: &mut Source, double_quote: SourcePos) -> Result<String,
} else {
return Err(CheckError::new()
.src(vec![(
(double_quote, src.get_pos()).into(),
(opening, src.get_pos()).into(),
Some(error_colors::StringEOF),
)])
.msg(format!("EOF in string literal")));
.msg(format!(
"EOF in string literal{}",
if closing_char != '"' {
format!(
"{opening_char}...{closing_char} (end string with '{closing_char}')"
)
} else {
String::new()
}
)));
}
}
Ok(s)
}
pub fn to_string_literal(val: &str, end: char) -> String {
val.replace("\\", "\\\\")
.replace("\r", "\\r")
.replace("\n", "\\n")
.replace("\"", "\\\"")
.replace(end, format!("\\{end}").as_str())
}

View File

@@ -1,47 +1,113 @@
use std::sync::Arc;
use crate::{
data::{self, Type},
errors::{error_colors, CheckError},
};
use super::Source;
/// multiple types are represented as a `Vec<ParsedType>`.
#[derive(Clone, Debug)]
pub enum ParsedType {
Reference(Vec<Self>),
Tuple(Vec<Vec<Self>>),
Type(String),
TypeWithInfo(String, String),
}
fn parse_single_type(src: &mut Source) -> Result<ParsedType, ()> {
pub fn parse_single_type(src: &mut Source) -> Result<ParsedType, CheckError> {
src.section_begin("parse single type".to_string());
src.skip_whitespace();
Ok(match src.peek_char() {
// Reference
Some('&') => {
src.next_char();
if let Some('{') = src.peek_char() {
src.next_char();
let types = parse_type(src)?;
let nc = src.next_char();
if !matches!(nc, Some('}')) {
let nc = if let Some(nc) = nc {
format!("'{nc}'")
} else {
format!("EOF")
};
return Err(CheckError::new().msg(format!(
"No closing }} in reference type with opening {{! Found {nc} instead"
)));
}
ParsedType::Reference(types)
} else {
ParsedType::Reference(vec![parse_single_type(src)?])
}
}
// Tuple
Some('(') => {
let pos_in_src = src.get_pos();
src.next_char();
src.section_begin("parse tuple's inner types".to_string());
let mut inner = vec![];
loop {
match src.peek_char() {
Some(')') => {
src.next_char();
break;
src.skip_whitespace();
if let Some(')') = src.peek_char() {
// empty tuple, don't even start the loop
} else {
loop {
inner.push(parse_type(src)?);
match src.peek_char() {
Some(')') => {
src.next_char();
break;
}
Some(',') => {
src.next_char();
}
_ => {
let ppos = src.get_pos();
src.next_char();
return Err(CheckError::new()
.src(vec![
((pos_in_src, src.get_pos()).into(), None),
(
(ppos, src.get_pos()).into(),
Some(error_colors::BadCharInTupleType),
),
])
.msg(format!(
"Unexpected character in tuple type, expected comma ',' or ')'."
)));
}
}
Some(',') => {
src.next_char();
}
_ => todo!("err: bad char in tuple inner type"),
}
inner.push(parse_type(src)?);
}
ParsedType::Tuple(inner)
}
Some(_) => ParsedType::Type(src.next_word().to_lowercase()),
Some(_) => {
let t = src.next_word().to_owned();
src.skip_whitespace();
if let Some('<') = src.peek_char() {
let pos = src.get_pos();
src.next_char();
ParsedType::TypeWithInfo(
t,
super::statements::parse_string_custom_end(src, pos, '<', '>')?,
)
} else {
ParsedType::Type(t)
}
}
None => todo!(),
})
}
fn parse_type(src: &mut Source) -> Result<Vec<ParsedType>, ()> {
pub fn parse_type(src: &mut Source) -> Result<Vec<ParsedType>, CheckError> {
src.section_begin("parse single type".to_string());
let mut types = vec![];
loop {
types.push(parse_single_type(src)?);
src.skip_whitespace();
if let Some('/') = src.peek_char() {
src.next_char();
continue;
} else {
break;
@@ -49,3 +115,49 @@ fn parse_type(src: &mut Source) -> Result<Vec<ParsedType>, ()> {
}
Ok(types)
}
pub fn type_from_parsed(
parsed: &Vec<ParsedType>,
info: &crate::program::run::CheckInfo,
) -> Result<Type, CheckError> {
let mut as_type = Type::empty();
for t in parsed.iter() {
as_type.add(match t {
ParsedType::Reference(inner) => {
let inner = type_from_parsed(inner, info)?;
Arc::new(data::reference::ReferenceT(inner))
}
ParsedType::Tuple(t) => Arc::new(data::tuple::TupleT(
t.iter()
.map(|v| type_from_parsed(v, info))
.collect::<Result<_, _>>()?,
)),
ParsedType::Type(name) => match info
.scopes
.iter()
.find_map(|scope| scope.types.iter().find(|v| v.0 == name).map(|(_, v)| v))
{
Some(Ok(t)) => Arc::clone(t),
Some(Err(_)) => {
return Err(CheckError::new().msg(format!(
"Type: specified type without info, but type needs additional info"
)))
}
None => return Err(CheckError::new().msg(format!("Unknown type '{name}'"))),
},
ParsedType::TypeWithInfo(name, additional_info) => match info
.scopes
.iter()
.find_map(|scope| scope.types.iter().find(|v| v.0 == name).map(|(_, v)| v))
{
Some(Ok(t)) => {
return Err(CheckError::new().msg(format!(
"Type: specified type with info, but type {t} doesn't need it"
)))
}
Some(Err(f)) => f(&additional_info, info)?,
None => return Err(CheckError::new().msg(format!("Unknown type '{name}'"))),
},
});
}
Ok(as_type)
}