Skip to content

Commit

Permalink
begin work on type checking, inferencing, and semantics checking (#11)
Browse files Browse the repository at this point in the history
* begin work on type checking, inferencing, and semantics checking

* fmt

* WIP; laptop dying

* match branch types

* wip type code blocks

* build failure

* operator parsing; no precedence

* compile fn

* run compile instead of parse from hllc

* begin trait declarations in ast

* return statements in AST

* allow code blocks

* handle implicit returns in code blocks

* type checking is taking shape

* generic type checking

* return multiple errors

* proper operator precedence

* add fn params to namespace when typechecking

* contextual error messages

* remove todo panics

* toml update
  • Loading branch information
sezna authored Feb 26, 2021
1 parent 0747988 commit fc38750
Show file tree
Hide file tree
Showing 22 changed files with 1,853 additions and 247 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[workspace]

members = ["parser", "hllc", "ast"]
members = ["parser", "hllc"]
10 changes: 0 additions & 10 deletions ast/Cargo.toml

This file was deleted.

45 changes: 0 additions & 45 deletions ast/src/ast.rs

This file was deleted.

3 changes: 0 additions & 3 deletions ast/src/lib.rs

This file was deleted.

47 changes: 32 additions & 15 deletions hllc/example_programs/basic.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
trait MyTrait {
fn some_fn_thing(): u32
} {
fn some_other_trait_thing(): bool {
return true;
}
}
contract {
/*
trait my_trait {
fn someFnThing(): u32
} {
fn some_other_trait_thing(): bool {
return true;
}
}
struct my_struct {
FieldName: u64
}
*/
fn contract_func_1(a: T, y: u32): T {
println("Test function.");
let z = x.a.b.c;
let x: byte = {
// a code block w/ implicit return
let y = 0b11110000;
y
};
let example_variable_decl = 5;
let y = if true {
let x = 5;
let z = 2;
a
} else { 10 };

a
}

fn contract_func_1(x: u32, y: u32): bool {
println("Test function.");
let x: byte = 0b11110000;
let example_variable_decl = 5;
return example_variable_decl;
/*
struct my_struct {
FieldName: u64
}
*/
}
8 changes: 8 additions & 0 deletions hllc/example_programs/warn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
contract {

fn Main() {
let x = 1 + (6 / 2 + 1 * 20);
return true;
}

}
37 changes: 28 additions & 9 deletions hllc/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![allow(warnings)]
use line_col::LineColLookup;
use parser::parse;
use parser::compile;
use source_span::{
fmt::{Color, Formatter, Style},
Position, SourceBuffer, Span, DEFAULT_METRICS,
Expand Down Expand Up @@ -29,26 +29,45 @@ fn main() -> Result<(), Box<dyn std::error::Error + 'static>> {
let opt = Opt::from_args();
let content = fs::read_to_string(opt.input.clone())?;

let res = parse(&content);
let res = compile(&content);

match res {
Ok((compiled, warnings)) => {
if let Some(output) = opt.output {
let mut file = File::create(output)?;
file.write_all(format!("{:#?}", compiled).as_bytes())?;
} else {
println!("{:#?}", compiled);
//println!("{:#?}", compiled);
}
for ref warning in warnings.iter() {
format_warning(&content, warning);
}
if warnings.is_empty() {
write_green(&format!("Successfully compiled {:?}", opt.input));
write_green(&format!("Successfully compiled {:?}.", opt.input));
} else {
write_yellow(&format!("Compiled {:?} with warnings.", opt.input));
write_yellow(&format!(
"Compiled {:?} with {} {}.",
opt.input,
warnings.len(),
if warnings.len() > 1 {
"warnings"
} else {
"warning"
}
));
}
}
Err(e) => format_err(&content, e),
Err(e) => {
let e_len = e.len();
e.into_iter().for_each(|e| format_err(&content, e));

write_red(format!(
"Aborting due to {} {}.",
e_len,
if e_len > 1 { "errors" } else { "error" }
))
.unwrap();
}
}

Ok(())
Expand Down Expand Up @@ -90,7 +109,7 @@ fn format_warning(input: &str, err: &parser::CompileWarning) {
println!("{}", formatted);
}

fn format_err(input: &str, err: parser::ParseError) {
fn format_err(input: &str, err: parser::CompileError) {
let metrics = DEFAULT_METRICS;
let chars = input.chars().map(|x| -> Result<_, ()> { Ok(x) });

Expand Down Expand Up @@ -121,10 +140,10 @@ fn format_err(input: &str, err: parser::ParseError) {
);

println!("{}", formatted);
write_red("Aborting due to previous error.").unwrap();
}

fn write_red(txt: &str) -> io::Result<()> {
fn write_red(txt: String) -> io::Result<()> {
let txt = txt.as_str();
let bufwtr = BufferWriter::stderr(ColorChoice::Always);
let mut buffer = bufwtr.buffer();
buffer.set_color(ColorSpec::new().set_fg(Some(TermColor::Red)))?;
Expand Down
42 changes: 35 additions & 7 deletions parser/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::parser::Rule;
use crate::types::TypeInfo;
use inflector::cases::classcase::to_class_case;
use inflector::cases::snakecase::to_snake_case;
use pest::Span;
Expand All @@ -16,6 +17,7 @@ macro_rules! eval {
macro_rules! assert_or_warn {
($bool_expr: expr, $warnings: ident, $span: expr, $warning: expr) => {
if !$bool_expr {
use crate::error::CompileWarning;
$warnings.push(CompileWarning {
warning_content: $warning,
span: $span,
Expand Down Expand Up @@ -44,20 +46,37 @@ impl<'sc> CompileWarning<'sc> {

#[derive(Debug, Clone)]
pub enum Warning<'sc> {
NonClassCaseStructName { struct_name: &'sc str },
NonClassCaseEnumName { enum_name: &'sc str },
NonSnakeCaseStructFieldName { field_name: &'sc str },
NonSnakeCaseEnumVariantName { variant_name: &'sc str },
NonClassCaseStructName {
struct_name: &'sc str,
},
NonClassCaseEnumName {
enum_name: &'sc str,
},
NonClassCaseEnumVariantName {
variant_name: &'sc str,
},
NonSnakeCaseStructFieldName {
field_name: &'sc str,
},
NonSnakeCaseFunctionName {
name: &'sc str,
},
LossOfPrecision {
initial_type: TypeInfo<'sc>,
cast_to: TypeInfo<'sc>,
},
}

impl<'sc> Warning<'sc> {
fn to_string(&self) -> String {
use Warning::*;
match self {
NonClassCaseStructName{ struct_name } => format!("Struct \"{}\"'s capitalization is not idiomatic. Structs should have a ClassCase name, like \"{}\".", struct_name, to_class_case(struct_name)),
NonClassCaseStructName{ struct_name } => format!("Struct name \"{}\" is not idiomatic. Structs should have a ClassCase name, like \"{}\".", struct_name, to_class_case(struct_name)),
NonClassCaseEnumName{ enum_name} => format!("Enum \"{}\"'s capitalization is not idiomatic. Enums should have a ClassCase name, like \"{}\".", enum_name, to_class_case(enum_name)),
NonSnakeCaseStructFieldName { field_name } => format!("Struct field name \"{}\" is not idiomatic. Struct field names should have a snake_case name, like \"{}\".", field_name, to_snake_case(field_name)),
NonSnakeCaseEnumVariantName { variant_name } => format!("Enum variant name \"{}\" is not idiomatic. Enum variant names should have a snake_case name, like \"{}\".", variant_name, to_snake_case(variant_name)),
NonClassCaseEnumVariantName { variant_name } => format!("Enum variant name \"{}\" is not idiomatic. Enum variant names should be ClassCase, like \"{}\".", variant_name, to_class_case(variant_name)),
NonSnakeCaseFunctionName { name } => format!("Function name \"{}\" is not idiomatic. Function names should be snake_case, like \"{}\".", name, to_snake_case(name)),
LossOfPrecision { initial_type, cast_to } => format!("This cast, from type {} to type {}, will lose precision.", initial_type.friendly_type_str(), cast_to.friendly_type_str()),
}
}
}
Expand All @@ -68,7 +87,7 @@ pub enum ParseError<'sc> {
ParseFailure(#[from] pest::error::Error<Rule>),
#[error("Invalid top-level item: {0:?}. A program should consist of a contract, script, or predicate at the top level.")]
InvalidTopLevelItem(Rule, Span<'sc>),
#[error("Internal compiler error: {0}. Please file an issue on the repository and include the code that triggered this error.")]
#[error("Internal compiler error: {0}\nPlease file an issue on the repository and include the code that triggered this error.")]
Internal(&'static str, Span<'sc>),
#[error("Unimplemented feature: {0:?}")]
Unimplemented(Rule, Span<'sc>),
Expand All @@ -85,6 +104,12 @@ pub enum ParseError<'sc> {
type_name: &'sc str,
span: Span<'sc>,
},
#[error("Program contains multiple contracts. A valid program should only contain at most one contract.")]
MultipleContracts(Span<'sc>),
#[error("Program contains multiple scripts. A valid program should only contain at most one script.")]
MultipleScripts(Span<'sc>),
#[error("Program contains multiple predicates. A valid program should only contain at most one predicate.")]
MultiplePredicates(Span<'sc>),
}

impl<'sc> ParseError<'sc> {
Expand All @@ -103,6 +128,9 @@ impl<'sc> ParseError<'sc> {
ExpectedOp { span, .. } => (span.start(), span.end()),
UnexpectedWhereClause(sp) => (sp.start(), sp.end()),
UndeclaredGenericTypeInWhereClause { span, .. } => (span.start(), span.end()),
MultiplePredicates(sp) => (sp.start(), sp.end()),
MultipleScripts(sp) => (sp.start(), sp.end()),
MultipleContracts(sp) => (sp.start(), sp.end()),
}
}

Expand Down
13 changes: 8 additions & 5 deletions parser/src/hll.pest
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,17 @@ script = { "script" ~ "{" ~ (declaration|use_statement)* ~ "}" }
predicate = { "predicate" ~ "{" ~ (declaration|use_statement)* ~ "}" }

// expressions
expr_inner = _{literal_value|if_exp|"(" ~ expr ~ ")"|func_app|struct_expression|var_exp|array_exp|match_expression}
expr_inner = _{literal_value|if_exp|parenthesized_expression|code_block|func_app|struct_expression|var_exp|array_exp|match_expression}
parenthesized_expression = { "(" ~ expr ~ ")" }
// // op exps built in to expr to prevent left recursion
expr = {expr_inner ~ (op ~ expr_inner)*}
func_app = { fn_name ~ "(" ~ (expr ~ ("," ~ expr)* )? ~ ")" }
fn_name = @{var_exp}
fn_name = {var_exp}
var_exp = { unary_op? ~ var_name_ident }

var_name_ident = { ident ~ ("." ~ ident)* }

if_exp = {"if" ~ expr ~ "then" ~ code_block ~ ("else" ~ code_block)?}
if_exp = {"if" ~ expr ~ code_block ~ ("else" ~ code_block)?}

op = {"+"|"-"|"/"|"*"|"=="|"!="|"|"|"||"|"&"|"&&"|"^"|"%"}
unary_op = { "!" | "ref" | "deref" }
Expand All @@ -49,6 +50,7 @@ u16_integer = { basic_integer ~ "u16" }
u32_integer = { basic_integer ~ "u32" }
// default is u64
u64_integer = { basic_integer ~ "u64"? }
u128_integer = { basic_integer ~ "u128" }
byte = { binary_byte | hex_byte }
binary_byte = @{ "0b" ~ ("1" | "0")*}
hex_byte = @{ "0x" ~ hex_digit* }
Expand All @@ -69,7 +71,7 @@ var_decl = {var_decl_keyword ~ mut_keyword? ~ var_name ~ type_ascription? ~
type_ascription = { ":" ~ type_name}
fn_decl = {fn_signature ~ code_block}
fn_signature = { fn_decl_keyword ~ fn_decl_name ~ type_params? ~ fn_decl_params ~ (fn_returns ~ return_type)? ~ trait_bounds? }
var_name = @{ident}
var_name = {ident}

struct_decl = { "struct" ~ struct_name ~ type_params? ~ trait_bounds? ~ "{" ~ struct_fields ~ "}" }
struct_name = @{ident}
Expand All @@ -84,7 +86,8 @@ enum_field_name = @{ident}
// // fn declaration
fn_decl_params = {"(" ~ (fn_decl_param ~ ("," ~ fn_decl_param)*)? ~ ")"}
type_params = { "<" ~ generic_type_param ~ (", " ~ generic_type_param)* ~ ">" }
fn_decl_param = {("self")? ~ ident ~ ":" ~ type_name}
fn_decl_param = {("self")? ~ fn_decl_param_name ~ ":" ~ type_name}
fn_decl_param_name = {ident}
return_type = {ident}
fn_decl_name = @{ident}
type_name = @{ident ~ type_params?}
Expand Down
Loading

0 comments on commit fc38750

Please sign in to comment.