mirror of
https://github.com/Dummi26/mers.git
synced 2025-12-16 03:57:50 +01:00
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:
@@ -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..]
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user