Compare commits
182 Commits
fixing_con
...
working_on
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98de15c07d | ||
|
|
0246e510ca | ||
|
|
71dacc94d6 | ||
|
|
4ded241c82 | ||
|
|
1a934d7804 | ||
|
|
627a740b0d | ||
|
|
401d5aabd6 | ||
|
|
f79125e9df | ||
|
|
4ad5739615 | ||
|
|
654e326c40 | ||
|
|
e00948cad9 | ||
|
|
0af6fed505 | ||
|
|
1f527f7949 | ||
|
|
8680c4faf6 | ||
|
|
b198984fc5 | ||
|
|
58779f8470 | ||
|
|
a0fa50392c | ||
|
|
d357876b16 | ||
|
|
e42f0c644c | ||
|
|
2ec7bf3b9a | ||
|
|
5147e1a3eb | ||
|
|
955c073174 | ||
|
|
7c46a29141 | ||
|
|
0adc761e72 | ||
|
|
b2039a7b67 | ||
|
|
b4c4531e4d | ||
|
|
2d36ad44d6 | ||
|
|
21132a369c | ||
|
|
ff0294c56e | ||
|
|
bc80c8f9ad | ||
|
|
e39356c0e5 | ||
|
|
d44bb02d61 | ||
|
|
9056e9b0e1 | ||
|
|
e9b90412ce | ||
|
|
65c47c20fc | ||
|
|
fab3fb8ec2 | ||
|
|
0d5ccd21fe | ||
|
|
69b7b9f528 | ||
|
|
9a09f40222 | ||
|
|
020819550b | ||
|
|
15f9dbe7a6 | ||
|
|
836bed1207 | ||
|
|
cee5b085d5 | ||
|
|
837a55c718 | ||
|
|
f4f89b39b6 | ||
|
|
c6b4ed7ee4 | ||
|
|
be425860af | ||
|
|
17e88b33f2 | ||
|
|
47f7eb1ef6 | ||
|
|
72d0cfe466 | ||
|
|
cea2f63b44 | ||
|
|
eec315dd58 | ||
|
|
1e9aa91c5d | ||
|
|
9813609ad7 | ||
|
|
5953d9d815 | ||
|
|
a74e09c761 | ||
|
|
ad53d4394b | ||
|
|
151246e1c5 | ||
|
|
77d2826918 | ||
|
|
1bd48ed5db | ||
|
|
c394b81746 | ||
|
|
ec29077247 | ||
|
|
62043ac2d1 | ||
|
|
bada386979 | ||
|
|
e71d404071 | ||
|
|
cab4702bd6 | ||
|
|
ec5a9d457e | ||
|
|
bfbc1580aa | ||
|
|
2d6c9010b9 | ||
|
|
f4ff92302f | ||
|
|
e88ed97b06 | ||
|
|
b8df09e956 | ||
|
|
d7f0147a4f | ||
|
|
f883512882 | ||
|
|
37070a6b3e | ||
|
|
ffe7deb00a | ||
|
|
d7baf065fb | ||
|
|
6b42f8b8de | ||
|
|
d9e67a6341 | ||
|
|
7de536ade0 | ||
|
|
f62b4c6906 | ||
|
|
4679a9fc7f | ||
|
|
c25354b2c7 | ||
|
|
5f8b842bf2 | ||
|
|
fef66e345b | ||
|
|
e57d33eae7 | ||
|
|
dca9ad06c3 | ||
|
|
354148c5ba | ||
|
|
6219a06d6f | ||
|
|
3b20b40eb7 | ||
|
|
4ecf63c54d | ||
|
|
3d00667caf | ||
|
|
4b9c7e38dd | ||
|
|
03317233c6 | ||
|
|
dff204069f | ||
|
|
f2282f0101 | ||
|
|
40ccea8c05 | ||
|
|
cae6f2f768 | ||
|
|
1be6991f55 | ||
|
|
1b60bd38ff | ||
|
|
3b20b9e209 | ||
|
|
de0e150536 | ||
|
|
baf51fb147 | ||
|
|
dc9e493fa1 | ||
|
|
d57a8045a9 | ||
|
|
50d5176b45 | ||
|
|
501eaeee87 | ||
|
|
8619c94217 | ||
|
|
fc7c86be1a | ||
|
|
77e0d639c2 | ||
|
|
9927a6b1fd | ||
|
|
e8dfc2be34 | ||
|
|
abe2db25b2 | ||
|
|
a99a5f10a4 | ||
|
|
c52bc65bb9 | ||
|
|
819c422cee | ||
|
|
1c11fec803 | ||
|
|
a5c3c383dc | ||
|
|
76046b134a | ||
|
|
c24223f28e | ||
|
|
79e02b0999 | ||
|
|
80eb703f5e | ||
|
|
4fccff5e27 | ||
|
|
e934d7bdc5 | ||
|
|
de199e785a | ||
|
|
81ac918a59 | ||
|
|
5d4505241a | ||
|
|
f67793308e | ||
|
|
693766fa59 | ||
|
|
f9f29dd0dd | ||
|
|
3c1823510f | ||
|
|
92078ef7d8 | ||
|
|
1abbe2e448 | ||
|
|
065bdd6bda | ||
|
|
e125e8b440 | ||
|
|
8565c7dfb3 | ||
|
|
f885d5dfb6 | ||
|
|
b85725125c | ||
|
|
2d961d6402 | ||
|
|
fa7b6ce96b | ||
|
|
5c9180efc2 | ||
|
|
1d5e5aa735 | ||
|
|
2c298c7247 | ||
|
|
f00fee0e37 | ||
|
|
0d13b5e3bc | ||
|
|
98f597f00a | ||
|
|
fb71881409 | ||
|
|
d1c3b4a81b | ||
|
|
f9181b5786 | ||
|
|
0e914cf057 | ||
|
|
04ea8c5ebc | ||
|
|
492ef4ae19 | ||
|
|
75a7a4499d | ||
|
|
99e6668c9a | ||
|
|
1d38a07cf8 | ||
|
|
0fa844bcf9 | ||
|
|
97bee58fbe | ||
|
|
34c2b43371 | ||
|
|
88b617de52 | ||
|
|
482674b19a | ||
|
|
a72b387ceb | ||
|
|
864e932e9f | ||
|
|
d7e73be44c | ||
|
|
6a548c9086 | ||
|
|
0c0690e86e | ||
|
|
6d18f80185 | ||
|
|
6825de3916 | ||
|
|
1b78fbff82 | ||
|
|
897c1181a9 | ||
|
|
6833bc4f00 | ||
|
|
f2ded78776 | ||
|
|
9debdd8d66 | ||
|
|
8067c862f3 | ||
|
|
f9c2fc3f9d | ||
|
|
5ead1e5d44 | ||
|
|
348a6f7c76 | ||
|
|
5f336ec1a9 | ||
|
|
da59fae0d3 | ||
|
|
5b5689accf | ||
|
|
32acf89814 | ||
|
|
c637a922a9 | ||
|
|
42d0aba21c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@ Cargo.lock
|
||||
target
|
||||
.schala_repl
|
||||
.schala_history
|
||||
rusty-tags.vi
|
||||
|
||||
@@ -6,11 +6,12 @@ authors = ["greg <greg.shuflin@protonmail.com>"]
|
||||
[dependencies]
|
||||
|
||||
schala-repl = { path = "schala-repl" }
|
||||
schala-codegen = { path = "schala-codegen" }
|
||||
schala-repl-codegen = { path = "schala-repl-codegen" }
|
||||
maaru-lang = { path = "maaru" }
|
||||
rukka-lang = { path = "rukka" }
|
||||
robo-lang = { path = "robo" }
|
||||
schala-lang = { path = "schala-lang" }
|
||||
schala-lang = { path = "schala-lang/language" }
|
||||
schala-lang-codegen = { path = "schala-lang/codegen" }
|
||||
|
||||
[build-dependencies]
|
||||
includedir_codegen = "0.2.0"
|
||||
|
||||
31
Grammar
31
Grammar
@@ -1,31 +0,0 @@
|
||||
|
||||
|
||||
<program> := <statements> EOF
|
||||
|
||||
<statements> := <statement>
|
||||
| <statement> SEP <statements>
|
||||
|
||||
<statement> := let <id> = <expr>
|
||||
| <expr>
|
||||
| <fn_block>
|
||||
|
||||
<fn_block> := fn <id> ( <arg_list> ) <statements> end
|
||||
|
||||
<arg_list> := e
|
||||
| <id>
|
||||
| <id> , <arg_list>
|
||||
|
||||
<expr> := if <expr> then <statements> end
|
||||
| if <expr> then <statements> else <statements> end
|
||||
| while <expr> SEP <statements> end
|
||||
| ( <expr> )
|
||||
| <binop>
|
||||
|
||||
<binop> := <simple_expr>
|
||||
| <simple_expr> <id> <binop>
|
||||
|
||||
<simple_expr> := <id>
|
||||
| <number>
|
||||
| <string>
|
||||
|
||||
|
||||
33
README.md
33
README.md
@@ -1,21 +1,21 @@
|
||||
|
||||
# Schala - a programming language meta-interpreter
|
||||
|
||||
Schala is a Rust framework written to make it easy to
|
||||
create and experiment with toy programming languages. It provides
|
||||
a common REPL, and a trait `ProgrammingLanguage` with provisions
|
||||
for tokenizing text, parsing tokens, evaluating an abstract syntax tree,
|
||||
and other tasks that are common to all programming languages.
|
||||
Schala is a Rust framework written to make it easy to create and experiment
|
||||
with toy programming languages. It provides a cross-language REPL and
|
||||
provisions for tokenizing text, parsing tokens, evaluating an abstract syntax
|
||||
tree, and other tasks that are common to all programming languages.
|
||||
|
||||
Schala is implemented as a Rust library `schala_lib`, which provides a
|
||||
`schala_main` function. This function serves as the main loop of the REPL, if run
|
||||
interactively, or otherwise reads and interprets programming language source
|
||||
files. It expects as input a vector of `PLIGenerator`, which is a type representing
|
||||
a closure that returns a boxed trait object that implements the `ProgrammingLanguage` trait,
|
||||
and stores any persistent state relevant to that programming language. The ability
|
||||
to share state between different programming languages is in the works.
|
||||
Schala is implemented as a Rust library `schala-repl`, which provides a
|
||||
function `repl_main` meant to be used as the equivalent of main() for library
|
||||
users. This function parses command-line arguments and either runs an interactive
|
||||
REPL or interprets a program non-interactively.
|
||||
|
||||
## About
|
||||
Individual programming language implementations are Rust types that implement
|
||||
the `ProgrammingLanguageInterface` trait and store whatever persistent state is
|
||||
relevant to that language. The ability to share state between different
|
||||
programming languages is in the works.
|
||||
|
||||
## History
|
||||
|
||||
Schala started out life as an experiment in writing a Javascript-like
|
||||
programming language that would never encounter any kind of runtime value
|
||||
@@ -58,6 +58,9 @@ of learning how to write a programming language.
|
||||
|
||||
### Type-checking
|
||||
https://skillsmatter.com/skillscasts/10868-inside-the-rust-compiler
|
||||
https://www.youtube.com/watch?v=il3gD7XMdmA
|
||||
http://dev.stephendiehl.com/fun/006_hindley_milner.html
|
||||
https://rust-lang-nursery.github.io/rustc-guide/type-inference.html
|
||||
|
||||
### Evaluation
|
||||
*Understanding Computation*, Tom Stuart, O'Reilly 2013
|
||||
@@ -67,6 +70,7 @@ https://skillsmatter.com/skillscasts/10868-inside-the-rust-compiler
|
||||
### Parsing
|
||||
http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/
|
||||
https://soc.github.io/languages/unified-condition-syntax
|
||||
http://www.lihaoyi.com/post/ZeroOverheadTreeProcessingwiththeVisitorPattern.html?a=1
|
||||
|
||||
[Crafting Interpreters](http://www.craftinginterpreters.com/)
|
||||
|
||||
@@ -75,4 +79,5 @@ http://blog.ulysse.io/2016/07/03/llvm-getting-started.html
|
||||
|
||||
###Rust resources
|
||||
https://thefullsnack.com/en/rust-for-the-web.html
|
||||
|
||||
https://rocket.rs/guide/getting-started/
|
||||
|
||||
23
TODO.md
23
TODO.md
@@ -1,6 +1,26 @@
|
||||
|
||||
# TODO Items
|
||||
|
||||
-Plan of attack:
|
||||
-write a visitor pattern for AST
|
||||
-convert AST type to including SourceMap'd wrappers (w/ .into())
|
||||
-at the same time, amke sure the visitor pattern "skips over" the SourceMap'd stuff
|
||||
so it can just care about AST structure
|
||||
|
||||
- AST : maybe replace the Expression type with "Ascription(TypeName, Box<Expression>) nodes??
|
||||
- parser: add a "debug" field to the Parser struct for all debug-related things
|
||||
|
||||
-scala-style html"dfasfsadf${}" string interpolations!
|
||||
|
||||
-fuzz test schala
|
||||
|
||||
|
||||
*A neat idea for pattern matching optimization would be if you could match on one of several things in a list
|
||||
ex:
|
||||
if x {
|
||||
is (comp, LHSPat, RHSPat) if comp in ["==, "<"] -> ...
|
||||
}
|
||||
|
||||
|
||||
- https://nshipster.com/never/
|
||||
-https://cranelift.readthedocs.io/en/latest/?badge=latest<Paste>
|
||||
@@ -94,10 +114,7 @@ type enum {
|
||||
|
||||
|
||||
|
||||
- AST : maybe replace the Expression type with "Ascription(TypeName, Box<Expression>) nodes??
|
||||
- parser: add a "debug" field to the Parser struct for all debug-related things
|
||||
|
||||
-scala-style html"dfasfsadf${}" string interpolations!
|
||||
|
||||
*Compiler passes architecture
|
||||
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
#![feature(trace_macros)]
|
||||
#![feature(proc_macro)]
|
||||
extern crate proc_macro;
|
||||
#[macro_use]
|
||||
extern crate quote;
|
||||
extern crate syn;
|
||||
|
||||
extern crate schala_repl;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use syn::{Ident, Attribute, DeriveInput};
|
||||
|
||||
fn extract_attribute_arg_by_name(name: &str, attrs: &Vec<Attribute>) -> Option<String> {
|
||||
use syn::{Meta, Lit, MetaNameValue};
|
||||
attrs.iter().map(|attr| attr.interpret_meta()).find(|meta| {
|
||||
match meta {
|
||||
&Some(Meta::NameValue(MetaNameValue { ident, .. })) if ident.as_ref() == name => true,
|
||||
_ => false
|
||||
}
|
||||
}).and_then(|meta| {
|
||||
match meta {
|
||||
Some(Meta::NameValue(MetaNameValue { lit: Lit::Str(litstr), .. })) => Some(litstr.value()),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_attribute_list(name: &str, attrs: &Vec<Attribute>) -> Option<Vec<(Ident, Option<Vec<Ident>>)>> {
|
||||
use syn::{Meta, MetaList, NestedMeta};
|
||||
attrs.iter().find(|attr| {
|
||||
match attr.path.segments.iter().nth(0) {
|
||||
Some(segment) if segment.ident.as_ref() == name => true,
|
||||
_ => false
|
||||
}
|
||||
}).and_then(|attr| {
|
||||
match attr.interpret_meta() {
|
||||
Some(Meta::List(MetaList { nested, .. })) => {
|
||||
Some(nested.iter().map(|nested_meta| match nested_meta {
|
||||
&NestedMeta::Meta(Meta::Word(ident)) => (ident, None),
|
||||
&NestedMeta::Meta(Meta::List(MetaList { ident, nested: ref nested2, .. })) => {
|
||||
let own_args = nested2.iter().map(|nested_meta2| match nested_meta2 {
|
||||
&NestedMeta::Meta(Meta::Word(ident)) => ident,
|
||||
_ => panic!("Bad format for doubly-nested attribute list")
|
||||
}).collect();
|
||||
(ident, Some(own_args))
|
||||
},
|
||||
_ => panic!("Bad format for nested list")
|
||||
}).collect())
|
||||
},
|
||||
_ => panic!("{} must be a comma-delimited list surrounded by parens", name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[proc_macro_derive(ProgrammingLanguageInterface, attributes(LanguageName, SourceFileExtension, PipelineSteps))]
|
||||
pub fn derive_programming_language_interface(input: TokenStream) -> TokenStream {
|
||||
use schala_repl::PassDescriptor;
|
||||
let ast: DeriveInput = syn::parse(input).unwrap();
|
||||
let name = &ast.ident;
|
||||
let attrs = &ast.attrs;
|
||||
|
||||
let language_name: String = extract_attribute_arg_by_name("LanguageName", attrs).expect("LanguageName is required");
|
||||
let file_ext = extract_attribute_arg_by_name("SourceFileExtension", attrs).expect("SourceFileExtension is required");
|
||||
let passes = extract_attribute_list("PipelineSteps", attrs).expect("PipelineSteps are required");
|
||||
let pass_idents = passes.iter().map(|x| x.0);
|
||||
|
||||
//let pass_names: Vec<String> = passes.iter().map(|pass| pass.0.to_string()).collect();
|
||||
let pass_descriptors = passes.iter().map(|pass| {
|
||||
let name = pass.0.to_string();
|
||||
let opts: Vec<String> = match &pass.1 {
|
||||
None => vec![],
|
||||
Some(opts) => opts.iter().map(|o| o.to_string()).collect(),
|
||||
};
|
||||
|
||||
quote! {
|
||||
PassDescriptor {
|
||||
name: #name.to_string(),
|
||||
debug_options: vec![#(format!(#opts)),*]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let tokens = quote! {
|
||||
use schala_repl::PassDescriptor;
|
||||
impl ProgrammingLanguageInterface for #name {
|
||||
fn get_language_name(&self) -> String {
|
||||
#language_name.to_string()
|
||||
}
|
||||
fn get_source_file_suffix(&self) -> String {
|
||||
#file_ext.to_string()
|
||||
}
|
||||
fn execute_pipeline(&mut self, input: &str, options: &EvalOptions) -> FinishedComputation {
|
||||
let mut chain = pass_chain![self, options; #(#pass_idents),* ];
|
||||
chain(input)
|
||||
}
|
||||
fn get_passes(&self) -> Vec<PassDescriptor> {
|
||||
vec![ #(#pass_descriptors),* ]
|
||||
//vec![ #(PassDescriptor { name: #pass_names.to_string(), debug_options: vec![] }),* ]
|
||||
}
|
||||
}
|
||||
};
|
||||
tokens.into()
|
||||
}
|
||||
12
schala-lang/codegen/Cargo.toml
Normal file
12
schala-lang/codegen/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "schala-lang-codegen"
|
||||
version = "0.1.0"
|
||||
authors = ["greg <greg.shuflin@protonmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "0.15.12", features = ["full", "extra-traits", "fold"] }
|
||||
quote = "0.6.8"
|
||||
54
schala-lang/codegen/src/lib.rs
Normal file
54
schala-lang/codegen/src/lib.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
#![feature(box_patterns)]
|
||||
#![recursion_limit="128"]
|
||||
extern crate proc_macro;
|
||||
#[macro_use]
|
||||
extern crate quote;
|
||||
#[macro_use]
|
||||
extern crate syn;
|
||||
|
||||
use self::proc_macro::TokenStream;
|
||||
use self::syn::fold::Fold;
|
||||
|
||||
struct RecursiveDescentFn {
|
||||
}
|
||||
|
||||
impl Fold for RecursiveDescentFn {
|
||||
fn fold_item_fn(&mut self, mut i: syn::ItemFn) -> syn::ItemFn {
|
||||
let box block = i.block;
|
||||
let ref ident = i.ident;
|
||||
|
||||
let new_block: syn::Block = parse_quote! {
|
||||
{
|
||||
let next_token = self.peek_with_token_offset();
|
||||
let record = ParseRecord {
|
||||
production_name: stringify!(#ident).to_string(),
|
||||
next_token: format!("{}", next_token.to_string_with_metadata()),
|
||||
level: self.parse_level,
|
||||
};
|
||||
self.parse_level += 1;
|
||||
self.parse_record.push(record);
|
||||
let result = { #block };
|
||||
|
||||
if self.parse_level != 0 {
|
||||
self.parse_level -= 1;
|
||||
}
|
||||
match result {
|
||||
Err(ParseError { token: None, msg }) =>
|
||||
Err(ParseError { token: Some(next_token), msg }),
|
||||
_ => result
|
||||
}
|
||||
}
|
||||
};
|
||||
i.block = Box::new(new_block);
|
||||
i
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn recursive_descent_method(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
|
||||
let input: syn::ItemFn = parse_macro_input!(item as syn::ItemFn);
|
||||
let mut folder = RecursiveDescentFn {};
|
||||
let output = folder.fold_item_fn(input);
|
||||
TokenStream::from(quote!(#output))
|
||||
}
|
||||
@@ -8,6 +8,9 @@ itertools = "0.5.8"
|
||||
take_mut = "0.1.3"
|
||||
maplit = "*"
|
||||
lazy_static = "0.2.8"
|
||||
failure = "0.1.2"
|
||||
|
||||
schala-repl = { path = "../schala-repl" }
|
||||
schala-codegen = { path = "../schala-codegen" }
|
||||
|
||||
schala-lang-codegen = { path = "../codegen" }
|
||||
schala-repl = { path = "../../schala-repl" }
|
||||
schala-repl-codegen = { path = "../../schala-repl-codegen" }
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::rc::Rc;
|
||||
use std::convert::From;
|
||||
|
||||
use source_map::{SourceMap};
|
||||
use builtin::{BinOp, PrefixOp};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@@ -12,10 +14,9 @@ pub enum Statement {
|
||||
}
|
||||
|
||||
pub type Block = Vec<Statement>;
|
||||
|
||||
pub type ParamName = Rc<String>;
|
||||
pub type InterfaceName = Rc<String>; //should be a singleton I think??
|
||||
pub type FormalParam = (ParamName, Option<TypeName>);
|
||||
pub type FormalParam = (ParamName, Option<TypeIdentifier>);
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Declaration {
|
||||
@@ -33,7 +34,7 @@ pub enum Declaration {
|
||||
expr: Expression,
|
||||
},
|
||||
Impl {
|
||||
type_name: TypeName,
|
||||
type_name: TypeIdentifier,
|
||||
interface_name: Option<InterfaceName>,
|
||||
block: Vec<Declaration>,
|
||||
},
|
||||
@@ -46,8 +47,9 @@ pub enum Declaration {
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Signature {
|
||||
pub name: Rc<String>,
|
||||
pub operator: bool,
|
||||
pub params: Vec<FormalParam>,
|
||||
pub type_anno: Option<TypeName>,
|
||||
pub type_anno: Option<TypeIdentifier>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@@ -56,23 +58,29 @@ pub struct TypeBody(pub Vec<Variant>);
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Variant {
|
||||
UnitStruct(Rc<String>),
|
||||
TupleStruct(Rc<String>, Vec<TypeName>),
|
||||
Record(Rc<String>, Vec<(Rc<String>, TypeName)>),
|
||||
TupleStruct(Rc<String>, Vec<TypeIdentifier>),
|
||||
Record(Rc<String>, Vec<(Rc<String>, TypeIdentifier)>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Expression(pub ExpressionType, pub Option<TypeName>);
|
||||
pub struct Expression(pub ExpressionType, pub Option<TypeIdentifier>);
|
||||
|
||||
impl From<Expression> for SourceMap<Expression> {
|
||||
fn from(node: Expression) -> Self {
|
||||
SourceMap { node, data: None }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum TypeName {
|
||||
Tuple(Vec<TypeName>),
|
||||
pub enum TypeIdentifier {
|
||||
Tuple(Vec<TypeIdentifier>),
|
||||
Singleton(TypeSingletonName)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct TypeSingletonName {
|
||||
pub name: Rc<String>,
|
||||
pub params: Vec<TypeName>,
|
||||
pub params: Vec<TypeIdentifier>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@@ -111,10 +119,18 @@ pub enum ExpressionType {
|
||||
},
|
||||
Lambda {
|
||||
params: Vec<FormalParam>,
|
||||
type_anno: Option<TypeIdentifier>,
|
||||
body: Block,
|
||||
},
|
||||
ListLiteral(Vec<Expression>),
|
||||
}
|
||||
|
||||
impl From<ExpressionType> for SourceMap<ExpressionType> {
|
||||
fn from(node: ExpressionType) -> Self {
|
||||
SourceMap { node, data: None }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Discriminator {
|
||||
Simple(Expression),
|
||||
@@ -157,7 +173,10 @@ pub enum Pattern {
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum PatternLiteral {
|
||||
NumPattern(ExpressionType),
|
||||
NumPattern {
|
||||
neg: bool,
|
||||
num: ExpressionType,
|
||||
},
|
||||
StringPattern(Rc<String>),
|
||||
BoolPattern(bool),
|
||||
VarPattern(Rc<String>)
|
||||
157
schala-lang/language/src/ast_visitor.rs
Normal file
157
schala-lang/language/src/ast_visitor.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use builtin::{PrefixOp, BinOp};
|
||||
use ast::*;
|
||||
|
||||
pub fn dispatch<V: ASTVisitor>(visitor: &mut V, ast: &AST) {
|
||||
for statement in ast.0.iter() {
|
||||
match statement {
|
||||
Statement::ExpressionStatement(e) => {
|
||||
dispatch_expression(visitor, e);
|
||||
visitor.expression(e);
|
||||
},
|
||||
Statement::Declaration(decl) => {
|
||||
dispatch_declaration(visitor, decl);
|
||||
visitor.declaration(decl);
|
||||
},
|
||||
};
|
||||
visitor.statement(statement);
|
||||
}
|
||||
visitor.ast(ast)
|
||||
}
|
||||
|
||||
fn dispatch_expression<V: ASTVisitor>(visitor: &mut V, expression: &Expression) {
|
||||
match expression {
|
||||
Expression(expr, maybe_anno) => {
|
||||
match expr {
|
||||
ExpressionType::NatLiteral(n) => visitor.nat_literal(n),
|
||||
ExpressionType::FloatLiteral(f) => visitor.float_literal(f),
|
||||
ExpressionType::StringLiteral(s) => visitor.string_literal(s),
|
||||
ExpressionType::BoolLiteral(b) => visitor.bool_literal(b),
|
||||
ExpressionType::BinExp(binop, lhs, rhs) => visitor.binop(binop, lhs, rhs),
|
||||
ExpressionType::PrefixExp(prefix, expr) => visitor.prefixop(prefix, expr),
|
||||
ExpressionType::TupleLiteral(v) => visitor.tuple_literal(v),
|
||||
ExpressionType::Value(v) => visitor.value(v),
|
||||
ExpressionType::NamedStruct { name, fields } => visitor.named_struct(name, fields),
|
||||
ExpressionType::Call { f, arguments } => visitor.call(f, arguments),
|
||||
ExpressionType::Index { indexee, indexers } => visitor.index(indexee, indexers),
|
||||
ExpressionType::IfExpression { discriminator, body } => visitor.if_expression(discriminator, body),
|
||||
ExpressionType::WhileExpression { condition, body } => visitor.while_expresssion(condition, body),
|
||||
ExpressionType::ForExpression { enumerators, body } => visitor.for_expression(enumerators, body),
|
||||
ExpressionType::Lambda { params, type_anno, body } => visitor.lambda_expression(params, type_anno, body),
|
||||
ExpressionType::ListLiteral(items) => visitor.list_literal(items),
|
||||
}
|
||||
visitor.anno_expr(maybe_anno);
|
||||
visitor.expr_kind(expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_declaration<V: ASTVisitor>(visitor: &mut V, declaration: &Declaration) {
|
||||
match declaration {
|
||||
Declaration::FuncSig(sig) => visitor.func_signature(sig),
|
||||
Declaration::FuncDecl(sig, block) => visitor.func_declaration(sig, block),
|
||||
Declaration::TypeDecl { name, body, mutable } => visitor.type_declaration(name, body, mutable),
|
||||
Declaration::TypeAlias(alias, name) => visitor.type_alias(alias, name),
|
||||
Declaration::Binding { name, constant, expr} => visitor.binding(name, constant, expr),
|
||||
Declaration::Impl { type_name, interface_name, block } => visitor.impl_block(type_name, interface_name, block),
|
||||
Declaration::Interface { name, signatures } => visitor.interface(name, signatures),
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ASTVisitor {
|
||||
fn ast(&mut self, _ast: &AST) { }
|
||||
fn statement(&mut self, _stmt: &Statement) { }
|
||||
fn expression(&mut self, _expr: &Expression) { }
|
||||
fn anno_expr(&mut self, &Option<TypeIdentifier>) { }
|
||||
fn expr_kind(&mut self, _expr: &ExpressionType) { }
|
||||
|
||||
fn nat_literal(&mut self, _n: &u64) { }
|
||||
fn float_literal(&mut self, _f: &f64) { }
|
||||
fn string_literal(&mut self, _s: &Rc<String>) { }
|
||||
fn bool_literal(&mut self, _bool: &bool) { }
|
||||
fn binop(&mut self, _binop: &BinOp, _lhs: &Expression, _rhs: &Expression) { }
|
||||
fn prefixop(&mut self, prefix: &PrefixOp, _expr: &Expression) { }
|
||||
fn tuple_literal(&mut self, _v: &Vec<Expression>) { }
|
||||
fn value(&mut self, _v: &Rc<String>) { }
|
||||
fn named_struct(&mut self, _name: &Rc<String>, _values: &Vec<(Rc<String>, Expression)>) { }
|
||||
fn call(&mut self, _f: &Box<Expression>, _arguments: &Vec<Expression>) { }
|
||||
fn index(&mut self, _indexee: &Box<Expression>, _indexers: &Vec<Expression>) { }
|
||||
fn if_expression(&mut self, _discriminator: &Discriminator, _body: &IfExpressionBody) { }
|
||||
fn while_expresssion(&mut self, _condition: &Option<Box<Expression>>, body: &Block) { }
|
||||
fn for_expression(&mut self, _enumerators: &Vec<Enumerator>, _body: &Box<ForBody>) { }
|
||||
fn lambda_expression(&mut self, _params: &Vec<FormalParam>, type_anno: &Option<TypeIdentifier>, body: &Block) { }
|
||||
fn list_literal(&mut self, _items: &Vec<Expression>) { }
|
||||
|
||||
|
||||
fn declaration(&mut self, _decl: &Declaration) { }
|
||||
fn func_signature(&mut self, _sig: &Signature) { }
|
||||
fn func_declaration(&mut self, _sig: &Signature, _block: &Vec<Statement>) { }
|
||||
fn type_declaration(&mut self, _name: &TypeSingletonName, _body: &TypeBody, _mutable: &bool) { }
|
||||
fn type_alias(&mut self, _alias: &Rc<String>, _name: &Rc<String>) { }
|
||||
fn binding(&mut self, _name: &Rc<String>, _constant: &bool, _expr: &Expression) { }
|
||||
fn impl_block(&mut self, _type_name: &TypeIdentifier, _interface_name: &Option<InterfaceName>, _block: &Vec<Declaration>) { }
|
||||
fn interface(&mut self, name: &Rc<String>, signatures: &Vec<Signature>) { }
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SchalaPrinter {
|
||||
s: String
|
||||
}
|
||||
|
||||
impl SchalaPrinter {
|
||||
fn new() -> SchalaPrinter {
|
||||
SchalaPrinter {
|
||||
s: format!("Schala source code:\n"),
|
||||
}
|
||||
}
|
||||
|
||||
fn done(self) -> String {
|
||||
self.s
|
||||
}
|
||||
}
|
||||
|
||||
impl ASTVisitor for SchalaPrinter {
|
||||
fn statement(&mut self, _: &Statement) {
|
||||
self.s.push_str("\n");
|
||||
}
|
||||
|
||||
fn expression(&mut self, _: &Expression) {
|
||||
self.s.push_str("some_expr");
|
||||
}
|
||||
|
||||
fn binding(&mut self, name: &Rc<String>, constant: &bool, _expr: &Expression) {
|
||||
self.s.push_str(&format!("let{} {} = {}",
|
||||
if *constant { "" } else { " mut" },
|
||||
name,
|
||||
"some_expr"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod visitor_tests {
|
||||
use ::tokenizing::{Token, tokenize};
|
||||
use ::parsing::ParseResult;
|
||||
use ::ast::AST;
|
||||
use super::*;
|
||||
|
||||
fn parse(input: &str) -> ParseResult<AST> {
|
||||
let tokens = tokenize(input);
|
||||
let mut parser = ::parsing::Parser::new(tokens);
|
||||
parser.parse()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let ast = parse("let a = 1 + 2; let b = 2 + 44;foo()").unwrap();
|
||||
let mut pp = SchalaPrinter::new();
|
||||
dispatch(&mut pp, &ast);
|
||||
let result = pp.done();
|
||||
assert_eq!(result, r#"Schala source code:
|
||||
let a = 1 + 2
|
||||
let b = 2 + 44
|
||||
foo()
|
||||
"#);
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,19 @@ use std::rc::Rc;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
|
||||
use self::Type::*; use self::TConstOld::*;
|
||||
use tokenizing::TokenKind;
|
||||
use self::BuiltinTypeSpecifier::*;
|
||||
use self::BuiltinTConst::*;
|
||||
|
||||
|
||||
//TODO get rid of these types and replace them with the right MonoType or whatever ones later
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Type {
|
||||
Const(TConstOld),
|
||||
Func(Box<Type>, Box<Type>),
|
||||
pub enum BuiltinTypeSpecifier {
|
||||
Const(BuiltinTConst),
|
||||
Func(Box<BuiltinTypeSpecifier>, Box<BuiltinTypeSpecifier>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum TConstOld {
|
||||
pub enum BuiltinTConst {
|
||||
Nat,
|
||||
Int,
|
||||
Float,
|
||||
@@ -21,7 +22,7 @@ pub enum TConstOld {
|
||||
Bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for Type {
|
||||
impl fmt::Display for BuiltinTypeSpecifier {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
@@ -39,16 +40,53 @@ impl BinOp {
|
||||
pub fn sigil(&self) -> &Rc<String> {
|
||||
&self.sigil
|
||||
}
|
||||
pub fn from_sigil_token(tok: &TokenKind) -> Option<BinOp> {
|
||||
use self::TokenKind::*;
|
||||
let s = match tok {
|
||||
Operator(op) => op,
|
||||
Period => ".",
|
||||
Pipe => "|",
|
||||
Slash => "/",
|
||||
LAngleBracket => "<",
|
||||
RAngleBracket => ">",
|
||||
_ => return None
|
||||
};
|
||||
Some(BinOp::from_sigil(s))
|
||||
}
|
||||
/*
|
||||
pub fn get_type(&self) -> Result<Type, String> {
|
||||
let s = self.sigil.as_str();
|
||||
BINOPS.get(s).map(|x| x.0.clone()).ok_or(format!("Binop {} not found", s))
|
||||
}
|
||||
*/
|
||||
pub fn min_precedence() -> i32 {
|
||||
i32::min_value()
|
||||
}
|
||||
pub fn get_precedence(op: &str) -> i32 {
|
||||
pub fn get_precedence_from_token(op: &TokenKind) -> Option<i32> {
|
||||
use self::TokenKind::*;
|
||||
let s = match op {
|
||||
Operator(op) => op,
|
||||
Period => ".",
|
||||
Pipe => "|",
|
||||
Slash => "/",
|
||||
LAngleBracket => "<",
|
||||
RAngleBracket => ">",
|
||||
_ => return None
|
||||
};
|
||||
let default = 10_000_000;
|
||||
BINOPS.get(op).map(|x| x.2.clone()).unwrap_or(default)
|
||||
Some(BINOPS.get(s).map(|x| x.2.clone()).unwrap_or_else(|| {
|
||||
println!("Warning: operator {} not defined", s);
|
||||
default
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn get_precedence(&self) -> i32 {
|
||||
let s: &str = &self.sigil;
|
||||
let default = 10_000_000;
|
||||
BINOPS.get(s).map(|x| x.2.clone()).unwrap_or_else(|| {
|
||||
println!("Warning: operator {} not defined", s);
|
||||
default
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,13 +105,15 @@ impl PrefixOp {
|
||||
pub fn is_prefix(op: &str) -> bool {
|
||||
PREFIX_OPS.get(op).is_some()
|
||||
}
|
||||
/*
|
||||
pub fn get_type(&self) -> Result<Type, String> {
|
||||
let s = self.sigil.as_str();
|
||||
PREFIX_OPS.get(s).map(|x| x.0.clone()).ok_or(format!("Prefix op {} not found", s))
|
||||
}
|
||||
*/
|
||||
}
|
||||
lazy_static! {
|
||||
static ref PREFIX_OPS: HashMap<&'static str, (Type, ())> =
|
||||
static ref PREFIX_OPS: HashMap<&'static str, (BuiltinTypeSpecifier, ())> =
|
||||
hashmap! {
|
||||
"+" => (Func(bx!(Const(Int)), bx!(Const(Int))), ()),
|
||||
"-" => (Func(bx!(Const(Int)), bx!(Const(Int))), ()),
|
||||
@@ -84,7 +124,7 @@ lazy_static! {
|
||||
/* the second tuple member is a placeholder for when I want to make evaluation rules tied to the
|
||||
* binop definition */
|
||||
lazy_static! {
|
||||
static ref BINOPS: HashMap<&'static str, (Type, (), i32)> =
|
||||
static ref BINOPS: HashMap<&'static str, (BuiltinTypeSpecifier, (), i32)> =
|
||||
hashmap! {
|
||||
"+" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 10),
|
||||
"-" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 10),
|
||||
@@ -96,5 +136,12 @@ lazy_static! {
|
||||
"^" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
|
||||
"&" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
|
||||
"|" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
|
||||
">" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
|
||||
">=" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
|
||||
"<" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
|
||||
"<=" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
|
||||
"==" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
|
||||
"=" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
|
||||
"<=>" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
|
||||
};
|
||||
}
|
||||
747
schala-lang/language/src/eval.rs
Normal file
747
schala-lang/language/src/eval.rs
Normal file
@@ -0,0 +1,747 @@
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::fmt::Write;
|
||||
use std::io;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use util::ScopeStack;
|
||||
use reduced_ast::{BoundVars, ReducedAST, Stmt, Expr, Lit, Func, Alternative, Subpattern};
|
||||
use symbol_table::{SymbolSpec, Symbol, SymbolTable};
|
||||
|
||||
pub struct State<'a> {
|
||||
values: ScopeStack<'a, Rc<String>, ValueEntry>,
|
||||
symbol_table_handle: Rc<RefCell<SymbolTable>>,
|
||||
}
|
||||
|
||||
macro_rules! builtin_binding {
|
||||
($name:expr, $values:expr) => {
|
||||
$values.insert(Rc::new(format!($name)), ValueEntry::Binding { constant: true, val: Node::Expr(Expr::Func(Func::BuiltIn(Rc::new(format!($name))))) });
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> State<'a> {
|
||||
pub fn new(symbol_table_handle: Rc<RefCell<SymbolTable>>) -> State<'a> {
|
||||
let mut values = ScopeStack::new(Some(format!("global")));
|
||||
builtin_binding!("print", values);
|
||||
builtin_binding!("println", values);
|
||||
builtin_binding!("getline", values);
|
||||
State { values, symbol_table_handle }
|
||||
}
|
||||
|
||||
pub fn debug_print(&self) -> String {
|
||||
format!("Values: {:?}", self.values)
|
||||
}
|
||||
|
||||
fn new_frame(&'a self, items: &'a Vec<Node>, bound_vars: &BoundVars) -> State<'a> {
|
||||
let mut inner_state = State {
|
||||
values: self.values.new_scope(None),
|
||||
symbol_table_handle: self.symbol_table_handle.clone(),
|
||||
};
|
||||
for (bound_var, val) in bound_vars.iter().zip(items.iter()) {
|
||||
if let Some(bv) = bound_var.as_ref() {
|
||||
inner_state.values.insert(bv.clone(), ValueEntry::Binding { constant: true, val: val.clone() });
|
||||
}
|
||||
}
|
||||
inner_state
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Node {
|
||||
Expr(Expr),
|
||||
PrimObject {
|
||||
name: Rc<String>,
|
||||
tag: usize,
|
||||
items: Vec<Node>,
|
||||
},
|
||||
PrimTuple {
|
||||
items: Vec<Node>
|
||||
}
|
||||
}
|
||||
|
||||
fn paren_wrapped_vec(terms: impl Iterator<Item=String>) -> String {
|
||||
let mut buf = String::new();
|
||||
write!(buf, "(").unwrap();
|
||||
for term in terms.map(|e| Some(e)).intersperse(None) {
|
||||
match term {
|
||||
Some(e) => write!(buf, "{}", e).unwrap(),
|
||||
None => write!(buf, ", ").unwrap(),
|
||||
};
|
||||
}
|
||||
write!(buf, ")").unwrap();
|
||||
buf
|
||||
}
|
||||
|
||||
|
||||
impl Node {
|
||||
fn to_repl(&self) -> String {
|
||||
match self {
|
||||
Node::Expr(e) => e.to_repl(),
|
||||
Node::PrimObject { name, items, .. } if items.len() == 0 => format!("{}", name),
|
||||
Node::PrimObject { name, items, .. } => format!("{}{}", name, paren_wrapped_vec(items.iter().map(|x| x.to_repl()))),
|
||||
Node::PrimTuple { items } => format!("{}", paren_wrapped_vec(items.iter().map(|x| x.to_repl()))),
|
||||
}
|
||||
}
|
||||
fn is_true(&self) -> bool {
|
||||
match self {
|
||||
Node::Expr(Expr::Lit(::reduced_ast::Lit::Bool(true))) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ValueEntry {
|
||||
Binding {
|
||||
constant: bool,
|
||||
val: /*FullyEvaluatedExpr*/ Node, //TODO make this use a subtype to represent fully evaluatedness
|
||||
}
|
||||
}
|
||||
|
||||
type EvalResult<T> = Result<T, String>;
|
||||
|
||||
impl Expr {
|
||||
fn to_node(self) -> Node {
|
||||
Node::Expr(self)
|
||||
}
|
||||
fn to_repl(&self) -> String {
|
||||
use self::Lit::*;
|
||||
use self::Func::*;
|
||||
|
||||
match self {
|
||||
Expr::Lit(ref l) => match l {
|
||||
Nat(n) => format!("{}", n),
|
||||
Int(i) => format!("{}", i),
|
||||
Float(f) => format!("{}", f),
|
||||
Bool(b) => format!("{}", b),
|
||||
StringLit(s) => format!("\"{}\"", s),
|
||||
},
|
||||
Expr::Func(f) => match f {
|
||||
BuiltIn(name) => format!("<built-in function '{}'>", name),
|
||||
UserDefined { name: None, .. } => format!("<function>"),
|
||||
UserDefined { name: Some(name), .. } => format!("<function '{}'>", name),
|
||||
},
|
||||
Expr::Constructor {
|
||||
type_name: _, name, arity, ..
|
||||
} => if *arity == 0 {
|
||||
format!("{}", name)
|
||||
} else {
|
||||
format!("<data constructor '{}'>", name)
|
||||
},
|
||||
Expr::Tuple(exprs) => paren_wrapped_vec(exprs.iter().map(|x| x.to_repl())),
|
||||
_ => format!("{:?}", self),
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_conditional_target_sigil(self, replacement: &Expr) -> Expr {
|
||||
use self::Expr::*;
|
||||
|
||||
match self {
|
||||
ConditionalTargetSigilValue => replacement.clone(),
|
||||
Unit | Lit(_) | Func(_) | Val(_) | Constructor { .. } |
|
||||
CaseMatch { .. } | UnimplementedSigilValue => self,
|
||||
Tuple(exprs) => Tuple(exprs.into_iter().map(|e| e.replace_conditional_target_sigil(replacement)).collect()),
|
||||
Call { f, args } => {
|
||||
let new_args = args.into_iter().map(|e| e.replace_conditional_target_sigil(replacement)).collect();
|
||||
Call { f, args: new_args }
|
||||
},
|
||||
Conditional { .. } => panic!("Dunno if I need this, but if so implement"),
|
||||
Assign { .. } => panic!("I'm pretty sure I don't need this"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> State<'a> {
|
||||
pub fn evaluate(&mut self, ast: ReducedAST, repl: bool) -> Vec<Result<String, String>> {
|
||||
let mut acc = vec![];
|
||||
|
||||
// handle prebindings
|
||||
for statement in ast.0.iter() {
|
||||
self.prebinding(statement);
|
||||
}
|
||||
|
||||
for statement in ast.0 {
|
||||
match self.statement(statement) {
|
||||
Ok(Some(ref output)) if repl => acc.push(Ok(output.to_repl())),
|
||||
Ok(_) => (),
|
||||
Err(error) => {
|
||||
acc.push(Err(format!("Runtime error: {}", error)));
|
||||
return acc;
|
||||
},
|
||||
}
|
||||
}
|
||||
acc
|
||||
}
|
||||
|
||||
fn prebinding(&mut self, stmt: &Stmt) {
|
||||
match stmt {
|
||||
Stmt::PreBinding { name, func } => {
|
||||
let v_entry = ValueEntry::Binding { constant: true, val: Node::Expr(Expr::Func(func.clone())) };
|
||||
self.values.insert(name.clone(), v_entry);
|
||||
},
|
||||
Stmt::Expr(_expr) => {
|
||||
//TODO have this support things like nested function defs
|
||||
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
|
||||
fn statement(&mut self, stmt: Stmt) -> EvalResult<Option<Node>> {
|
||||
match stmt {
|
||||
Stmt::Binding { name, constant, expr } => {
|
||||
let val = self.expression(Node::Expr(expr))?;
|
||||
self.values.insert(name.clone(), ValueEntry::Binding { constant, val });
|
||||
Ok(None)
|
||||
},
|
||||
Stmt::Expr(expr) => Ok(Some(self.expression(expr.to_node())?)),
|
||||
Stmt::PreBinding {..} | Stmt::Noop => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn block(&mut self, stmts: Vec<Stmt>) -> EvalResult<Node> {
|
||||
let mut ret = None;
|
||||
for stmt in stmts {
|
||||
ret = self.statement(stmt)?;
|
||||
}
|
||||
Ok(ret.unwrap_or(Node::Expr(Expr::Unit)))
|
||||
}
|
||||
|
||||
fn expression(&mut self, node: Node) -> EvalResult<Node> {
|
||||
use self::Expr::*;
|
||||
match node {
|
||||
t @ Node::PrimTuple { .. } => Ok(t),
|
||||
obj @ Node::PrimObject { .. } => Ok(obj),
|
||||
Node::Expr(expr) => match expr {
|
||||
literal @ Lit(_) => Ok(Node::Expr(literal)),
|
||||
Call { box f, args } => self.call_expression(f, args),
|
||||
Val(v) => self.value(v),
|
||||
Constructor { arity, ref name, tag, .. } if arity == 0 => Ok(Node::PrimObject { name: name.clone(), tag, items: vec![] }),
|
||||
constructor @ Constructor { .. } => Ok(Node::Expr(constructor)),
|
||||
func @ Func(_) => Ok(Node::Expr(func)),
|
||||
Tuple(exprs) => {
|
||||
let nodes = exprs.into_iter().map(|expr| self.expression(Node::Expr(expr))).collect::<Result<Vec<Node>,_>>()?;
|
||||
Ok(Node::PrimTuple { items: nodes })
|
||||
},
|
||||
Conditional { box cond, then_clause, else_clause } => self.conditional(cond, then_clause, else_clause),
|
||||
Assign { box val, box expr } => self.assign_expression(val, expr),
|
||||
Unit => Ok(Node::Expr(Unit)),
|
||||
CaseMatch { box cond, alternatives } => self.case_match_expression(cond, alternatives),
|
||||
ConditionalTargetSigilValue => Ok(Node::Expr(ConditionalTargetSigilValue)),
|
||||
UnimplementedSigilValue => Err(format!("Sigil value eval not implemented")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn call_expression(&mut self, f: Expr, args: Vec<Expr>) -> EvalResult<Node> {
|
||||
use self::Expr::*;
|
||||
match self.expression(Node::Expr(f))? {
|
||||
Node::Expr(Constructor { type_name, name, tag, arity }) => self.apply_data_constructor(type_name, name, tag, arity, args),
|
||||
Node::Expr(Func(f)) => self.apply_function(f, args),
|
||||
other => return Err(format!("Tried to call {:?} which is not a function or data constructor", other)),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_data_constructor(&mut self, _type_name: Rc<String>, name: Rc<String>, tag: usize, arity: usize, args: Vec<Expr>) -> EvalResult<Node> {
|
||||
if arity != args.len() {
|
||||
return Err(format!("Data constructor {} requires {} args", name, arity));
|
||||
}
|
||||
|
||||
let evaled_args = args.into_iter().map(|expr| self.expression(Node::Expr(expr))).collect::<Result<Vec<Node>,_>>()?;
|
||||
//let evaled_args = vec![];
|
||||
Ok(Node::PrimObject {
|
||||
name: name.clone(),
|
||||
items: evaled_args,
|
||||
tag
|
||||
})
|
||||
}
|
||||
|
||||
fn apply_function(&mut self, f: Func, args: Vec<Expr>) -> EvalResult<Node> {
|
||||
match f {
|
||||
Func::BuiltIn(sigil) => Ok(Node::Expr(self.apply_builtin(sigil, args)?)),
|
||||
Func::UserDefined { params, body, name } => {
|
||||
|
||||
if params.len() != args.len() {
|
||||
return Err(format!("calling a {}-argument function with {} args", params.len(), args.len()))
|
||||
}
|
||||
let mut func_state = State {
|
||||
values: self.values.new_scope(name.map(|n| format!("{}", n))),
|
||||
symbol_table_handle: self.symbol_table_handle.clone(),
|
||||
};
|
||||
for (param, val) in params.into_iter().zip(args.into_iter()) {
|
||||
let val = func_state.expression(Node::Expr(val))?;
|
||||
func_state.values.insert(param, ValueEntry::Binding { constant: true, val });
|
||||
}
|
||||
// TODO figure out function return semantics
|
||||
func_state.block(body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_builtin(&mut self, name: Rc<String>, args: Vec<Expr>) -> EvalResult<Expr> {
|
||||
use self::Expr::*;
|
||||
use self::Lit::*;
|
||||
let evaled_args: Result<Vec<Expr>, String> = args.into_iter().map(|arg| {
|
||||
match self.expression(Node::Expr(arg)) {
|
||||
Ok(Node::Expr(e)) => Ok(e),
|
||||
Ok(Node::PrimTuple { .. }) => Err(format!("Trying to apply a builtin to a tuple")),
|
||||
Ok(Node::PrimObject { .. }) => Err(format!("Trying to apply a builtin to a primitive object")),
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}).collect();
|
||||
let evaled_args = evaled_args?;
|
||||
|
||||
Ok(match (name.as_str(), evaled_args.as_slice()) {
|
||||
/* binops */
|
||||
("+", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l + r)),
|
||||
("++", &[Lit(StringLit(ref s1)), Lit(StringLit(ref s2))]) => Lit(StringLit(Rc::new(format!("{}{}", s1, s2)))),
|
||||
("-", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l - r)),
|
||||
("*", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l * r)),
|
||||
("/", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Float((l as f64)/ (r as f64))),
|
||||
("//", &[Lit(Nat(l)), Lit(Nat(r))]) => if r == 0 {
|
||||
return Err(format!("divide by zero"));
|
||||
} else {
|
||||
Lit(Nat(l / r))
|
||||
},
|
||||
("%", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l % r)),
|
||||
("^", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l ^ r)),
|
||||
("&", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l & r)),
|
||||
("|", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l | r)),
|
||||
|
||||
/* comparisons */
|
||||
("==", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Bool(l == r)),
|
||||
("==", &[Lit(Int(l)), Lit(Int(r))]) => Lit(Bool(l == r)),
|
||||
("==", &[Lit(Float(l)), Lit(Float(r))]) => Lit(Bool(l == r)),
|
||||
("==", &[Lit(Bool(l)), Lit(Bool(r))]) => Lit(Bool(l == r)),
|
||||
("==", &[Lit(StringLit(ref l)), Lit(StringLit(ref r))]) => Lit(Bool(l == r)),
|
||||
|
||||
("<", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Bool(l < r)),
|
||||
("<", &[Lit(Int(l)), Lit(Int(r))]) => Lit(Bool(l < r)),
|
||||
("<", &[Lit(Float(l)), Lit(Float(r))]) => Lit(Bool(l < r)),
|
||||
|
||||
("<=", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Bool(l <= r)),
|
||||
("<=", &[Lit(Int(l)), Lit(Int(r))]) => Lit(Bool(l <= r)),
|
||||
("<=", &[Lit(Float(l)), Lit(Float(r))]) => Lit(Bool(l <= r)),
|
||||
|
||||
(">", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Bool(l > r)),
|
||||
(">", &[Lit(Int(l)), Lit(Int(r))]) => Lit(Bool(l > r)),
|
||||
(">", &[Lit(Float(l)), Lit(Float(r))]) => Lit(Bool(l > r)),
|
||||
|
||||
(">=", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Bool(l >= r)),
|
||||
(">=", &[Lit(Int(l)), Lit(Int(r))]) => Lit(Bool(l >= r)),
|
||||
(">=", &[Lit(Float(l)), Lit(Float(r))]) => Lit(Bool(l >= r)),
|
||||
|
||||
/* prefix ops */
|
||||
("!", &[Lit(Bool(true))]) => Lit(Bool(false)),
|
||||
("!", &[Lit(Bool(false))]) => Lit(Bool(true)),
|
||||
("-", &[Lit(Nat(n))]) => Lit(Int(-1*(n as i64))),
|
||||
("-", &[Lit(Int(n))]) => Lit(Int(-1*(n as i64))),
|
||||
("+", &[Lit(Int(n))]) => Lit(Int(n)),
|
||||
("+", &[Lit(Nat(n))]) => Lit(Nat(n)),
|
||||
|
||||
|
||||
/* builtin functions */
|
||||
("print", &[ref anything]) => {
|
||||
print!("{}", anything.to_repl());
|
||||
Expr::Unit
|
||||
},
|
||||
("println", &[ref anything]) => {
|
||||
println!("{}", anything.to_repl());
|
||||
Expr::Unit
|
||||
},
|
||||
("getline", &[]) => {
|
||||
let mut buf = String::new();
|
||||
io::stdin().read_line(&mut buf).expect("Error readling line in 'getline'");
|
||||
Lit(StringLit(Rc::new(buf.trim().to_string())))
|
||||
},
|
||||
(x, args) => return Err(format!("bad or unimplemented builtin {:?} | {:?}", x, args)),
|
||||
})
|
||||
}
|
||||
|
||||
fn conditional(&mut self, cond: Expr, then_clause: Vec<Stmt>, else_clause: Vec<Stmt>) -> EvalResult<Node> {
|
||||
let cond = self.expression(Node::Expr(cond))?;
|
||||
Ok(match cond {
|
||||
Node::Expr(Expr::Lit(Lit::Bool(true))) => self.block(then_clause)?,
|
||||
Node::Expr(Expr::Lit(Lit::Bool(false))) => self.block(else_clause)?,
|
||||
_ => return Err(format!("Conditional with non-boolean condition"))
|
||||
})
|
||||
}
|
||||
|
||||
fn assign_expression(&mut self, val: Expr, expr: Expr) -> EvalResult<Node> {
|
||||
let name = match val {
|
||||
Expr::Val(name) => name,
|
||||
_ => return Err(format!("Trying to assign to a non-value")),
|
||||
};
|
||||
|
||||
let constant = match self.values.lookup(&name) {
|
||||
None => return Err(format!("Constant {} is undefined", name)),
|
||||
Some(ValueEntry::Binding { constant, .. }) => constant.clone(),
|
||||
};
|
||||
if constant {
|
||||
return Err(format!("trying to update {}, a non-mutable binding", name));
|
||||
}
|
||||
let val = self.expression(Node::Expr(expr))?;
|
||||
self.values.insert(name.clone(), ValueEntry::Binding { constant: false, val });
|
||||
Ok(Node::Expr(Expr::Unit))
|
||||
}
|
||||
|
||||
fn guard_passes(&mut self, guard: &Option<Expr>, cond: &Node) -> EvalResult<bool> {
|
||||
if let Some(ref guard_expr) = guard {
|
||||
let guard_expr = match cond {
|
||||
Node::Expr(ref e) => guard_expr.clone().replace_conditional_target_sigil(e),
|
||||
_ => guard_expr.clone()
|
||||
};
|
||||
Ok(self.expression(guard_expr.to_node())?.is_true())
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
fn case_match_expression(&mut self, cond: Expr, alternatives: Vec<Alternative>) -> EvalResult<Node> {
|
||||
|
||||
//TODO need to handle recursive subpatterns
|
||||
let all_subpatterns_pass = |state: &mut State, subpatterns: &Vec<Option<Subpattern>>, items: &Vec<Node>| -> EvalResult<bool> {
|
||||
|
||||
if subpatterns.len() == 0 {
|
||||
return Ok(true)
|
||||
}
|
||||
|
||||
if items.len() != subpatterns.len() {
|
||||
return Err(format!("Subpattern length isn't correct items {} subpatterns {}", items.len(), subpatterns.len()));
|
||||
}
|
||||
|
||||
for (maybe_subp, cond) in subpatterns.iter().zip(items.iter()) {
|
||||
if let Some(subp) = maybe_subp {
|
||||
if !state.guard_passes(&subp.guard, &cond)? {
|
||||
return Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
};
|
||||
|
||||
let cond = self.expression(Node::Expr(cond))?;
|
||||
for alt in alternatives {
|
||||
// no matter what type of condition we have, ignore alternative if the guard evaluates false
|
||||
if !self.guard_passes(&alt.guard, &cond)? {
|
||||
continue;
|
||||
}
|
||||
|
||||
match cond {
|
||||
Node::PrimObject { ref tag, ref items, .. } => {
|
||||
if alt.tag.map(|t| t == *tag).unwrap_or(true) {
|
||||
let mut inner_state = self.new_frame(items, &alt.bound_vars);
|
||||
if all_subpatterns_pass(&mut inner_state, &alt.subpatterns, items)? {
|
||||
return inner_state.block(alt.item);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
},
|
||||
Node::PrimTuple { ref items } => {
|
||||
let mut inner_state = self.new_frame(items, &alt.bound_vars);
|
||||
if all_subpatterns_pass(&mut inner_state, &alt.subpatterns, items)? {
|
||||
return inner_state.block(alt.item);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
},
|
||||
Node::Expr(ref _e) => {
|
||||
if let None = alt.tag {
|
||||
return self.block(alt.item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(format!("{:?} failed pattern match", cond))
|
||||
}
|
||||
|
||||
fn value(&mut self, name: Rc<String>) -> EvalResult<Node> {
|
||||
use self::ValueEntry::*;
|
||||
use self::Func::*;
|
||||
//TODO add a layer of indirection here to talk to the symbol table first, and only then look up
|
||||
//in the values table
|
||||
|
||||
let symbol_table = self.symbol_table_handle.borrow();
|
||||
let value = symbol_table.lookup_by_name(&name);
|
||||
Ok(match value {
|
||||
Some(Symbol { name, spec }) => match spec {
|
||||
//TODO I'll need this type_name later to do a table lookup
|
||||
SymbolSpec::DataConstructor { type_name: _type_name, type_args, .. } => {
|
||||
if type_args.len() == 0 {
|
||||
Node::PrimObject { name: name.clone(), tag: 0, items: vec![] }
|
||||
} else {
|
||||
return Err(format!("This data constructor thing not done"))
|
||||
}
|
||||
},
|
||||
SymbolSpec::Func(_) => match self.values.lookup(&name) {
|
||||
Some(Binding { val: Node::Expr(Expr::Func(UserDefined { name, params, body })), .. }) => {
|
||||
Node::Expr(Expr::Func(UserDefined { name: name.clone(), params: params.clone(), body: body.clone() }))
|
||||
},
|
||||
_ => unreachable!(),
|
||||
},
|
||||
},
|
||||
/* see if it's an ordinary variable TODO make variables go in symbol table */
|
||||
None => match self.values.lookup(&name) {
|
||||
Some(Binding { val, .. }) => val.clone(),
|
||||
None => return Err(format!("Couldn't find value {}", name)),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod eval_tests {
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use tokenizing::{Token, tokenize};
|
||||
use ::parsing::ParseResult;
|
||||
use ::ast::AST;
|
||||
use symbol_table::SymbolTable;
|
||||
use eval::State;
|
||||
|
||||
fn parse(tokens: Vec<Token>) -> ParseResult<AST> {
|
||||
let mut parser = ::parsing::Parser::new(tokens);
|
||||
parser.parse()
|
||||
}
|
||||
|
||||
fn evaluate_all_outputs(input: &str) -> Vec<Result<String, String>> {
|
||||
let symbol_table = Rc::new(RefCell::new(SymbolTable::new()));
|
||||
let mut state = State::new(symbol_table);
|
||||
let ast = parse(tokenize(input)).unwrap();
|
||||
state.symbol_table_handle.borrow_mut().add_top_level_symbols(&ast).unwrap();
|
||||
let reduced = ast.reduce(&state.symbol_table_handle.borrow());
|
||||
let all_output = state.evaluate(reduced, true);
|
||||
all_output
|
||||
}
|
||||
|
||||
macro_rules! test_in_fresh_env {
|
||||
($string:expr, $correct:expr) => {
|
||||
{
|
||||
let all_output = evaluate_all_outputs($string);
|
||||
let ref output = all_output.last().unwrap();
|
||||
assert_eq!(**output, Ok($correct.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_eval() {
|
||||
test_in_fresh_env!("1 + 2", "3");
|
||||
test_in_fresh_env!("let mut a = 1; a = 2", "Unit");
|
||||
test_in_fresh_env!("let mut a = 1; a = 2; a", "2");
|
||||
test_in_fresh_env!(r#"("a", 1 + 2)"#, r#"("a", 3)"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_eval() {
|
||||
test_in_fresh_env!("fn oi(x) { x + 1 }; oi(4)", "5");
|
||||
test_in_fresh_env!("fn oi(x) { x + 1 }; oi(1+2)", "4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scopes() {
|
||||
let scope_ok = r#"
|
||||
let a = 20
|
||||
fn haha() {
|
||||
let a = 10
|
||||
a
|
||||
}
|
||||
haha()
|
||||
"#;
|
||||
test_in_fresh_env!(scope_ok, "10");
|
||||
let scope_ok = r#"
|
||||
let a = 20
|
||||
fn haha() {
|
||||
let a = 10
|
||||
a
|
||||
}
|
||||
a
|
||||
"#;
|
||||
test_in_fresh_env!(scope_ok, "20");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_is_patterns() {
|
||||
let source = r#"
|
||||
type Option<T> = Some(T) | None
|
||||
let x = Some(9); if x is Some(q) then { q } else { 0 }"#;
|
||||
test_in_fresh_env!(source, "9");
|
||||
|
||||
let source = r#"
|
||||
type Option<T> = Some(T) | None
|
||||
let x = None; if x is Some(q) then { q } else { 0 }"#;
|
||||
test_in_fresh_env!(source, "0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_if_matching() {
|
||||
let source = r#"
|
||||
type Option<T> = Some(T) | None
|
||||
let a = None
|
||||
if a { is None -> 4, is Some(x) -> x }
|
||||
"#;
|
||||
test_in_fresh_env!(source, "4");
|
||||
|
||||
let source = r#"
|
||||
type Option<T> = Some(T) | None
|
||||
let a = Some(99)
|
||||
if a { is None -> 4, is Some(x) -> x }
|
||||
"#;
|
||||
test_in_fresh_env!(source, "99");
|
||||
|
||||
let source = r#"
|
||||
let a = 10
|
||||
if a { is 10 -> "x", is 4 -> "y" }
|
||||
"#;
|
||||
test_in_fresh_env!(source, "\"x\"");
|
||||
|
||||
let source = r#"
|
||||
let a = 10
|
||||
if a { is 15 -> "x", is 10 -> "y" }
|
||||
"#;
|
||||
test_in_fresh_env!(source, "\"y\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_pattern() {
|
||||
let source = r#"
|
||||
let a = "foo"
|
||||
if a { is "foo" -> "x", is _ -> "y" }
|
||||
"#;
|
||||
test_in_fresh_env!(source, "\"x\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boolean_pattern() {
|
||||
let source = r#"
|
||||
let a = true
|
||||
if a {
|
||||
is true -> "x",
|
||||
is false -> "y"
|
||||
}
|
||||
"#;
|
||||
test_in_fresh_env!(source, "\"x\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boolean_pattern_2() {
|
||||
let source = r#"
|
||||
let a = false
|
||||
if a { is true -> "x", is false -> "y" }
|
||||
"#;
|
||||
test_in_fresh_env!(source, "\"y\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_pattern() {
|
||||
let source = r#"
|
||||
type Option<T> = Some(T) | None
|
||||
if Some(10) {
|
||||
is _ -> "hella"
|
||||
}
|
||||
"#;
|
||||
test_in_fresh_env!(source, "\"hella\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_pattern() {
|
||||
let source = r#"
|
||||
if (1, 2) {
|
||||
is (1, x) -> x,
|
||||
is _ -> 99
|
||||
}
|
||||
"#;
|
||||
test_in_fresh_env!(source, 2);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn tuple_pattern_2() {
|
||||
let source = r#"
|
||||
if (1, 2) {
|
||||
is (10, x) -> x,
|
||||
is (y, x) -> x + y
|
||||
}
|
||||
"#;
|
||||
test_in_fresh_env!(source, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_pattern_3() {
|
||||
let source = r#"
|
||||
if (1, 5) {
|
||||
is (10, x) -> x,
|
||||
is (1, x) -> x
|
||||
}
|
||||
"#;
|
||||
test_in_fresh_env!(source, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_pattern_4() {
|
||||
let source = r#"
|
||||
if (1, 5) {
|
||||
is (10, x) -> x,
|
||||
is (1, x) -> x,
|
||||
}
|
||||
"#;
|
||||
test_in_fresh_env!(source, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prim_obj_pattern() {
|
||||
let source = r#"
|
||||
type Stuff = Mulch(Nat) | Jugs(Nat, String) | Mardok
|
||||
let a = Mulch(20)
|
||||
let b = Jugs(1, "haha")
|
||||
let c = Mardok
|
||||
|
||||
let x = if a {
|
||||
is Mulch(20) -> "x",
|
||||
is _ -> "ERR"
|
||||
}
|
||||
|
||||
let y = if b {
|
||||
is Mulch(n) -> "ERR",
|
||||
is Jugs(2, _) -> "ERR",
|
||||
is Jugs(1, s) -> s,
|
||||
is _ -> "ERR",
|
||||
}
|
||||
|
||||
let z = if c {
|
||||
is Jugs(_, _) -> "ERR",
|
||||
is Mardok -> "NIGH",
|
||||
is _ -> "ERR",
|
||||
}
|
||||
|
||||
(x, y, z)
|
||||
"#;
|
||||
test_in_fresh_env!(source, r#"("x", "haha", "NIGH")"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_lambda_syntax() {
|
||||
let source = r#"
|
||||
let q = \(x, y) { x * y }
|
||||
let x = q(5,2)
|
||||
let y = \(m, n, o) { m + n + o }(1,2,3)
|
||||
(x, y)
|
||||
"#;
|
||||
test_in_fresh_env!(source, r"(10, 6)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lambda_syntax_2() {
|
||||
let source = r#"
|
||||
fn milta() {
|
||||
\(x) { x + 33 }
|
||||
}
|
||||
milta()(10)
|
||||
"#;
|
||||
test_in_fresh_env!(source, "43");
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,22 @@
|
||||
#![feature(trace_macros)]
|
||||
#![feature(custom_attribute)]
|
||||
#![feature(unrestricted_attribute_tokens)]
|
||||
#![feature(slice_patterns, box_patterns, box_syntax)]
|
||||
#![feature(proc_macro)]
|
||||
|
||||
//! `schala-lang` is where the Schala programming language is actually implemented.
|
||||
//! It defines the `Schala` type, which contains the state for a Schala REPL, and implements
|
||||
//! `ProgrammingLanguageInterface` and the chain of compiler passes for it.
|
||||
|
||||
extern crate itertools;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate maplit;
|
||||
#[macro_use]
|
||||
extern crate schala_repl;
|
||||
#[macro_use]
|
||||
extern crate schala_codegen;
|
||||
extern crate schala_repl_codegen;
|
||||
#[macro_use]
|
||||
extern crate schala_lang_codegen;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
@@ -22,9 +29,11 @@ macro_rules! bx {
|
||||
}
|
||||
|
||||
mod util;
|
||||
mod source_map;
|
||||
mod builtin;
|
||||
mod tokenizing;
|
||||
mod ast;
|
||||
mod ast_visitor;
|
||||
mod parsing;
|
||||
mod symbol_table;
|
||||
mod typechecking;
|
||||
@@ -36,27 +45,44 @@ mod eval;
|
||||
#[LanguageName = "Schala"]
|
||||
#[SourceFileExtension = "schala"]
|
||||
#[PipelineSteps(tokenizing, parsing(compact,expanded,trace), symbol_table, typechecking, ast_reducing, eval)]
|
||||
#[DocMethod = get_doc]
|
||||
#[HandleCustomInterpreterDirectives = handle_custom_interpreter_directives]
|
||||
/// All bits of state necessary to parse and execute a Schala program are stored in this struct
|
||||
/// `state` represents the execution state for the AST-walking interpreter, the other fields
|
||||
/// should be self-explanatory.
|
||||
pub struct Schala {
|
||||
state: eval::State<'static>,
|
||||
symbol_table: Rc<RefCell<symbol_table::SymbolTable>>,
|
||||
type_context: typechecking::TypeContext<'static>,
|
||||
active_parser: Option<parsing::Parser>,
|
||||
}
|
||||
|
||||
impl Schala {
|
||||
fn get_doc(&self, commands: &Vec<&str>) -> Option<String> {
|
||||
Some(format!("Documentation on commands: {:?}", commands))
|
||||
}
|
||||
|
||||
fn handle_custom_interpreter_directives(&mut self, commands: &Vec<&str>) -> Option<String> {
|
||||
Some(format!("Schala-lang command: {:?} not supported", commands.get(0)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Schala {
|
||||
/// Creates a new Schala environment *without* any prelude.
|
||||
fn new_blank_env() -> Schala {
|
||||
let symbols = Rc::new(RefCell::new(symbol_table::SymbolTable::new()));
|
||||
Schala {
|
||||
symbol_table: symbols.clone(),
|
||||
type_context: typechecking::TypeContext::new(symbols.clone()),
|
||||
state: eval::State::new(symbols),
|
||||
type_context: typechecking::TypeContext::new(),
|
||||
active_parser: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new Schala environment with the standard prelude, which is defined as ordinary
|
||||
/// Schala code in the file `prelude.schala`
|
||||
pub fn new() -> Schala {
|
||||
let prelude = r#"
|
||||
type Option<T> = Some(T) | None
|
||||
"#;
|
||||
let prelude = include_str!("prelude.schala");
|
||||
let mut s = Schala::new_blank_env();
|
||||
s.execute_pipeline(prelude, &EvalOptions::default());
|
||||
s
|
||||
@@ -66,7 +92,7 @@ type Option<T> = Some(T) | None
|
||||
fn tokenizing(_handle: &mut Schala, input: &str, comp: Option<&mut UnfinishedComputation>) -> Result<Vec<tokenizing::Token>, String> {
|
||||
let tokens = tokenizing::tokenize(input);
|
||||
comp.map(|comp| {
|
||||
let token_string = tokens.iter().map(|t| format!("{:?}<L:{},C:{}>", t.token_type, t.offset.0, t.offset.1)).join(", ");
|
||||
let token_string = tokens.iter().map(|t| format!("{:?}<L:{},C:{}>", t.kind, t.offset.0, t.offset.1)).join(", ");
|
||||
comp.add_artifact(TraceArtifact::new("tokens", token_string));
|
||||
});
|
||||
|
||||
@@ -78,9 +104,17 @@ fn tokenizing(_handle: &mut Schala, input: &str, comp: Option<&mut UnfinishedCom
|
||||
}
|
||||
}
|
||||
|
||||
fn parsing(_handle: &mut Schala, input: Vec<tokenizing::Token>, comp: Option<&mut UnfinishedComputation>) -> Result<ast::AST, String> {
|
||||
fn parsing(handle: &mut Schala, input: Vec<tokenizing::Token>, comp: Option<&mut UnfinishedComputation>) -> Result<ast::AST, String> {
|
||||
use parsing::Parser;
|
||||
|
||||
let mut parser = match handle.active_parser.take() {
|
||||
None => Parser::new(input),
|
||||
Some(parser) => parser
|
||||
};
|
||||
|
||||
let ast = parser.parse();
|
||||
let trace = parser.format_parse_trace();
|
||||
|
||||
let (ast, trace) = parsing::parse(input);
|
||||
comp.map(|comp| {
|
||||
//TODO need to control which of these debug stages get added
|
||||
let opt = comp.cur_debug_options.get(0).map(|s| s.clone());
|
||||
@@ -108,22 +142,14 @@ fn symbol_table(handle: &mut Schala, input: ast::AST, comp: Option<&mut Unfinish
|
||||
}
|
||||
|
||||
fn typechecking(handle: &mut Schala, input: ast::AST, comp: Option<&mut UnfinishedComputation>) -> Result<ast::AST, String> {
|
||||
match handle.type_context.type_check_ast(&input) {
|
||||
Ok(ty) => {
|
||||
comp.map(|c| {
|
||||
c.add_artifact(TraceArtifact::new("type_table", format!("{}", handle.type_context.debug_types())));
|
||||
c.add_artifact(TraceArtifact::new("type_check", format!("{:?}", ty)));
|
||||
});
|
||||
Ok(input)
|
||||
},
|
||||
Err(msg) => {
|
||||
comp.map(|comp| {
|
||||
comp.add_artifact(TraceArtifact::new("type_table", format!("{}", handle.type_context.debug_types())));
|
||||
comp.add_artifact(TraceArtifact::new("type_check", format!("Type error: {:?}", msg)));
|
||||
});
|
||||
Ok(input)
|
||||
}
|
||||
}
|
||||
let result = handle.type_context.typecheck(&input);
|
||||
|
||||
comp.map(|comp| {
|
||||
let artifact = TraceArtifact::new("type", format!("{:?}", result));
|
||||
comp.add_artifact(artifact);
|
||||
});
|
||||
|
||||
Ok(input)
|
||||
}
|
||||
|
||||
fn ast_reducing(handle: &mut Schala, input: ast::AST, comp: Option<&mut UnfinishedComputation>) -> Result<reduced_ast::ReducedAST, String> {
|
||||
File diff suppressed because it is too large
Load Diff
13
schala-lang/language/src/prelude.schala
Normal file
13
schala-lang/language/src/prelude.schala
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
type Option<T> = Some(T) | None
|
||||
type Color = Red | Green | Blue
|
||||
type Ord = LT | EQ | GT
|
||||
|
||||
|
||||
fn map(input: Option<T>, func: Func): Option<T> {
|
||||
if input {
|
||||
is Some(x) -> Some(func(x)),
|
||||
is None -> None,
|
||||
}
|
||||
}
|
||||
|
||||
394
schala-lang/language/src/reduced_ast.rs
Normal file
394
schala-lang/language/src/reduced_ast.rs
Normal file
@@ -0,0 +1,394 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use ast::*;
|
||||
use symbol_table::{Symbol, SymbolSpec, SymbolTable};
|
||||
use builtin::{BinOp, PrefixOp};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ReducedAST(pub Vec<Stmt>);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Stmt {
|
||||
PreBinding {
|
||||
name: Rc<String>,
|
||||
func: Func,
|
||||
},
|
||||
Binding {
|
||||
name: Rc<String>,
|
||||
constant: bool,
|
||||
expr: Expr,
|
||||
},
|
||||
Expr(Expr),
|
||||
Noop,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Expr {
|
||||
Unit,
|
||||
Lit(Lit),
|
||||
Tuple(Vec<Expr>),
|
||||
Func(Func),
|
||||
Val(Rc<String>),
|
||||
Constructor {
|
||||
type_name: Rc<String>,
|
||||
name: Rc<String>,
|
||||
tag: usize,
|
||||
arity: usize,
|
||||
},
|
||||
Call {
|
||||
f: Box<Expr>,
|
||||
args: Vec<Expr>,
|
||||
},
|
||||
Assign {
|
||||
val: Box<Expr>,
|
||||
expr: Box<Expr>,
|
||||
},
|
||||
Conditional {
|
||||
cond: Box<Expr>,
|
||||
then_clause: Vec<Stmt>,
|
||||
else_clause: Vec<Stmt>,
|
||||
},
|
||||
ConditionalTargetSigilValue,
|
||||
CaseMatch {
|
||||
cond: Box<Expr>,
|
||||
alternatives: Vec<Alternative>
|
||||
},
|
||||
UnimplementedSigilValue
|
||||
}
|
||||
|
||||
pub type BoundVars = Vec<Option<Rc<String>>>; //remember that order matters here
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Alternative {
|
||||
pub tag: Option<usize>,
|
||||
pub subpatterns: Vec<Option<Subpattern>>,
|
||||
pub guard: Option<Expr>,
|
||||
pub bound_vars: BoundVars,
|
||||
pub item: Vec<Stmt>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Subpattern {
|
||||
pub tag: Option<usize>,
|
||||
pub subpatterns: Vec<Option<Subpattern>>,
|
||||
pub bound_vars: BoundVars,
|
||||
pub guard: Option<Expr>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Lit {
|
||||
Nat(u64),
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
StringLit(Rc<String>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Func {
|
||||
BuiltIn(Rc<String>),
|
||||
UserDefined {
|
||||
name: Option<Rc<String>>,
|
||||
params: Vec<Rc<String>>,
|
||||
body: Vec<Stmt>,
|
||||
}
|
||||
}
|
||||
|
||||
impl AST {
|
||||
pub fn reduce(&self, symbol_table: &SymbolTable) -> ReducedAST {
|
||||
let mut output = vec![];
|
||||
for statement in self.0.iter() {
|
||||
output.push(statement.reduce(symbol_table));
|
||||
}
|
||||
ReducedAST(output)
|
||||
}
|
||||
}
|
||||
|
||||
impl Statement {
|
||||
fn reduce(&self, symbol_table: &SymbolTable) -> Stmt {
|
||||
use ast::Statement::*;
|
||||
match self {
|
||||
ExpressionStatement(expr) => Stmt::Expr(expr.reduce(symbol_table)),
|
||||
Declaration(decl) => decl.reduce(symbol_table),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
fn reduce(&self, symbol_table: &SymbolTable) -> Expr {
|
||||
use ast::ExpressionType::*;
|
||||
let ref input = self.0;
|
||||
match input {
|
||||
NatLiteral(n) => Expr::Lit(Lit::Nat(*n)),
|
||||
FloatLiteral(f) => Expr::Lit(Lit::Float(*f)),
|
||||
StringLiteral(s) => Expr::Lit(Lit::StringLit(s.clone())),
|
||||
BoolLiteral(b) => Expr::Lit(Lit::Bool(*b)),
|
||||
BinExp(binop, lhs, rhs) => binop.reduce(symbol_table, lhs, rhs),
|
||||
PrefixExp(op, arg) => op.reduce(symbol_table, arg),
|
||||
Value(name) => match symbol_table.lookup_by_name(name) {
|
||||
Some(Symbol { spec: SymbolSpec::DataConstructor { index, type_args, type_name}, .. }) => Expr::Constructor {
|
||||
type_name: type_name.clone(),
|
||||
name: name.clone(),
|
||||
tag: index.clone(),
|
||||
arity: type_args.len(),
|
||||
},
|
||||
_ => Expr::Val(name.clone()),
|
||||
},
|
||||
Call { f, arguments } => Expr::Call {
|
||||
f: Box::new(f.reduce(symbol_table)),
|
||||
args: arguments.iter().map(|arg| arg.reduce(symbol_table)).collect(),
|
||||
},
|
||||
TupleLiteral(exprs) => Expr::Tuple(exprs.iter().map(|e| e.reduce(symbol_table)).collect()),
|
||||
IfExpression { discriminator, body } => reduce_if_expression(discriminator, body, symbol_table),
|
||||
Lambda { params, body, .. } => reduce_lambda(params, body, symbol_table),
|
||||
NamedStruct { .. } => Expr::UnimplementedSigilValue,
|
||||
Index { .. } => Expr::UnimplementedSigilValue,
|
||||
WhileExpression { .. } => Expr::UnimplementedSigilValue,
|
||||
ForExpression { .. } => Expr::UnimplementedSigilValue,
|
||||
ListLiteral { .. } => Expr::UnimplementedSigilValue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reduce_lambda(params: &Vec<FormalParam>, body: &Block, symbol_table: &SymbolTable) -> Expr {
|
||||
Expr::Func(Func::UserDefined {
|
||||
name: None,
|
||||
params: params.iter().map(|param| param.0.clone()).collect(),
|
||||
body: body.iter().map(|stmt| stmt.reduce(symbol_table)).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
fn reduce_if_expression(discriminator: &Discriminator, body: &IfExpressionBody, symbol_table: &SymbolTable) -> Expr {
|
||||
let cond = Box::new(match *discriminator {
|
||||
Discriminator::Simple(ref expr) => expr.reduce(symbol_table),
|
||||
Discriminator::BinOp(ref _expr, ref _binop) => panic!("Can't yet handle binop discriminators")
|
||||
});
|
||||
match *body {
|
||||
IfExpressionBody::SimpleConditional(ref then_clause, ref else_clause) => {
|
||||
let then_clause = then_clause.iter().map(|expr| expr.reduce(symbol_table)).collect();
|
||||
let else_clause = match else_clause {
|
||||
None => vec![],
|
||||
Some(stmts) => stmts.iter().map(|expr| expr.reduce(symbol_table)).collect(),
|
||||
};
|
||||
Expr::Conditional { cond, then_clause, else_clause }
|
||||
},
|
||||
IfExpressionBody::SimplePatternMatch(ref pat, ref then_clause, ref else_clause) => {
|
||||
let then_clause = then_clause.iter().map(|expr| expr.reduce(symbol_table)).collect();
|
||||
let else_clause = match else_clause {
|
||||
None => vec![],
|
||||
Some(stmts) => stmts.iter().map(|expr| expr.reduce(symbol_table)).collect(),
|
||||
};
|
||||
|
||||
let alternatives = vec![
|
||||
pat.to_alternative(then_clause, symbol_table),
|
||||
Alternative {
|
||||
tag: None,
|
||||
subpatterns: vec![],
|
||||
bound_vars: vec![],
|
||||
guard: None,
|
||||
item: else_clause
|
||||
},
|
||||
];
|
||||
|
||||
Expr::CaseMatch {
|
||||
cond,
|
||||
alternatives,
|
||||
}
|
||||
},
|
||||
IfExpressionBody::GuardList(ref guard_arms) => {
|
||||
let mut alternatives = vec![];
|
||||
for arm in guard_arms {
|
||||
match arm.guard {
|
||||
Guard::Pat(ref p) => {
|
||||
let item = arm.body.iter().map(|expr| expr.reduce(symbol_table)).collect();
|
||||
let alt = p.to_alternative(item, symbol_table);
|
||||
alternatives.push(alt);
|
||||
},
|
||||
Guard::HalfExpr(HalfExpr { op: _, expr: _ }) => {
|
||||
return Expr::UnimplementedSigilValue
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::CaseMatch { cond, alternatives }
|
||||
}
|
||||
}
|
||||
}
|
||||
/* ig var pat
|
||||
* x is SomeBigOldEnum(_, x, Some(t))
|
||||
*/
|
||||
|
||||
fn handle_symbol(symbol: Option<&Symbol>, inner_patterns: &Vec<Pattern>, symbol_table: &SymbolTable) -> Subpattern {
|
||||
use self::Pattern::*;
|
||||
let tag = symbol.map(|symbol| match symbol.spec {
|
||||
SymbolSpec::DataConstructor { index, .. } => index.clone(),
|
||||
_ => panic!("Symbol is not a data constructor - this should've been caught in type-checking"),
|
||||
});
|
||||
let bound_vars = inner_patterns.iter().map(|p| match p {
|
||||
Literal(PatternLiteral::VarPattern(var)) => Some(var.clone()),
|
||||
_ => None,
|
||||
}).collect();
|
||||
|
||||
let subpatterns = inner_patterns.iter().map(|p| match p {
|
||||
Ignored => None,
|
||||
Literal(PatternLiteral::VarPattern(_)) => None,
|
||||
Literal(other) => Some(other.to_subpattern(symbol_table)),
|
||||
tp @ TuplePattern(_) => Some(tp.to_subpattern(symbol_table)),
|
||||
ts @ TupleStruct(_, _) => Some(ts.to_subpattern(symbol_table)),
|
||||
Record(..) => unimplemented!(),
|
||||
}).collect();
|
||||
|
||||
let guard = None;
|
||||
/*
|
||||
let guard_equality_exprs: Vec<Expr> = subpatterns.iter().map(|p| match p {
|
||||
Literal(lit) => match lit {
|
||||
_ => unimplemented!()
|
||||
},
|
||||
_ => unimplemented!()
|
||||
}).collect();
|
||||
*/
|
||||
|
||||
Subpattern {
|
||||
tag,
|
||||
subpatterns,
|
||||
guard,
|
||||
bound_vars,
|
||||
}
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
fn to_alternative(&self, item: Vec<Stmt>, symbol_table: &SymbolTable) -> Alternative {
|
||||
let s = self.to_subpattern(symbol_table);
|
||||
Alternative {
|
||||
tag: s.tag,
|
||||
subpatterns: s.subpatterns,
|
||||
bound_vars: s.bound_vars,
|
||||
guard: s.guard,
|
||||
item
|
||||
}
|
||||
}
|
||||
|
||||
fn to_subpattern(&self, symbol_table: &SymbolTable) -> Subpattern {
|
||||
use self::Pattern::*;
|
||||
match self {
|
||||
TupleStruct(name, inner_patterns) => {
|
||||
let symbol = symbol_table.lookup_by_name(name).expect(&format!("Symbol {} not found", name));
|
||||
handle_symbol(Some(symbol), inner_patterns, symbol_table)
|
||||
},
|
||||
TuplePattern(inner_patterns) => handle_symbol(None, inner_patterns, symbol_table),
|
||||
Record(_name, _pairs) => {
|
||||
unimplemented!()
|
||||
},
|
||||
Ignored => Subpattern { tag: None, subpatterns: vec![], guard: None, bound_vars: vec![] },
|
||||
Literal(lit) => lit.to_subpattern(symbol_table),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PatternLiteral {
|
||||
fn to_subpattern(&self, symbol_table: &SymbolTable) -> Subpattern {
|
||||
use self::PatternLiteral::*;
|
||||
match self {
|
||||
NumPattern { neg, num } => {
|
||||
let comparison = Expr::Lit(match (neg, num) {
|
||||
(false, ExpressionType::NatLiteral(n)) => Lit::Nat(*n),
|
||||
(false, ExpressionType::FloatLiteral(f)) => Lit::Float(*f),
|
||||
(true, ExpressionType::NatLiteral(n)) => Lit::Int(-1*(*n as i64)),
|
||||
(true, ExpressionType::FloatLiteral(f)) => Lit::Float(-1.0*f),
|
||||
_ => panic!("This should never happen")
|
||||
});
|
||||
let guard = Some(Expr::Call {
|
||||
f: Box::new(Expr::Func(Func::BuiltIn(Rc::new("==".to_string())))),
|
||||
args: vec![comparison, Expr::ConditionalTargetSigilValue],
|
||||
});
|
||||
Subpattern {
|
||||
tag: None,
|
||||
subpatterns: vec![],
|
||||
guard,
|
||||
bound_vars: vec![],
|
||||
}
|
||||
},
|
||||
StringPattern(s) => {
|
||||
let guard = Some(Expr::Call {
|
||||
f: Box::new(Expr::Func(Func::BuiltIn(Rc::new("==".to_string())))),
|
||||
args: vec![Expr::Lit(Lit::StringLit(s.clone())), Expr::ConditionalTargetSigilValue]
|
||||
});
|
||||
|
||||
Subpattern {
|
||||
tag: None,
|
||||
subpatterns: vec![],
|
||||
guard,
|
||||
bound_vars: vec![],
|
||||
}
|
||||
},
|
||||
BoolPattern(b) => {
|
||||
let guard = Some(if *b {
|
||||
Expr::ConditionalTargetSigilValue
|
||||
} else {
|
||||
Expr::Call {
|
||||
f: Box::new(Expr::Func(Func::BuiltIn(Rc::new("!".to_string())))),
|
||||
args: vec![Expr::ConditionalTargetSigilValue]
|
||||
}
|
||||
});
|
||||
Subpattern {
|
||||
tag: None,
|
||||
subpatterns: vec![],
|
||||
guard,
|
||||
bound_vars: vec![],
|
||||
}
|
||||
},
|
||||
VarPattern(var) => match symbol_table.lookup_by_name(var) {
|
||||
Some(symbol) => handle_symbol(Some(symbol), &vec![], symbol_table),
|
||||
None => Subpattern {
|
||||
tag: None,
|
||||
subpatterns: vec![],
|
||||
guard: None,
|
||||
bound_vars: vec![Some(var.clone())],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Declaration {
|
||||
fn reduce(&self, symbol_table: &SymbolTable) -> Stmt {
|
||||
use self::Declaration::*;
|
||||
use ::ast::Signature;
|
||||
match self {
|
||||
Binding {name, constant, expr } => Stmt::Binding { name: name.clone(), constant: *constant, expr: expr.reduce(symbol_table) },
|
||||
FuncDecl(Signature { name, params, .. }, statements) => Stmt::PreBinding {
|
||||
name: name.clone(),
|
||||
func: Func::UserDefined {
|
||||
name: Some(name.clone()),
|
||||
params: params.iter().map(|param| param.0.clone()).collect(),
|
||||
body: statements.iter().map(|stmt| stmt.reduce(symbol_table)).collect(),
|
||||
}
|
||||
},
|
||||
TypeDecl { .. } => Stmt::Noop,
|
||||
TypeAlias(_, _) => Stmt::Noop,
|
||||
Interface { .. } => Stmt::Noop,
|
||||
Impl { .. } => Stmt::Expr(Expr::UnimplementedSigilValue),
|
||||
_ => Stmt::Expr(Expr::UnimplementedSigilValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BinOp {
|
||||
fn reduce(&self, symbol_table: &SymbolTable, lhs: &Box<Expression>, rhs: &Box<Expression>) -> Expr {
|
||||
if **self.sigil() == "=" {
|
||||
Expr::Assign {
|
||||
val: Box::new(lhs.reduce(symbol_table)),
|
||||
expr: Box::new(rhs.reduce(symbol_table)),
|
||||
}
|
||||
} else {
|
||||
let f = Box::new(Expr::Func(Func::BuiltIn(self.sigil().clone())));
|
||||
Expr::Call { f, args: vec![lhs.reduce(symbol_table), rhs.reduce(symbol_table)]}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrefixOp {
|
||||
fn reduce(&self, symbol_table: &SymbolTable, arg: &Box<Expression>) -> Expr {
|
||||
let f = Box::new(Expr::Func(Func::BuiltIn(self.sigil().clone())));
|
||||
Expr::Call { f, args: vec![arg.reduce(symbol_table)]}
|
||||
}
|
||||
}
|
||||
27
schala-lang/language/src/source_map.rs
Normal file
27
schala-lang/language/src/source_map.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SourceMap<T> {
|
||||
pub node: T,
|
||||
pub data: Option<SourceData>
|
||||
}
|
||||
|
||||
impl<T> SourceMap<T> {
|
||||
pub fn get(&self) -> &T {
|
||||
&self.node
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn get_source_data(&self) -> Option<SourceData> {
|
||||
self.data.clone()
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SourceData {
|
||||
pub line_number: usize,
|
||||
pub char_idx: usize
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -11,10 +11,16 @@ pub struct SymbolTable {
|
||||
pub values: HashMap<Rc<String>, Symbol> //TODO this will eventually have real type information
|
||||
}
|
||||
|
||||
//TODO add various types of lookups here, maybe multiple hash tables internally? also make values
|
||||
//non-public
|
||||
impl SymbolTable {
|
||||
pub fn new() -> SymbolTable {
|
||||
SymbolTable { values: HashMap::new() }
|
||||
}
|
||||
|
||||
pub fn lookup_by_name(&self, name: &Rc<String>) -> Option<&Symbol> {
|
||||
self.values.get(name)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -33,6 +39,7 @@ impl fmt::Display for Symbol {
|
||||
pub enum SymbolSpec {
|
||||
Func(Vec<TypeName>),
|
||||
DataConstructor {
|
||||
index: usize,
|
||||
type_name: Rc<String>,
|
||||
type_args: Vec<Rc<String>>,
|
||||
},
|
||||
@@ -43,7 +50,7 @@ impl fmt::Display for SymbolSpec {
|
||||
use self::SymbolSpec::*;
|
||||
match self {
|
||||
Func(type_names) => write!(f, "Func({:?})", type_names),
|
||||
DataConstructor { type_name, type_args } => write!(f, "DataConstructor({:?} -> {})", type_args, type_name),
|
||||
DataConstructor { index, type_name, type_args } => write!(f, "DataConstructor(idx: {})({:?} -> {})", index, type_args, type_name),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,7 +59,7 @@ impl SymbolTable {
|
||||
/* note: this adds names for *forward reference* but doesn't actually create any types. solve that problem
|
||||
* later */
|
||||
pub fn add_top_level_symbols(&mut self, ast: &ast::AST) -> Result<(), String> {
|
||||
use self::ast::{Statement, TypeName, Variant, TypeSingletonName, TypeBody};
|
||||
use self::ast::{Statement, TypeIdentifier, Variant, TypeSingletonName, TypeBody};
|
||||
use self::ast::Declaration::*;
|
||||
for statement in ast.0.iter() {
|
||||
if let Statement::Declaration(decl) = statement {
|
||||
@@ -62,7 +69,7 @@ impl SymbolTable {
|
||||
let mut types = vec![];
|
||||
for param in signature.params.iter() {
|
||||
match param {
|
||||
(_, Some(ty)) => {
|
||||
(_, Some(_ty)) => {
|
||||
//TODO eventually handle this case different
|
||||
types.push(Rc::new(format!("{}", ch)));
|
||||
ch = ((ch as u8) + 1) as char;
|
||||
@@ -79,11 +86,13 @@ impl SymbolTable {
|
||||
Symbol { name: signature.name.clone(), spec }
|
||||
);
|
||||
},
|
||||
TypeDecl { name: TypeSingletonName { name, params}, body: TypeBody(variants), mutable } => {
|
||||
for var in variants {
|
||||
//TODO figure out why _params isn't being used here
|
||||
TypeDecl { name: TypeSingletonName { name, params: _params}, body: TypeBody(variants), mutable: _mutable, } => {
|
||||
for (index, var) in variants.iter().enumerate() {
|
||||
match var {
|
||||
Variant::UnitStruct(variant_name) => {
|
||||
let spec = SymbolSpec::DataConstructor {
|
||||
index,
|
||||
type_name: name.clone(),
|
||||
type_args: vec![],
|
||||
};
|
||||
@@ -91,10 +100,11 @@ impl SymbolTable {
|
||||
},
|
||||
Variant::TupleStruct(variant_name, tuple_members) => {
|
||||
let type_args = tuple_members.iter().map(|type_name| match type_name {
|
||||
TypeName::Singleton(TypeSingletonName { name, ..}) => name.clone(),
|
||||
TypeName::Tuple(_) => unimplemented!(),
|
||||
TypeIdentifier::Singleton(TypeSingletonName { name, ..}) => name.clone(),
|
||||
TypeIdentifier::Tuple(_) => unimplemented!(),
|
||||
}).collect();
|
||||
let spec = SymbolSpec::DataConstructor {
|
||||
let spec = SymbolSpec::DataConstructor {
|
||||
index,
|
||||
type_name: name.clone(),
|
||||
type_args
|
||||
};
|
||||
@@ -5,14 +5,14 @@ use std::iter::{Iterator, Peekable};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum TokenType {
|
||||
pub enum TokenKind {
|
||||
Newline, Semicolon,
|
||||
|
||||
LParen, RParen,
|
||||
LSquareBracket, RSquareBracket,
|
||||
LAngleBracket, RAngleBracket,
|
||||
LCurlyBrace, RCurlyBrace,
|
||||
Pipe,
|
||||
Pipe, Backslash,
|
||||
|
||||
Comma, Period, Colon, Underscore,
|
||||
Slash,
|
||||
@@ -27,9 +27,9 @@ pub enum TokenType {
|
||||
|
||||
Error(String),
|
||||
}
|
||||
use self::TokenType::*;
|
||||
use self::TokenKind::*;
|
||||
|
||||
impl fmt::Display for TokenType {
|
||||
impl fmt::Display for TokenKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
&Operator(ref s) => write!(f, "Operator({})", **s),
|
||||
@@ -49,7 +49,6 @@ pub enum Kw {
|
||||
Is,
|
||||
Func,
|
||||
For, While,
|
||||
Match,
|
||||
Const, Let, In,
|
||||
Mut,
|
||||
Return,
|
||||
@@ -88,19 +87,28 @@ lazy_static! {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Token {
|
||||
pub token_type: TokenType,
|
||||
pub kind: TokenKind,
|
||||
pub offset: (usize, usize),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TokenMetadata {
|
||||
pub offset: (usize, usize)
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn get_error(&self) -> Option<String> {
|
||||
match self.token_type {
|
||||
TokenType::Error(ref s) => Some(s.clone()),
|
||||
match self.kind {
|
||||
TokenKind::Error(ref s) => Some(s.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn to_string_with_metadata(&self) -> String {
|
||||
format!("{}(L:{},c:{})", self.token_type, self.offset.0, self.offset.1)
|
||||
format!("{}(L:{},c:{})", self.kind, self.offset.0, self.offset.1)
|
||||
}
|
||||
|
||||
pub fn get_kind(&self) -> TokenKind {
|
||||
self.kind.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +130,7 @@ pub fn tokenize(input: &str) -> Vec<Token> {
|
||||
.peekable();
|
||||
|
||||
while let Some((line_idx, ch_idx, c)) = input.next() {
|
||||
let cur_tok_type = match c {
|
||||
let cur_tok_kind = match c {
|
||||
'/' => match input.peek().map(|t| t.2) {
|
||||
Some('/') => {
|
||||
while let Some((_, _, c)) = input.next() {
|
||||
@@ -158,17 +166,18 @@ pub fn tokenize(input: &str) -> Vec<Token> {
|
||||
'{' => LCurlyBrace, '}' => RCurlyBrace,
|
||||
'[' => LSquareBracket, ']' => RSquareBracket,
|
||||
'"' => handle_quote(&mut input),
|
||||
'\\' => Backslash,
|
||||
c if c.is_digit(10) => handle_digit(c, &mut input),
|
||||
c if c.is_alphabetic() || c == '_' => handle_alphabetic(c, &mut input), //TODO I'll probably have to rewrite this if I care about types being uppercase, also type parameterization
|
||||
c if is_operator(&c) => handle_operator(c, &mut input),
|
||||
unknown => Error(format!("Unexpected character: {}", unknown)),
|
||||
};
|
||||
tokens.push(Token { token_type: cur_tok_type, offset: (line_idx, ch_idx) });
|
||||
tokens.push(Token { kind: cur_tok_kind, offset: (line_idx, ch_idx) });
|
||||
}
|
||||
tokens
|
||||
}
|
||||
|
||||
fn handle_digit(c: char, input: &mut Peekable<impl Iterator<Item=CharData>>) -> TokenType {
|
||||
fn handle_digit(c: char, input: &mut Peekable<impl Iterator<Item=CharData>>) -> TokenKind {
|
||||
if c == '0' && input.peek().map_or(false, |&(_, _, c)| { c == 'x' }) {
|
||||
input.next();
|
||||
let rest: String = input.peeking_take_while(|&(_, _, ref c)| c.is_digit(16) || *c == '_').map(|(_, _, c)| { c }).collect();
|
||||
@@ -183,7 +192,7 @@ fn handle_digit(c: char, input: &mut Peekable<impl Iterator<Item=CharData>>) ->
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_quote(input: &mut Peekable<impl Iterator<Item=CharData>>) -> TokenType {
|
||||
fn handle_quote(input: &mut Peekable<impl Iterator<Item=CharData>>) -> TokenKind {
|
||||
let mut buf = String::new();
|
||||
loop {
|
||||
match input.next().map(|(_, _, c)| { c }) {
|
||||
@@ -202,22 +211,22 @@ fn handle_quote(input: &mut Peekable<impl Iterator<Item=CharData>>) -> TokenType
|
||||
}
|
||||
},
|
||||
Some(c) => buf.push(c),
|
||||
None => return TokenType::Error(format!("Unclosed string")),
|
||||
None => return TokenKind::Error(format!("Unclosed string")),
|
||||
}
|
||||
}
|
||||
TokenType::StrLiteral(Rc::new(buf))
|
||||
TokenKind::StrLiteral(Rc::new(buf))
|
||||
}
|
||||
|
||||
fn handle_alphabetic(c: char, input: &mut Peekable<impl Iterator<Item=CharData>>) -> TokenType {
|
||||
fn handle_alphabetic(c: char, input: &mut Peekable<impl Iterator<Item=CharData>>) -> TokenKind {
|
||||
let mut buf = String::new();
|
||||
buf.push(c);
|
||||
if c == '_' && input.peek().map(|&(_, _, c)| { !c.is_alphabetic() }).unwrap_or(true) {
|
||||
return TokenType::Underscore
|
||||
return TokenKind::Underscore
|
||||
}
|
||||
|
||||
loop {
|
||||
match input.peek().map(|&(_, _, c)| { c }) {
|
||||
Some(c) if c.is_alphanumeric() => {
|
||||
Some(c) if c.is_alphanumeric() || c == '_' => {
|
||||
input.next();
|
||||
buf.push(c);
|
||||
},
|
||||
@@ -226,12 +235,12 @@ fn handle_alphabetic(c: char, input: &mut Peekable<impl Iterator<Item=CharData>>
|
||||
}
|
||||
|
||||
match KEYWORDS.get(buf.as_str()) {
|
||||
Some(kw) => TokenType::Keyword(*kw),
|
||||
None => TokenType::Identifier(Rc::new(buf)),
|
||||
Some(kw) => TokenKind::Keyword(*kw),
|
||||
None => TokenKind::Identifier(Rc::new(buf)),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_operator(c: char, input: &mut Peekable<impl Iterator<Item=CharData>>) -> TokenType {
|
||||
fn handle_operator(c: char, input: &mut Peekable<impl Iterator<Item=CharData>>) -> TokenKind {
|
||||
match c {
|
||||
'<' | '>' | '|' | '.' => {
|
||||
let ref next = input.peek().map(|&(_, _, c)| { c });
|
||||
@@ -276,7 +285,7 @@ fn handle_operator(c: char, input: &mut Peekable<impl Iterator<Item=CharData>>)
|
||||
}
|
||||
}
|
||||
}
|
||||
TokenType::Operator(Rc::new(buf))
|
||||
TokenKind::Operator(Rc::new(buf))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -291,26 +300,29 @@ mod schala_tokenizer_tests {
|
||||
#[test]
|
||||
fn tokens() {
|
||||
let a = tokenize("let a: A<B> = c ++ d");
|
||||
let token_types: Vec<TokenType> = a.into_iter().map(move |t| t.token_type).collect();
|
||||
assert_eq!(token_types, vec![Keyword(Let), ident!("a"), Colon, ident!("A"),
|
||||
let token_kinds: Vec<TokenKind> = a.into_iter().map(move |t| t.kind).collect();
|
||||
assert_eq!(token_kinds, vec![Keyword(Let), ident!("a"), Colon, ident!("A"),
|
||||
LAngleBracket, ident!("B"), RAngleBracket, op!("="), ident!("c"), op!("++"), ident!("d")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn underscores() {
|
||||
let token_types: Vec<TokenType> = tokenize("4_8").into_iter().map(move |t| t.token_type).collect();
|
||||
assert_eq!(token_types, vec![digit!("4"), Underscore, digit!("8")]);
|
||||
let token_kinds: Vec<TokenKind> = tokenize("4_8").into_iter().map(move |t| t.kind).collect();
|
||||
assert_eq!(token_kinds, vec![digit!("4"), Underscore, digit!("8")]);
|
||||
|
||||
let token_kinds2: Vec<TokenKind> = tokenize("aba_yo").into_iter().map(move |t| t.kind).collect();
|
||||
assert_eq!(token_kinds2, vec![ident!("aba_yo")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comments() {
|
||||
let token_types: Vec<TokenType> = tokenize("1 + /* hella /* bro */ */ 2").into_iter().map(move |t| t.token_type).collect();
|
||||
assert_eq!(token_types, vec![digit!("1"), op!("+"), digit!("2")]);
|
||||
let token_kinds: Vec<TokenKind> = tokenize("1 + /* hella /* bro */ */ 2").into_iter().map(move |t| t.kind).collect();
|
||||
assert_eq!(token_kinds, vec![digit!("1"), op!("+"), digit!("2")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn backtick_operators() {
|
||||
let token_types: Vec<TokenType> = tokenize("1 `plus` 2").into_iter().map(move |t| t.token_type).collect();
|
||||
assert_eq!(token_types, vec![digit!("1"), op!("plus"), digit!("2")]);
|
||||
let token_kinds: Vec<TokenKind> = tokenize("1 `plus` 2").into_iter().map(move |t| t.kind).collect();
|
||||
assert_eq!(token_kinds, vec![digit!("1"), op!("plus"), digit!("2")]);
|
||||
}
|
||||
}
|
||||
254
schala-lang/language/src/typechecking.rs
Normal file
254
schala-lang/language/src/typechecking.rs
Normal file
@@ -0,0 +1,254 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use ast::*;
|
||||
use util::ScopeStack;
|
||||
|
||||
pub type TypeName = Rc<String>;
|
||||
|
||||
pub struct TypeContext<'a> {
|
||||
variable_map: ScopeStack<'a, Rc<String>, Type<TVar>>,
|
||||
evar_count: u32
|
||||
}
|
||||
|
||||
/// `InferResult` is the monad in which type inference takes place.
|
||||
type InferResult<T> = Result<T, TypeError>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct TypeError { msg: String }
|
||||
|
||||
impl TypeError {
|
||||
fn new<A>(msg: &str) -> InferResult<A> {
|
||||
Err(TypeError { msg: msg.to_string() })
|
||||
}
|
||||
}
|
||||
|
||||
/// `Type` is parameterized by whether the type variables can be just universal, or universal or
|
||||
/// existential.
|
||||
#[derive(Debug, Clone)]
|
||||
enum Type<A> {
|
||||
Var(A),
|
||||
Const(TConst),
|
||||
Arrow(Box<Type<A>>, Box<Type<A>>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum TVar {
|
||||
Univ(UVar),
|
||||
Exist(ExistentialVar)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct UVar(Rc<String>);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ExistentialVar(u32);
|
||||
|
||||
impl Type<UVar> {
|
||||
fn to_tvar(&self) -> Type<TVar> {
|
||||
match self {
|
||||
Type::Var(UVar(name)) => Type::Var(TVar::Univ(UVar(name.clone()))),
|
||||
Type::Const(ref c) => Type::Const(c.clone()),
|
||||
Type::Arrow(a, b) => Type::Arrow(
|
||||
Box::new(a.to_tvar()),
|
||||
Box::new(b.to_tvar())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Type<TVar> {
|
||||
fn skolemize(&self) -> Type<UVar> {
|
||||
match self {
|
||||
Type::Var(TVar::Univ(uvar)) => Type::Var(uvar.clone()),
|
||||
Type::Var(TVar::Exist(_)) => Type::Var(UVar(Rc::new(format!("sk")))),
|
||||
Type::Const(ref c) => Type::Const(c.clone()),
|
||||
Type::Arrow(a, b) => Type::Arrow(
|
||||
Box::new(a.skolemize()),
|
||||
Box::new(b.skolemize())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeIdentifier {
|
||||
fn to_monotype(&self) -> Type<UVar> {
|
||||
match self {
|
||||
TypeIdentifier::Tuple(_) => Type::Const(TConst::Nat),
|
||||
TypeIdentifier::Singleton(TypeSingletonName { name, .. }) => {
|
||||
match &name[..] {
|
||||
"Nat" => Type::Const(TConst::Nat),
|
||||
"Int" => Type::Const(TConst::Int),
|
||||
"Float" => Type::Const(TConst::Float),
|
||||
"Bool" => Type::Const(TConst::Bool),
|
||||
"String" => Type::Const(TConst::StringT),
|
||||
_ => Type::Const(TConst::Nat),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum TConst {
|
||||
User(Rc<String>),
|
||||
Unit,
|
||||
Nat,
|
||||
Int,
|
||||
Float,
|
||||
StringT,
|
||||
Bool,
|
||||
}
|
||||
|
||||
impl TConst {
|
||||
fn user(name: &str) -> TConst {
|
||||
TConst::User(Rc::new(name.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TypeContext<'a> {
|
||||
pub fn new() -> TypeContext<'a> {
|
||||
TypeContext {
|
||||
variable_map: ScopeStack::new(None),
|
||||
evar_count: 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn typecheck(&mut self, ast: &AST) -> Result<String, String> {
|
||||
match self.infer_ast(ast) {
|
||||
Ok(t) => Ok(format!("{:?}", t)),
|
||||
Err(err) => Err(format!("Type error: {:?}", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TypeContext<'a> {
|
||||
fn infer_ast(&mut self, ast: &AST) -> InferResult<Type<UVar>> {
|
||||
self.infer_block(&ast.0)
|
||||
}
|
||||
|
||||
fn infer_statement(&mut self, stmt: &Statement) -> InferResult<Type<UVar>> {
|
||||
match stmt {
|
||||
Statement::ExpressionStatement(ref expr) => self.infer_expr(expr),
|
||||
Statement::Declaration(ref decl) => self.infer_decl(decl),
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_expr(&mut self, expr: &Expression) -> InferResult<Type<UVar>> {
|
||||
match expr {
|
||||
Expression(expr_type, Some(type_anno)) => {
|
||||
let tx = self.infer_expr_type(expr_type)?;
|
||||
let ty = type_anno.to_monotype();
|
||||
self.unify(&ty.to_tvar(), &tx.to_tvar()).map(|x| x.skolemize())
|
||||
},
|
||||
Expression(expr_type, None) => self.infer_expr_type(expr_type)
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_decl(&mut self, _decl: &Declaration) -> InferResult<Type<UVar>> {
|
||||
Ok(Type::Const(TConst::user("unimplemented")))
|
||||
}
|
||||
|
||||
fn infer_expr_type(&mut self, expr_type: &ExpressionType) -> InferResult<Type<UVar>> {
|
||||
use self::ExpressionType::*;
|
||||
Ok(match expr_type {
|
||||
NatLiteral(_) => Type::Const(TConst::Nat),
|
||||
FloatLiteral(_) => Type::Const(TConst::Float),
|
||||
StringLiteral(_) => Type::Const(TConst::StringT),
|
||||
BoolLiteral(_) => Type::Const(TConst::Bool),
|
||||
Value(name) => {
|
||||
//TODO handle the distinction between 0-arg constructors and variables at some point
|
||||
// need symbol table for that
|
||||
match self.variable_map.lookup(name) {
|
||||
Some(ty) => ty.clone().skolemize(),
|
||||
None => return TypeError::new(&format!("Variable {} not found", name))
|
||||
}
|
||||
},
|
||||
IfExpression { discriminator, body } => self.infer_if_expr(discriminator, body)?,
|
||||
Call { f, arguments } => {
|
||||
let tf = self.infer_expr(f)?; //has to be an Arrow Type
|
||||
let targ = self.infer_expr(&arguments[0])?; // TODO make this work with functions with more than one arg
|
||||
match tf {
|
||||
Type::Arrow(t1, t2) => {
|
||||
self.unify(&t1.to_tvar(), &targ.to_tvar())?;
|
||||
*t2.clone()
|
||||
},
|
||||
_ => return TypeError::new("not a function")
|
||||
}
|
||||
},
|
||||
|
||||
Lambda { params, .. } => {
|
||||
|
||||
let _arg_type = match ¶ms[0] {
|
||||
(_, Some(type_anno)) => type_anno.to_monotype().to_tvar(),
|
||||
(_, None) => self.allocate_existential(),
|
||||
};
|
||||
//let _result_type = unimplemented!();
|
||||
return TypeError::new("Unimplemented");
|
||||
|
||||
//Type::Arrow(Box::new(arg_type), Box::new(result_type))
|
||||
}
|
||||
_ => Type::Const(TConst::user("unimplemented"))
|
||||
})
|
||||
}
|
||||
|
||||
fn infer_if_expr(&mut self, discriminator: &Discriminator, body: &IfExpressionBody) -> InferResult<Type<UVar>> {
|
||||
let _test = match discriminator {
|
||||
Discriminator::Simple(expr) => expr,
|
||||
_ => return TypeError::new("Dame desu")
|
||||
};
|
||||
|
||||
let (_then_clause, _maybe_else_clause) = match body {
|
||||
IfExpressionBody::SimpleConditional(a, b) => (a, b),
|
||||
_ => return TypeError::new("Dont work")
|
||||
};
|
||||
|
||||
TypeError::new("Not implemented")
|
||||
}
|
||||
|
||||
fn infer_block(&mut self, block: &Block) -> InferResult<Type<UVar>> {
|
||||
let mut output = Type::Const(TConst::Unit);
|
||||
for statement in block.iter() {
|
||||
output = self.infer_statement(statement)?;
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn unify(&mut self, _t1: &Type<TVar>, _t2: &Type<TVar>) -> InferResult<Type<TVar>> {
|
||||
TypeError::new("not implemented")
|
||||
}
|
||||
|
||||
fn allocate_existential(&mut self) -> Type<TVar> {
|
||||
let n = self.evar_count;
|
||||
self.evar_count += 1;
|
||||
Type::Var(TVar::Exist(ExistentialVar(n)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn parse(input: &str) -> AST {
|
||||
let tokens: Vec<::tokenizing::Token> = ::tokenizing::tokenize(input);
|
||||
let mut parser = ::parsing::Parser::new(tokens);
|
||||
parser.parse().unwrap()
|
||||
}
|
||||
|
||||
macro_rules! type_test {
|
||||
($input:expr, $correct:expr) => {
|
||||
{
|
||||
let mut tc = TypeContext::new();
|
||||
let ast = parse($input);
|
||||
tc.add_symbols(&ast);
|
||||
assert_eq!($correct, tc.type_check(&ast).unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[test]
|
||||
fn basic_inference() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,16 @@ use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use std::cmp::Eq;
|
||||
|
||||
//TODO rename this ScopeStack
|
||||
#[derive(Default, Debug)]
|
||||
pub struct StateStack<'a, T: 'a, V: 'a> where T: Hash + Eq {
|
||||
parent: Option<&'a StateStack<'a, T, V>>,
|
||||
pub struct ScopeStack<'a, T: 'a, V: 'a> where T: Hash + Eq {
|
||||
parent: Option<&'a ScopeStack<'a, T, V>>,
|
||||
values: HashMap<T, V>,
|
||||
scope_name: Option<String>
|
||||
}
|
||||
|
||||
impl<'a, T, V> StateStack<'a, T, V> where T: Hash + Eq {
|
||||
pub fn new(name: Option<String>) -> StateStack<'a, T, V> where T: Hash + Eq {
|
||||
StateStack {
|
||||
impl<'a, T, V> ScopeStack<'a, T, V> where T: Hash + Eq {
|
||||
pub fn new(name: Option<String>) -> ScopeStack<'a, T, V> where T: Hash + Eq {
|
||||
ScopeStack {
|
||||
parent: None,
|
||||
values: HashMap::new(),
|
||||
scope_name: name
|
||||
@@ -28,14 +27,15 @@ impl<'a, T, V> StateStack<'a, T, V> where T: Hash + Eq {
|
||||
(Some(value), _) => Some(value),
|
||||
}
|
||||
}
|
||||
//TODO rename new_scope
|
||||
pub fn new_frame(&'a self, name: Option<String>) -> StateStack<'a, T, V> where T: Hash + Eq {
|
||||
StateStack {
|
||||
|
||||
pub fn new_scope(&'a self, name: Option<String>) -> ScopeStack<'a, T, V> where T: Hash + Eq {
|
||||
ScopeStack {
|
||||
parent: Some(self),
|
||||
values: HashMap::default(),
|
||||
scope_name: name,
|
||||
}
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn get_name(&self) -> Option<&String> {
|
||||
self.scope_name.as_ref()
|
||||
}
|
||||
@@ -1,375 +0,0 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::fmt::Write;
|
||||
use std::io;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use util::StateStack;
|
||||
use reduced_ast::{ReducedAST, Stmt, Expr, Lit, Func};
|
||||
use symbol_table::{SymbolSpec, Symbol, SymbolTable};
|
||||
|
||||
pub struct State<'a> {
|
||||
values: StateStack<'a, Rc<String>, ValueEntry>,
|
||||
symbol_table_handle: Rc<RefCell<SymbolTable>>,
|
||||
}
|
||||
|
||||
macro_rules! builtin_binding {
|
||||
($name:expr, $values:expr) => {
|
||||
$values.insert(Rc::new(format!($name)), ValueEntry::Binding { constant: true, val: Expr::Func(Func::BuiltIn(Rc::new(format!($name)))) });
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> State<'a> {
|
||||
pub fn new(symbol_table_handle: Rc<RefCell<SymbolTable>>) -> State<'a> {
|
||||
let mut values = StateStack::new(Some(format!("global")));
|
||||
builtin_binding!("print", values);
|
||||
builtin_binding!("println", values);
|
||||
builtin_binding!("getline", values);
|
||||
State { values, symbol_table_handle }
|
||||
}
|
||||
|
||||
pub fn debug_print(&self) -> String {
|
||||
format!("Values: {:?}", self.values)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ValueEntry {
|
||||
Binding {
|
||||
constant: bool,
|
||||
val: /*FullyEvaluatedExpr*/ Expr,
|
||||
}
|
||||
}
|
||||
|
||||
type EvalResult<T> = Result<T, String>;
|
||||
|
||||
|
||||
impl Expr {
|
||||
fn to_repl(&self) -> String {
|
||||
use self::Lit::*;
|
||||
use self::Func::*;
|
||||
fn paren_wrapped_vec(exprs: &Vec<Expr>) -> String {
|
||||
let mut buf = String::new();
|
||||
write!(buf, "(").unwrap();
|
||||
for term in exprs.iter().map(|e| Some(e)).intersperse(None) {
|
||||
match term {
|
||||
Some(e) => write!(buf, "{}", e.to_repl()).unwrap(),
|
||||
None => write!(buf, ", ").unwrap(),
|
||||
};
|
||||
}
|
||||
write!(buf, ")").unwrap();
|
||||
buf
|
||||
}
|
||||
|
||||
match self {
|
||||
Expr::Lit(ref l) => match l {
|
||||
Nat(n) => format!("{}", n),
|
||||
Int(i) => format!("{}", i),
|
||||
Float(f) => format!("{}", f),
|
||||
Bool(b) => format!("{}", b),
|
||||
StringLit(s) => format!("\"{}\"", s),
|
||||
Custom(name, args) if args.len() == 0 => format!("{}", name),
|
||||
Custom(name, args) => format!("{}{}", name, paren_wrapped_vec(args)),
|
||||
},
|
||||
Expr::Func(f) => match f {
|
||||
BuiltIn(name) => format!("<built-in function {}>", name),
|
||||
UserDefined { name: None, .. } => format!("<function>"),
|
||||
UserDefined { name: Some(name), .. } => format!("<function {}>", name),
|
||||
},
|
||||
Expr::Constructor { name } => format!("<constructor {}>", name),
|
||||
Expr::Tuple(exprs) => paren_wrapped_vec(exprs),
|
||||
_ => format!("{:?}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> State<'a> {
|
||||
pub fn evaluate(&mut self, ast: ReducedAST, repl: bool) -> Vec<Result<String, String>> {
|
||||
let mut acc = vec![];
|
||||
|
||||
// handle prebindings
|
||||
for statement in ast.0.iter() {
|
||||
self.prebinding(statement);
|
||||
}
|
||||
|
||||
for statement in ast.0 {
|
||||
match self.statement(statement) {
|
||||
Ok(Some(ref output)) if repl => acc.push(Ok(output.to_repl())),
|
||||
Ok(_) => (),
|
||||
Err(error) => {
|
||||
acc.push(Err(format!("Runtime error: {}", error)));
|
||||
return acc;
|
||||
},
|
||||
}
|
||||
}
|
||||
acc
|
||||
}
|
||||
|
||||
fn prebinding(&mut self, stmt: &Stmt) {
|
||||
match stmt {
|
||||
Stmt::PreBinding { name, func } => {
|
||||
let v_entry = ValueEntry::Binding { constant: true, val: Expr::Func(func.clone()) };
|
||||
self.values.insert(name.clone(), v_entry);
|
||||
},
|
||||
Stmt::Expr(_expr) => {
|
||||
//TODO have this support things like nested function defs
|
||||
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
|
||||
fn statement(&mut self, stmt: Stmt) -> EvalResult<Option<Expr>> {
|
||||
match stmt {
|
||||
Stmt::Binding { name, constant, expr } => {
|
||||
let val = self.expression(expr)?;
|
||||
self.values.insert(name.clone(), ValueEntry::Binding { constant, val });
|
||||
Ok(None)
|
||||
},
|
||||
Stmt::Expr(expr) => Ok(Some(self.expression(expr)?)),
|
||||
Stmt::PreBinding {..} | Stmt::Noop => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn block(&mut self, stmts: Vec<Stmt>) -> EvalResult<Expr> {
|
||||
let mut ret = None;
|
||||
for stmt in stmts {
|
||||
ret = self.statement(stmt)?;
|
||||
}
|
||||
Ok(ret.unwrap_or(Expr::Unit))
|
||||
}
|
||||
|
||||
fn expression(&mut self, expr: Expr) -> EvalResult<Expr> {
|
||||
use self::Expr::*;
|
||||
match expr {
|
||||
literal @ Lit(_) => Ok(literal),
|
||||
Call { box f, args } => {
|
||||
match self.expression(f)? {
|
||||
Constructor {name} => self.apply_data_constructor(name, args),
|
||||
Func(f) => self.apply_function(f, args),
|
||||
other => return Err(format!("Tried to call {:?} which is not a function or data constructor", other)),
|
||||
}
|
||||
},
|
||||
Val(v) => self.value(v),
|
||||
constr @ Constructor { .. } => Ok(constr),
|
||||
func @ Func(_) => Ok(func),
|
||||
Tuple(exprs) => Ok(Tuple(exprs.into_iter().map(|expr| self.expression(expr)).collect::<Result<Vec<Expr>,_>>()?)),
|
||||
Conditional { box cond, then_clause, else_clause } => self.conditional(cond, then_clause, else_clause),
|
||||
Assign { box val, box expr } => {
|
||||
let name = match val {
|
||||
Expr::Val(name) => name,
|
||||
_ => return Err(format!("Trying to assign to a non-value")),
|
||||
};
|
||||
|
||||
let constant = match self.values.lookup(&name) {
|
||||
None => return Err(format!("{} is undefined", name)),
|
||||
Some(ValueEntry::Binding { constant, .. }) => constant.clone(),
|
||||
};
|
||||
if constant {
|
||||
return Err(format!("trying to update {}, a non-mutable binding", name));
|
||||
}
|
||||
let val = self.expression(expr)?;
|
||||
self.values.insert(name.clone(), ValueEntry::Binding { constant: false, val });
|
||||
Ok(Expr::Unit)
|
||||
},
|
||||
e => Err(format!("Expr {:?} eval not implemented", e))
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_data_constructor(&mut self, name: Rc<String>, args: Vec<Expr>) -> EvalResult<Expr> {
|
||||
{
|
||||
let symbol_table = self.symbol_table_handle.borrow();
|
||||
match symbol_table.values.get(&name) {
|
||||
Some(Symbol { spec: SymbolSpec::DataConstructor { type_name, type_args }, name }) => {
|
||||
if args.len() != type_args.len() {
|
||||
return Err(format!("Data constructor {} requires {} args", name, type_args.len()));
|
||||
}
|
||||
()
|
||||
},
|
||||
_ => return Err(format!("Bad symbol {}", name))
|
||||
}
|
||||
}
|
||||
let evaled_args = args.into_iter().map(|expr| self.expression(expr)).collect::<Result<Vec<Expr>,_>>()?;
|
||||
//let evaled_args = vec![];
|
||||
Ok(Expr::Lit(self::Lit::Custom(name.clone(), evaled_args)))
|
||||
}
|
||||
|
||||
fn apply_function(&mut self, f: Func, args: Vec<Expr>) -> EvalResult<Expr> {
|
||||
match f {
|
||||
Func::BuiltIn(sigil) => self.apply_builtin(sigil, args),
|
||||
Func::UserDefined { params, body, name } => {
|
||||
|
||||
if params.len() != args.len() {
|
||||
return Err(format!("calling a {}-argument function with {} args", params.len(), args.len()))
|
||||
}
|
||||
let mut func_state = State {
|
||||
values: self.values.new_frame(name.map(|n| format!("{}", n))),
|
||||
symbol_table_handle: self.symbol_table_handle.clone(),
|
||||
};
|
||||
for (param, val) in params.into_iter().zip(args.into_iter()) {
|
||||
let val = func_state.expression(val)?;
|
||||
func_state.values.insert(param, ValueEntry::Binding { constant: true, val });
|
||||
}
|
||||
// TODO figure out function return semantics
|
||||
func_state.block(body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_builtin(&mut self, name: Rc<String>, args: Vec<Expr>) -> EvalResult<Expr> {
|
||||
use self::Expr::*;
|
||||
use self::Lit::*;
|
||||
let evaled_args: Result<Vec<Expr>, String> = args.into_iter().map(|arg| self.expression(arg)).collect();
|
||||
let evaled_args = evaled_args?;
|
||||
|
||||
Ok(match (name.as_str(), evaled_args.as_slice()) {
|
||||
/* binops */
|
||||
("+", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l + r)),
|
||||
("++", &[Lit(StringLit(ref s1)), Lit(StringLit(ref s2))]) => Lit(StringLit(Rc::new(format!("{}{}", s1, s2)))),
|
||||
("-", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l - r)),
|
||||
("*", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l * r)),
|
||||
("/", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Float((l as f64)/ (r as f64))),
|
||||
("//", &[Lit(Nat(l)), Lit(Nat(r))]) => if r == 0 {
|
||||
return Err(format!("divide by zero"));
|
||||
} else {
|
||||
Lit(Nat(l / r))
|
||||
},
|
||||
("%", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l % r)),
|
||||
("^", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l ^ r)),
|
||||
("&", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l & r)),
|
||||
("|", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l | r)),
|
||||
|
||||
("==", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Bool(l == r)),
|
||||
("==", &[Lit(Int(l)), Lit(Int(r))]) => Lit(Bool(l == r)),
|
||||
("==", &[Lit(Float(l)), Lit(Float(r))]) => Lit(Bool(l == r)),
|
||||
("==", &[Lit(Bool(l)), Lit(Bool(r))]) => Lit(Bool(l == r)),
|
||||
("==", &[Lit(StringLit(ref l)), Lit(StringLit(ref r))]) => Lit(Bool(l == r)),
|
||||
|
||||
/* prefix ops */
|
||||
("!", &[Lit(Bool(true))]) => Lit(Bool(false)),
|
||||
("!", &[Lit(Bool(false))]) => Lit(Bool(true)),
|
||||
("-", &[Lit(Nat(n))]) => Lit(Int(-1*(n as i64))),
|
||||
("-", &[Lit(Int(n))]) => Lit(Int(-1*(n as i64))),
|
||||
("+", &[Lit(Int(n))]) => Lit(Int(n)),
|
||||
("+", &[Lit(Nat(n))]) => Lit(Nat(n)),
|
||||
|
||||
|
||||
/* builtin functions */
|
||||
("print", &[ref anything]) => {
|
||||
print!("{}", anything.to_repl());
|
||||
Expr::Unit
|
||||
},
|
||||
("println", &[ref anything]) => {
|
||||
println!("{}", anything.to_repl());
|
||||
Expr::Unit
|
||||
},
|
||||
("getline", &[]) => {
|
||||
let mut buf = String::new();
|
||||
io::stdin().read_line(&mut buf).expect("Error readling line in 'getline'");
|
||||
Lit(StringLit(Rc::new(buf.trim().to_string())))
|
||||
},
|
||||
(x, args) => return Err(format!("bad or unimplemented builtin {:?} | {:?}", x, args)),
|
||||
})
|
||||
}
|
||||
|
||||
fn conditional(&mut self, cond: Expr, then_clause: Vec<Stmt>, else_clause: Vec<Stmt>) -> EvalResult<Expr> {
|
||||
let cond = self.expression(cond)?;
|
||||
Ok(match cond {
|
||||
Expr::Lit(Lit::Bool(true)) => self.block(then_clause)?,
|
||||
Expr::Lit(Lit::Bool(false)) => self.block(else_clause)?,
|
||||
_ => return Err(format!("Conditional with non-boolean condition"))
|
||||
})
|
||||
}
|
||||
|
||||
fn value(&mut self, name: Rc<String>) -> EvalResult<Expr> {
|
||||
use self::ValueEntry::*;
|
||||
use self::Func::*;
|
||||
//TODO add a layer of indirection here to talk to the symbol table first, and only then look up
|
||||
//in the values table
|
||||
|
||||
let symbol_table = self.symbol_table_handle.borrow();
|
||||
let value = symbol_table.values.get(&name);
|
||||
Ok(match value {
|
||||
Some(Symbol { name, spec }) => match spec {
|
||||
SymbolSpec::DataConstructor { type_name, type_args } => {
|
||||
if type_args.len() == 0 {
|
||||
Expr::Lit(Lit::Custom(name.clone(), vec![]))
|
||||
} else {
|
||||
return Err(format!("This data constructor thing not done"))
|
||||
}
|
||||
},
|
||||
SymbolSpec::Func(_) => match self.values.lookup(&name) {
|
||||
Some(Binding { val: Expr::Func(UserDefined { name, params, body }), .. }) => {
|
||||
Expr::Func(UserDefined { name: name.clone(), params: params.clone(), body: body.clone() })
|
||||
},
|
||||
_ => unreachable!(),
|
||||
},
|
||||
},
|
||||
/* see if it's an ordinary variable TODO make variables go in symbol table */
|
||||
None => match self.values.lookup(&name) {
|
||||
Some(Binding { val, .. }) => val.clone(),
|
||||
None => return Err(format!("Couldn't find value {}", name)),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod eval_tests {
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use symbol_table::SymbolTable;
|
||||
use tokenizing::tokenize;
|
||||
use parsing::parse;
|
||||
use eval::State;
|
||||
|
||||
macro_rules! fresh_env {
|
||||
($string:expr, $correct:expr) => {
|
||||
let symbol_table = Rc::new(RefCell::new(SymbolTable::new()));
|
||||
let mut state = State::new(symbol_table);
|
||||
let ast = parse(tokenize($string)).0.unwrap();
|
||||
state.symbol_table_handle.borrow_mut().add_top_level_symbols(&ast);
|
||||
let reduced = ast.reduce(&state.symbol_table_handle.borrow());
|
||||
let all_output = state.evaluate(reduced, true);
|
||||
let ref output = all_output.last().unwrap();
|
||||
assert_eq!(**output, Ok($correct.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_eval() {
|
||||
fresh_env!("1 + 2", "3");
|
||||
fresh_env!("let mut a = 1; a = 2", "Unit");
|
||||
fresh_env!("let mut a = 1; a = 2; a", "2");
|
||||
fresh_env!(r#"("a", 1 + 2)"#, r#"("a", 3)"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_eval() {
|
||||
fresh_env!("fn oi(x) { x + 1 }; oi(4)", "5");
|
||||
fresh_env!("fn oi(x) { x + 1 }; oi(1+2)", "4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scopes() {
|
||||
let scope_ok = r#"
|
||||
let a = 20
|
||||
fn haha() {
|
||||
let a = 10
|
||||
a
|
||||
}
|
||||
haha()
|
||||
"#;
|
||||
fresh_env!(scope_ok, "10");
|
||||
let scope_ok = r#"
|
||||
let a = 20
|
||||
fn haha() {
|
||||
let a = 10
|
||||
a
|
||||
}
|
||||
a
|
||||
"#;
|
||||
fresh_env!(scope_ok, "20");
|
||||
}
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use ast::{AST, Statement, Expression, Declaration, Discriminator, IfExpressionBody, Pattern};
|
||||
use symbol_table::{Symbol, SymbolSpec, SymbolTable};
|
||||
use builtin::{BinOp, PrefixOp};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ReducedAST(pub Vec<Stmt>);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Stmt {
|
||||
PreBinding {
|
||||
name: Rc<String>,
|
||||
func: Func,
|
||||
},
|
||||
Binding {
|
||||
name: Rc<String>,
|
||||
constant: bool,
|
||||
expr: Expr,
|
||||
},
|
||||
Expr(Expr),
|
||||
Noop,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Expr {
|
||||
Unit,
|
||||
Lit(Lit),
|
||||
Tuple(Vec<Expr>),
|
||||
Func(Func),
|
||||
Val(Rc<String>),
|
||||
Constructor {
|
||||
name: Rc<String>,
|
||||
},
|
||||
Call {
|
||||
f: Box<Expr>,
|
||||
args: Vec<Expr>,
|
||||
},
|
||||
Assign {
|
||||
val: Box<Expr>,
|
||||
expr: Box<Expr>,
|
||||
},
|
||||
Conditional {
|
||||
cond: Box<Expr>,
|
||||
then_clause: Vec<Stmt>,
|
||||
else_clause: Vec<Stmt>,
|
||||
},
|
||||
Match {
|
||||
cond: Box<Expr>,
|
||||
arms: Vec<(Pattern, Vec<Stmt>)>
|
||||
},
|
||||
UnimplementedSigilValue
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Lit {
|
||||
Nat(u64),
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
StringLit(Rc<String>),
|
||||
Custom(Rc<String>, Vec<Expr>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Func {
|
||||
BuiltIn(Rc<String>),
|
||||
UserDefined {
|
||||
name: Option<Rc<String>>,
|
||||
params: Vec<Rc<String>>,
|
||||
body: Vec<Stmt>,
|
||||
}
|
||||
}
|
||||
|
||||
impl AST {
|
||||
pub fn reduce(&self, symbol_table: &SymbolTable) -> ReducedAST {
|
||||
let mut output = vec![];
|
||||
for statement in self.0.iter() {
|
||||
output.push(statement.reduce(symbol_table));
|
||||
}
|
||||
ReducedAST(output)
|
||||
}
|
||||
}
|
||||
|
||||
impl Statement {
|
||||
fn reduce(&self, symbol_table: &SymbolTable) -> Stmt {
|
||||
use ast::Statement::*;
|
||||
match self {
|
||||
ExpressionStatement(expr) => Stmt::Expr(expr.reduce(symbol_table)),
|
||||
Declaration(decl) => decl.reduce(symbol_table),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
fn reduce(&self, symbol_table: &SymbolTable) -> Expr {
|
||||
use ast::ExpressionType::*;
|
||||
let ref input = self.0;
|
||||
match input {
|
||||
NatLiteral(n) => Expr::Lit(Lit::Nat(*n)),
|
||||
FloatLiteral(f) => Expr::Lit(Lit::Float(*f)),
|
||||
StringLiteral(s) => Expr::Lit(Lit::StringLit(s.clone())),
|
||||
BoolLiteral(b) => Expr::Lit(Lit::Bool(*b)),
|
||||
BinExp(binop, lhs, rhs) => binop.reduce(symbol_table, lhs, rhs),
|
||||
PrefixExp(op, arg) => op.reduce(symbol_table, arg),
|
||||
Value(name) => {
|
||||
match symbol_table.values.get(name) {
|
||||
Some(Symbol { spec: SymbolSpec::DataConstructor { type_args, .. }, .. }) => {
|
||||
Expr::Constructor { name: name.clone() }
|
||||
},
|
||||
_ => Expr::Val(name.clone()),
|
||||
}
|
||||
},
|
||||
Call { f, arguments } => Expr::Call {
|
||||
f: Box::new(f.reduce(symbol_table)),
|
||||
args: arguments.iter().map(|arg| arg.reduce(symbol_table)).collect(),
|
||||
},
|
||||
TupleLiteral(exprs) => Expr::Tuple(exprs.iter().map(|e| e.reduce(symbol_table)).collect()),
|
||||
IfExpression { discriminator, body } => reduce_if_expression(discriminator, body, symbol_table),
|
||||
_ => Expr::UnimplementedSigilValue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reduce_if_expression(discriminator: &Discriminator, body: &IfExpressionBody, symbol_table: &SymbolTable) -> Expr {
|
||||
let cond = Box::new(match *discriminator {
|
||||
Discriminator::Simple(ref expr) => expr.reduce(symbol_table),
|
||||
_ => panic!(),
|
||||
});
|
||||
match *body {
|
||||
IfExpressionBody::SimpleConditional(ref then_clause, ref else_clause) => {
|
||||
let then_clause = then_clause.iter().map(|expr| expr.reduce(symbol_table)).collect();
|
||||
let else_clause = match else_clause {
|
||||
None => vec![],
|
||||
Some(stmts) => stmts.iter().map(|expr| expr.reduce(symbol_table)).collect(),
|
||||
};
|
||||
Expr::Conditional { cond, then_clause, else_clause }
|
||||
},
|
||||
IfExpressionBody::SimplePatternMatch(ref pat, ref then_clause, ref else_clause) => {
|
||||
let then_clause = then_clause.iter().map(|expr| expr.reduce(symbol_table)).collect();
|
||||
let else_clause = match else_clause {
|
||||
None => vec![],
|
||||
Some(stmts) => stmts.iter().map(|expr| expr.reduce(symbol_table)).collect(),
|
||||
};
|
||||
Expr::Match {
|
||||
cond,
|
||||
arms: vec![
|
||||
(pat.clone(), then_clause),
|
||||
(Pattern::Ignored, else_clause)
|
||||
],
|
||||
}
|
||||
},
|
||||
IfExpressionBody::GuardList(ref _guard_arms) => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Declaration {
|
||||
fn reduce(&self, symbol_table: &SymbolTable) -> Stmt {
|
||||
use self::Declaration::*;
|
||||
use ::ast::Signature;
|
||||
match self {
|
||||
Binding {name, constant, expr } => Stmt::Binding { name: name.clone(), constant: *constant, expr: expr.reduce(symbol_table) },
|
||||
FuncDecl(Signature { name, params, .. }, statements) => Stmt::PreBinding {
|
||||
name: name.clone(),
|
||||
func: Func::UserDefined {
|
||||
name: Some(name.clone()),
|
||||
params: params.iter().map(|param| param.0.clone()).collect(),
|
||||
body: statements.iter().map(|stmt| stmt.reduce(symbol_table)).collect(),
|
||||
}
|
||||
},
|
||||
TypeDecl { .. } => Stmt::Noop,
|
||||
TypeAlias(_, _) => Stmt::Noop,
|
||||
Interface { .. } => Stmt::Noop,
|
||||
Impl { .. } => Stmt::Expr(Expr::UnimplementedSigilValue),
|
||||
_ => Stmt::Expr(Expr::UnimplementedSigilValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BinOp {
|
||||
fn reduce(&self, symbol_table: &SymbolTable, lhs: &Box<Expression>, rhs: &Box<Expression>) -> Expr {
|
||||
if **self.sigil() == "=" {
|
||||
Expr::Assign {
|
||||
val: Box::new(lhs.reduce(symbol_table)),
|
||||
expr: Box::new(rhs.reduce(symbol_table)),
|
||||
}
|
||||
} else {
|
||||
let f = Box::new(Expr::Func(Func::BuiltIn(self.sigil().clone())));
|
||||
Expr::Call { f, args: vec![lhs.reduce(symbol_table), rhs.reduce(symbol_table)]}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrefixOp {
|
||||
fn reduce(&self, symbol_table: &SymbolTable, arg: &Box<Expression>) -> Expr {
|
||||
let f = Box::new(Expr::Func(Func::BuiltIn(self.sigil().clone())));
|
||||
Expr::Call { f, args: vec![arg.reduce(symbol_table)]}
|
||||
}
|
||||
}
|
||||
@@ -1,493 +0,0 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::fmt::Write;
|
||||
/*
|
||||
use std::collections::hash_set::Union;
|
||||
use std::iter::Iterator;
|
||||
use itertools::Itertools;
|
||||
*/
|
||||
|
||||
use ast;
|
||||
use util::StateStack;
|
||||
use symbol_table::{SymbolSpec, SymbolTable};
|
||||
|
||||
pub type TypeName = Rc<String>;
|
||||
type TypeResult<T> = Result<T, String>;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
enum Type {
|
||||
Const(TConst),
|
||||
Var(TypeName),
|
||||
Func(Vec<Type>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
enum TConst {
|
||||
Unit,
|
||||
Nat,
|
||||
StringT,
|
||||
Custom(String)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
struct Scheme {
|
||||
names: Vec<TypeName>,
|
||||
ty: Type,
|
||||
}
|
||||
|
||||
impl fmt::Display for Scheme {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "∀{:?} . {:?}", self.names, self.ty)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
struct Substitution(HashMap<TypeName, Type>);
|
||||
impl Substitution {
|
||||
fn empty() -> Substitution {
|
||||
Substitution(HashMap::new())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
struct TypeEnv(HashMap<TypeName, Scheme>);
|
||||
|
||||
impl TypeEnv {
|
||||
fn default() -> TypeEnv {
|
||||
TypeEnv(HashMap::new())
|
||||
}
|
||||
|
||||
fn populate_from_symbols(&mut self, symbol_table: &SymbolTable) {
|
||||
for (name, symbol) in symbol_table.values.iter() {
|
||||
if let SymbolSpec::Func(ref type_names) = symbol.spec {
|
||||
let mut ch: char = 'a';
|
||||
let mut names = vec![];
|
||||
for _ in type_names.iter() {
|
||||
names.push(Rc::new(format!("{}", ch)));
|
||||
ch = ((ch as u8) + 1) as char;
|
||||
}
|
||||
|
||||
let sigma = Scheme {
|
||||
names: names.clone(),
|
||||
ty: Type::Func(names.into_iter().map(|n| Type::Var(n)).collect())
|
||||
};
|
||||
self.0.insert(name.clone(), sigma);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TypeContext<'a> {
|
||||
values: StateStack<'a, TypeName, Type>,
|
||||
symbol_table_handle: Rc<RefCell<SymbolTable>>,
|
||||
global_env: TypeEnv
|
||||
}
|
||||
|
||||
impl<'a> TypeContext<'a> {
|
||||
pub fn new(symbol_table_handle: Rc<RefCell<SymbolTable>>) -> TypeContext<'static> {
|
||||
TypeContext { values: StateStack::new(None), global_env: TypeEnv::default(), symbol_table_handle }
|
||||
}
|
||||
|
||||
pub fn debug_types(&self) -> String {
|
||||
let mut output = format!("Type environment\n");
|
||||
for (name, scheme) in &self.global_env.0 {
|
||||
write!(output, "{} -> {}\n", name, scheme).unwrap();
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
pub fn type_check_ast(&mut self, input: &ast::AST) -> Result<String, String> {
|
||||
let ref symbol_table = self.symbol_table_handle.borrow();
|
||||
self.global_env.populate_from_symbols(symbol_table);
|
||||
let output = self.global_env.infer_block(&input.0)?;
|
||||
Ok(format!("{:?}", output))
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeEnv {
|
||||
fn instantiate(&mut self, sigma: Scheme) -> Type {
|
||||
match sigma {
|
||||
Scheme { ty, .. } => ty,
|
||||
}
|
||||
}
|
||||
fn generate(&mut self, ty: Type) -> Scheme {
|
||||
Scheme {
|
||||
names: vec![], //TODO incomplete
|
||||
ty
|
||||
}
|
||||
}
|
||||
fn infer_block(&mut self, block: &Vec<ast::Statement>) -> TypeResult<Type> {
|
||||
let mut output = Type::Const(TConst::Unit);
|
||||
for statement in block {
|
||||
output = self.infer_statement(statement)?;
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
fn infer_statement(&mut self, statement: &ast::Statement) -> TypeResult<Type> {
|
||||
match statement {
|
||||
ast::Statement::ExpressionStatement(expr) => self.infer_expr(expr),
|
||||
ast::Statement::Declaration(decl) => self.infer_decl(decl)
|
||||
}
|
||||
}
|
||||
fn infer_decl(&mut self, decl: &ast::Declaration) -> TypeResult<Type> {
|
||||
use ast::Declaration::*;
|
||||
match decl {
|
||||
Binding { name, expr, .. } => {
|
||||
let ty = self.infer_expr(expr)?;
|
||||
let sigma = self.generate(ty);
|
||||
self.0.insert(name.clone(), sigma);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
Ok(Type::Const(TConst::Unit))
|
||||
}
|
||||
fn infer_expr(&mut self, expr: &ast::Expression) -> TypeResult<Type> {
|
||||
match expr {
|
||||
ast::Expression(expr, Some(anno)) => {
|
||||
self.infer_exprtype(expr)
|
||||
},
|
||||
ast::Expression(expr, None) => {
|
||||
self.infer_exprtype(expr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_exprtype(&mut self, expr: &ast::ExpressionType) -> TypeResult<Type> {
|
||||
use self::TConst::*;
|
||||
use ast::ExpressionType::*;
|
||||
Ok(match expr {
|
||||
NatLiteral(_) => Type::Const(Nat),
|
||||
StringLiteral(_) => Type::Const(StringT),
|
||||
BinExp(op, lhs, rhs) => {
|
||||
|
||||
return Err(format!("NOTDONE"))
|
||||
},
|
||||
Call { f, arguments } => {
|
||||
|
||||
return Err(format!("NOTDONE"))
|
||||
},
|
||||
Value(name) => {
|
||||
let s = match self.0.get(name) {
|
||||
Some(sigma) => sigma.clone(),
|
||||
None => return Err(format!("Unknown variable: {}", name))
|
||||
};
|
||||
self.instantiate(s)
|
||||
},
|
||||
_ => Type::Const(Unit)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* GIANT TODO - use the rust im crate, unless I make this code way less haskell-ish after it's done
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
|
||||
pub type TypeResult<T> = Result<T, String>;
|
||||
*/
|
||||
|
||||
/* TODO this should just check the name against a map, and that map should be pre-populated with
|
||||
* types */
|
||||
/*
|
||||
impl parsing::TypeName {
|
||||
fn to_type(&self) -> TypeResult<Type> {
|
||||
use self::parsing::TypeSingletonName;
|
||||
use self::parsing::TypeName::*;
|
||||
use self::Type::*; use self::TConstOld::*;
|
||||
Ok(match self {
|
||||
Tuple(_) => return Err(format!("Tuples not yet implemented")),
|
||||
Singleton(name) => match name {
|
||||
TypeSingletonName { name, .. } => match &name[..] {
|
||||
/*
|
||||
"Nat" => Const(Nat),
|
||||
"Int" => Const(Int),
|
||||
"Float" => Const(Float),
|
||||
"Bool" => Const(Bool),
|
||||
"String" => Const(StringT),
|
||||
*/
|
||||
n => Const(Custom(n.to_string()))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
impl TypeContext {
|
||||
pub fn type_check_ast(&mut self, ast: &parsing::AST) -> TypeResult<String> {
|
||||
let ref block = ast.0;
|
||||
let mut infer = Infer::default();
|
||||
let env = TypeEnvironment::default();
|
||||
let output = infer.infer_block(block, &env);
|
||||
match output {
|
||||
Ok(s) => Ok(format!("{:?}", s)),
|
||||
Err(s) => Err(format!("Error: {:?}", s))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this is the equivalent of the Haskell Infer monad
|
||||
#[derive(Debug, Default)]
|
||||
struct Infer {
|
||||
_idents: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum InferError {
|
||||
CannotUnify(MonoType, MonoType),
|
||||
OccursCheckFailed(Rc<String>, MonoType),
|
||||
UnknownIdentifier(Rc<String>),
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
type InferResult<T> = Result<T, InferError>;
|
||||
|
||||
|
||||
impl Infer {
|
||||
fn fresh(&mut self) -> MonoType {
|
||||
let i = self._idents;
|
||||
self._idents += 1;
|
||||
let name = Rc::new(format!("{}", ('a' as u8 + 1) as char));
|
||||
MonoType::Var(name)
|
||||
}
|
||||
|
||||
fn unify(&mut self, a: MonoType, b: MonoType) -> InferResult<Substitution> {
|
||||
use self::InferError::*; use self::MonoType::*;
|
||||
Ok(match (a, b) {
|
||||
(Const(ref a), Const(ref b)) if a == b => Substitution::new(),
|
||||
(Var(ref name), ref var) => Substitution::bind_variable(name, var),
|
||||
(ref var, Var(ref name)) => Substitution::bind_variable(name, var),
|
||||
(Function(box a1, box b1), Function(box a2, box b2)) => {
|
||||
let s1 = self.unify(a1, a2)?;
|
||||
let s2 = self.unify(b1.apply_substitution(&s1), b2.apply_substitution(&s1))?;
|
||||
s1.merge(s2)
|
||||
},
|
||||
(a, b) => return Err(CannotUnify(a, b))
|
||||
})
|
||||
}
|
||||
|
||||
fn infer_block(&mut self, block: &Vec<parsing::Statement>, env: &TypeEnvironment) -> InferResult<MonoType> {
|
||||
use self::parsing::Statement;
|
||||
let mut ret = MonoType::Const(TypeConst::Unit);
|
||||
for statement in block.iter() {
|
||||
ret = match statement {
|
||||
Statement::ExpressionStatement(expr) => {
|
||||
let (sub, ty) = self.infer_expr(expr, env)?;
|
||||
//TODO handle substitution monadically
|
||||
|
||||
ty
|
||||
}
|
||||
Statement::Declaration(decl) => MonoType::Const(TypeConst::Unit),
|
||||
}
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn infer_expr(&mut self, expr: &parsing::Expression, env: &TypeEnvironment) -> InferResult<(Substitution, MonoType)> {
|
||||
use self::parsing::Expression;
|
||||
match expr {
|
||||
Expression(e, Some(anno)) => self.infer_annotated_expr(e, anno, env),
|
||||
/*
|
||||
let anno_ty = anno.to_type()?;
|
||||
let ty = self.infer_exprtype(&e)?;
|
||||
self.unify(ty, anno_ty)
|
||||
},
|
||||
*/
|
||||
Expression(e, None) => self.infer_exprtype(e, env)
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_annotated_expr(&mut self, expr: &parsing::ExpressionType, anno: &parsing::TypeName, env: &TypeEnvironment) -> InferResult<(Substitution, MonoType)> {
|
||||
Err(InferError::Custom(format!("exprtype not done: {:?}", expr)))
|
||||
}
|
||||
fn infer_exprtype(&mut self, expr: &parsing::ExpressionType, env: &TypeEnvironment) -> InferResult<(Substitution, MonoType)> {
|
||||
use self::parsing::ExpressionType::*;
|
||||
use self::TypeConst::*;
|
||||
Ok(match expr {
|
||||
NatLiteral(_) => (Substitution::new(), MonoType::Const(Nat)),
|
||||
FloatLiteral(_) => (Substitution::new(), MonoType::Const(Float)),
|
||||
StringLiteral(_) => (Substitution::new(), MonoType::Const(StringT)),
|
||||
BoolLiteral(_) => (Substitution::new(), MonoType::Const(Bool)),
|
||||
Value(name) => match env.lookup(name) {
|
||||
Some(sigma) => {
|
||||
let tau = self.instantiate(&sigma);
|
||||
(Substitution::new(), tau)
|
||||
},
|
||||
None => return Err(InferError::UnknownIdentifier(name.clone())),
|
||||
},
|
||||
e => return Err(InferError::Custom(format!("Type inference for {:?} not done", e)))
|
||||
})
|
||||
}
|
||||
fn instantiate(&mut self, sigma: &PolyType) -> MonoType {
|
||||
let ref ty: MonoType = sigma.1;
|
||||
let mut subst = Substitution::new();
|
||||
|
||||
for name in sigma.0.iter() {
|
||||
let fresh_mvar = self.fresh();
|
||||
let new = Substitution::bind_variable(name, &fresh_mvar);
|
||||
subst = subst.merge(new);
|
||||
}
|
||||
ty.apply_substitution(&subst)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* OLD STUFF DOWN HERE */
|
||||
|
||||
|
||||
|
||||
/*
|
||||
impl TypeContext {
|
||||
fn infer_block(&mut self, statements: &Vec<parsing::Statement>) -> TypeResult<Type> {
|
||||
let mut ret_type = Type::Const(TConst::Unit);
|
||||
for statement in statements {
|
||||
ret_type = self.infer_statement(statement)?;
|
||||
}
|
||||
Ok(ret_type)
|
||||
}
|
||||
|
||||
fn infer_statement(&mut self, statement: &parsing::Statement) -> TypeResult<Type> {
|
||||
use self::parsing::Statement::*;
|
||||
match statement {
|
||||
ExpressionStatement(expr) => self.infer(expr),
|
||||
Declaration(decl) => self.add_declaration(decl),
|
||||
}
|
||||
}
|
||||
fn add_declaration(&mut self, decl: &parsing::Declaration) -> TypeResult<Type> {
|
||||
use self::parsing::Declaration::*;
|
||||
use self::Type::*;
|
||||
match decl {
|
||||
Binding { name, expr, .. } => {
|
||||
let ty = self.infer(expr)?;
|
||||
self.bindings.insert(name.clone(), ty);
|
||||
},
|
||||
_ => return Err(format!("other formats not done"))
|
||||
}
|
||||
Ok(Void)
|
||||
}
|
||||
fn infer(&mut self, expr: &parsing::Expression) -> TypeResult<Type> {
|
||||
use self::parsing::Expression;
|
||||
match expr {
|
||||
Expression(e, Some(anno)) => {
|
||||
let anno_ty = anno.to_type()?;
|
||||
let ty = self.infer_exprtype(&e)?;
|
||||
self.unify(ty, anno_ty)
|
||||
},
|
||||
Expression(e, None) => self.infer_exprtype(e)
|
||||
}
|
||||
}
|
||||
fn infer_exprtype(&mut self, expr: &parsing::ExpressionType) -> TypeResult<Type> {
|
||||
use self::parsing::ExpressionType::*;
|
||||
use self::Type::*; use self::TConst::*;
|
||||
match expr {
|
||||
NatLiteral(_) => Ok(Const(Nat)),
|
||||
FloatLiteral(_) => Ok(Const(Float)),
|
||||
StringLiteral(_) => Ok(Const(StringT)),
|
||||
BoolLiteral(_) => Ok(Const(Bool)),
|
||||
BinExp(op, lhs, rhs) => { /* remember there are both the haskell convention talk and the write you a haskell ways to do this! */
|
||||
match op.get_type()? {
|
||||
Func(box t1, box Func(box t2, box t3)) => {
|
||||
let lhs_ty = self.infer(lhs)?;
|
||||
let rhs_ty = self.infer(rhs)?;
|
||||
self.unify(t1, lhs_ty)?;
|
||||
self.unify(t2, rhs_ty)?;
|
||||
Ok(t3)
|
||||
},
|
||||
other => Err(format!("{:?} is not a binary function type", other))
|
||||
}
|
||||
},
|
||||
PrefixExp(op, expr) => match op.get_type()? {
|
||||
Func(box t1, box t2) => {
|
||||
let expr_ty = self.infer(expr)?;
|
||||
self.unify(t1, expr_ty)?;
|
||||
Ok(t2)
|
||||
},
|
||||
other => Err(format!("{:?} is not a prefix op function type", other))
|
||||
},
|
||||
Value(name) => {
|
||||
match self.bindings.get(name) {
|
||||
Some(ty) => Ok(ty.clone()),
|
||||
None => Err(format!("No binding found for variable: {}", name)),
|
||||
}
|
||||
},
|
||||
Call { f, arguments } => {
|
||||
let mut tf = self.infer(f)?;
|
||||
for arg in arguments.iter() {
|
||||
match tf {
|
||||
Func(box t, box rest) => {
|
||||
let t_arg = self.infer(arg)?;
|
||||
self.unify(t, t_arg)?;
|
||||
tf = rest;
|
||||
},
|
||||
other => return Err(format!("Function call failed to unify; last type: {:?}", other)),
|
||||
}
|
||||
}
|
||||
Ok(tf)
|
||||
},
|
||||
TupleLiteral(expressions) => {
|
||||
let mut types = vec![];
|
||||
for expr in expressions {
|
||||
types.push(self.infer(expr)?);
|
||||
}
|
||||
Ok(Sum(types))
|
||||
},
|
||||
_ => Err(format!("Type not yet implemented"))
|
||||
}
|
||||
}
|
||||
fn unify(&mut self, t1: Type, t2: Type) -> TypeResult<Type> {
|
||||
use self::Type::*;// use self::TConst::*;
|
||||
match (t1, t2) {
|
||||
(Const(ref a), Const(ref b)) if a == b => Ok(Const(a.clone())),
|
||||
(a, b) => Err(format!("Types {:?} and {:?} don't unify", a, b))
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
/*
|
||||
use super::{Type, TConst, TypeContext};
|
||||
use super::Type::*;
|
||||
use super::TConst::*;
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
|
||||
macro_rules! type_test {
|
||||
($input:expr, $correct:expr) => {
|
||||
{
|
||||
let symbol_table = Rc::new(RefCell::new(SymbolTable::new()));
|
||||
let mut tc = TypeContext::new(symbol_table);
|
||||
let ast = ::ast::parse(::tokenizing::tokenize($input)).0.unwrap() ;
|
||||
//tc.add_symbols(&ast);
|
||||
assert_eq!($correct, tc.infer_block(&ast.0).unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_inference() {
|
||||
type_test!("30", Const(Nat));
|
||||
//type_test!("fn x(a: Int): Bool {}; x(1)", TConst(Boolean));
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
[package]
|
||||
name = "schala-codegen"
|
||||
name = "schala-repl-codegen"
|
||||
version = "0.1.0"
|
||||
authors = ["greg <greg.shuflin@protonmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "0.13.1", features = ["full", "extra-traits"] }
|
||||
quote = "0.5"
|
||||
syn = { version = "0.15.6", features = ["full", "extra-traits"] }
|
||||
quote = "0.6.8"
|
||||
proc-macro2 = "0.4.19"
|
||||
schala-repl = { path = "../schala-repl" }
|
||||
|
||||
[lib]
|
||||
199
schala-repl-codegen/src/lib.rs
Normal file
199
schala-repl-codegen/src/lib.rs
Normal file
@@ -0,0 +1,199 @@
|
||||
#![feature(trace_macros)]
|
||||
#![recursion_limit="128"]
|
||||
extern crate proc_macro;
|
||||
extern crate proc_macro2;
|
||||
#[macro_use]
|
||||
extern crate quote;
|
||||
extern crate syn;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use syn::{Ident, Attribute, DeriveInput};
|
||||
|
||||
fn find_attr_by_name<'a>(name: &str, attrs: &'a Vec<Attribute>) -> Option<&'a Attribute> {
|
||||
attrs.iter().find(|attr| {
|
||||
let first = attr.path.segments.first();
|
||||
let seg: Option<&&syn::PathSegment> = first.as_ref().map(|x| x.value());
|
||||
seg.map(|seg| seg.ident.to_string() == name).unwrap_or(false)
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_attribute_arg_by_name(name: &str, attrs: &Vec<Attribute>) -> Option<String> {
|
||||
use syn::{Meta, Lit, MetaNameValue};
|
||||
find_attr_by_name(name, attrs)
|
||||
.and_then(|attr| {
|
||||
match attr.interpret_meta() {
|
||||
Some(Meta::NameValue(MetaNameValue { lit: Lit::Str(litstr), .. })) => Some(litstr.value()),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_attribute_list(name: &str, attrs: &Vec<Attribute>) -> Option<Vec<(Ident, Option<Vec<Ident>>)>> {
|
||||
use syn::{Meta, MetaList, NestedMeta};
|
||||
find_attr_by_name(name, attrs)
|
||||
.and_then(|attr| {
|
||||
match attr.interpret_meta() {
|
||||
Some(Meta::List(MetaList { nested, .. })) => {
|
||||
Some(nested.iter().map(|nested_meta| match nested_meta {
|
||||
&NestedMeta::Meta(Meta::Word(ref ident)) => (ident.clone(), None),
|
||||
&NestedMeta::Meta(Meta::List(MetaList { ref ident, nested: ref nested2, .. })) => {
|
||||
let own_args = nested2.iter().map(|nested_meta2| match nested_meta2 {
|
||||
&NestedMeta::Meta(Meta::Word(ref ident)) => ident.clone(),
|
||||
_ => panic!("Bad format for doubly-nested attribute list")
|
||||
}).collect();
|
||||
(ident.clone(), Some(own_args))
|
||||
},
|
||||
_ => panic!("Bad format for nested list")
|
||||
}).collect())
|
||||
},
|
||||
_ => panic!("{} must be a comma-delimited list surrounded by parens", name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn get_attribute_identifier(attr_name: &str, attrs: &Vec<Attribute>) -> Option<proc_macro2::Ident> {
|
||||
find_attr_by_name(attr_name, attrs).and_then(|attr| {
|
||||
let tts = attr.tts.clone().into_iter().collect::<Vec<_>>();
|
||||
|
||||
if tts.len() == 2 {
|
||||
let ref after_equals: proc_macro2::TokenTree = tts[1];
|
||||
match after_equals {
|
||||
proc_macro2::TokenTree::Ident(ident) => Some(ident.clone()),
|
||||
_ => None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* a pass_chain function signature looks like:
|
||||
* fn(&mut ProgrammingLanguageInterface, A, Option<&mut DebugHandler>) -> Result<B, String>
|
||||
*
|
||||
* TODO use some kind of failure-handling library to make this better
|
||||
*/
|
||||
fn generate_pass_chain(idents: Vec<Ident>) -> proc_macro2::TokenStream {
|
||||
let final_return = quote! {
|
||||
{
|
||||
let final_output: FinishedComputation = unfinished_computation.finish(Ok(input_to_next_stage));
|
||||
final_output
|
||||
}
|
||||
};
|
||||
|
||||
let nested_passes = idents.iter()
|
||||
.rev()
|
||||
.fold(final_return, |later_fragment, pass_name| {
|
||||
quote! {
|
||||
{
|
||||
let pass_name = stringify!(#pass_name);
|
||||
let (output, duration) = {
|
||||
let ref debug_map = eval_options.debug_passes;
|
||||
let debug_handle = match debug_map.get(pass_name) {
|
||||
Some(PassDebugOptionsDescriptor { opts }) => {
|
||||
let ptr = &mut unfinished_computation;
|
||||
ptr.cur_debug_options = opts.clone();
|
||||
Some(ptr)
|
||||
}
|
||||
_ => None
|
||||
};
|
||||
let start = time::Instant::now();
|
||||
let pass_output = #pass_name(self, input_to_next_stage, debug_handle);
|
||||
let elapsed = start.elapsed();
|
||||
(pass_output, elapsed)
|
||||
};
|
||||
if eval_options.debug_timing {
|
||||
unfinished_computation.durations.push(duration);
|
||||
}
|
||||
match output {
|
||||
Ok(input_to_next_stage) => #later_fragment,
|
||||
//TODO this error type needs to be guaranteed to provide a useable string
|
||||
Err(err) => return unfinished_computation.output(Err(format!("Pass {} failed:\n{}", pass_name, err))),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
{
|
||||
use std::time;
|
||||
use schala_repl::PassDebugOptionsDescriptor;
|
||||
|
||||
let eval_options = options;
|
||||
let input_to_next_stage = input;
|
||||
let mut unfinished_computation = UnfinishedComputation::default();
|
||||
#nested_passes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_derive(ProgrammingLanguageInterface,
|
||||
attributes(LanguageName, SourceFileExtension, PipelineSteps, DocMethod, HandleCustomInterpreterDirectives))]
|
||||
pub fn derive_programming_language_interface(input: TokenStream) -> TokenStream {
|
||||
let ast: DeriveInput = syn::parse(input).unwrap();
|
||||
let name = &ast.ident;
|
||||
let attrs = &ast.attrs;
|
||||
|
||||
let language_name: String = extract_attribute_arg_by_name("LanguageName", attrs).expect("LanguageName is required");
|
||||
let file_ext = extract_attribute_arg_by_name("SourceFileExtension", attrs).expect("SourceFileExtension is required");
|
||||
let passes = extract_attribute_list("PipelineSteps", attrs).expect("PipelineSteps are required");
|
||||
let pass_idents = passes.iter().map(|x| x.0.clone());
|
||||
|
||||
let get_doc_impl = match get_attribute_identifier("DocMethod", attrs) {
|
||||
None => quote! { },
|
||||
Some(method_name) => quote! {
|
||||
fn get_doc(&self, commands: &Vec<&str>) -> Option<String> {
|
||||
self.#method_name(commands)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let handle_custom_interpreter_directives_impl = match get_attribute_identifier("HandleCustomInterpreterDirectives", attrs) {
|
||||
None => quote! { },
|
||||
Some(method_name) => quote! {
|
||||
fn handle_custom_interpreter_directives(&mut self, commands: &Vec<&str>) -> Option<String> {
|
||||
//println!("If #method_name is &self not &mut self, this runs forever");
|
||||
self.#method_name(commands)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let pass_descriptors = passes.iter().map(|pass| {
|
||||
let name = pass.0.to_string();
|
||||
let opts: Vec<String> = match &pass.1 {
|
||||
None => vec![],
|
||||
Some(opts) => opts.iter().map(|o| o.to_string()).collect(),
|
||||
};
|
||||
|
||||
quote! {
|
||||
PassDescriptor {
|
||||
name: #name.to_string(),
|
||||
debug_options: vec![#(format!(#opts)),*]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let pass_chain = generate_pass_chain(pass_idents.collect());
|
||||
|
||||
let tokens = quote! {
|
||||
use schala_repl::PassDescriptor;
|
||||
impl ProgrammingLanguageInterface for #name {
|
||||
fn get_language_name(&self) -> String {
|
||||
#language_name.to_string()
|
||||
}
|
||||
fn get_source_file_suffix(&self) -> String {
|
||||
#file_ext.to_string()
|
||||
}
|
||||
fn execute_pipeline(&mut self, input: &str, options: &EvalOptions) -> FinishedComputation {
|
||||
#pass_chain
|
||||
}
|
||||
fn get_passes(&self) -> Vec<PassDescriptor> {
|
||||
vec![ #(#pass_descriptors),* ]
|
||||
}
|
||||
#get_doc_impl
|
||||
#handle_custom_interpreter_directives_impl
|
||||
}
|
||||
};
|
||||
|
||||
let output: TokenStream = tokens.into();
|
||||
output
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
use colored::*;
|
||||
use std::fmt::Write;
|
||||
use std::time;
|
||||
|
||||
pub struct LLVMCodeString(pub String);
|
||||
|
||||
@@ -8,6 +9,7 @@ pub struct LLVMCodeString(pub String);
|
||||
pub struct EvalOptions {
|
||||
pub execution_method: ExecutionMethod,
|
||||
pub debug_passes: HashMap<String, PassDebugOptionsDescriptor>,
|
||||
pub debug_timing: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq)]
|
||||
@@ -35,12 +37,14 @@ impl Default for ExecutionMethod {
|
||||
#[derive(Debug, Default)]
|
||||
pub struct UnfinishedComputation {
|
||||
artifacts: Vec<(String, TraceArtifact)>,
|
||||
pub durations: Vec<time::Duration>,
|
||||
pub cur_debug_options: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FinishedComputation {
|
||||
artifacts: Vec<(String, TraceArtifact)>,
|
||||
durations: Vec<time::Duration>,
|
||||
text_output: Result<String, String>,
|
||||
}
|
||||
|
||||
@@ -51,18 +55,36 @@ impl UnfinishedComputation {
|
||||
pub fn finish(self, text_output: Result<String, String>) -> FinishedComputation {
|
||||
FinishedComputation {
|
||||
artifacts: self.artifacts,
|
||||
text_output
|
||||
text_output,
|
||||
durations: self.durations,
|
||||
}
|
||||
}
|
||||
pub fn output(self, output: Result<String, String>) -> FinishedComputation {
|
||||
FinishedComputation {
|
||||
artifacts: self.artifacts,
|
||||
text_output: output
|
||||
text_output: output,
|
||||
durations: self.durations,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FinishedComputation {
|
||||
|
||||
fn get_timing(&self) -> Option<String> {
|
||||
if self.durations.len() != 0 {
|
||||
let mut buf = String::new();
|
||||
write!(&mut buf, "Timing: ").unwrap();
|
||||
for duration in self.durations.iter() {
|
||||
let timing = (duration.as_secs() as f64) + (duration.subsec_nanos() as f64 * 1e-9);
|
||||
write!(&mut buf, "{}s, ", timing).unwrap()
|
||||
}
|
||||
write!(&mut buf, "\n").unwrap();
|
||||
Some(buf)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_repl(&self) -> String {
|
||||
let mut buf = String::new();
|
||||
for (stage, artifact) in self.artifacts.iter() {
|
||||
@@ -72,6 +94,11 @@ impl FinishedComputation {
|
||||
write!(&mut buf, "{}: {}\n", stage, output).unwrap();
|
||||
}
|
||||
|
||||
match self.get_timing() {
|
||||
Some(timing) => write!(&mut buf, "{}", timing).unwrap(),
|
||||
None => ()
|
||||
}
|
||||
|
||||
match self.text_output {
|
||||
Ok(ref output) => write!(&mut buf, "{}", output).unwrap(),
|
||||
Err(ref err) => write!(&mut buf, "{} {}", "Error: ".red().bold(), err).unwrap(),
|
||||
@@ -128,7 +155,7 @@ impl TraceArtifact {
|
||||
|
||||
pub trait ProgrammingLanguageInterface {
|
||||
fn execute_pipeline(&mut self, _input: &str, _eval_options: &EvalOptions) -> FinishedComputation {
|
||||
FinishedComputation { artifacts: vec![], text_output: Err(format!("Execution pipeline not done")) }
|
||||
FinishedComputation { artifacts: vec![], text_output: Err(format!("Execution pipeline not done")), durations: vec![] }
|
||||
}
|
||||
|
||||
fn get_language_name(&self) -> String;
|
||||
@@ -142,55 +169,7 @@ pub trait ProgrammingLanguageInterface {
|
||||
fn custom_interpreter_directives_help(&self) -> String {
|
||||
format!(">> No custom interpreter directives specified <<")
|
||||
}
|
||||
}
|
||||
|
||||
/* a pass_chain function signature looks like:
|
||||
* fn(&mut ProgrammingLanguageInterface, A, Option<&mut DebugHandler>) -> Result<B, String>
|
||||
*
|
||||
* TODO use some kind of failure-handling library to make this better
|
||||
*/
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! pass_chain {
|
||||
($state:expr, $options:expr; $($pass:path), *) => {
|
||||
|text_input| {
|
||||
let mut comp = UnfinishedComputation::default();
|
||||
pass_chain_helper! { ($state, comp, $options); text_input $(, $pass)* }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! pass_chain_helper {
|
||||
(($state:expr, $comp:expr, $options:expr); $input:expr, $pass:path $(, $rest:path)*) => {
|
||||
{
|
||||
use schala_repl::PassDebugOptionsDescriptor;
|
||||
let pass_name = stringify!($pass);
|
||||
let output = {
|
||||
let ref debug_map = $options.debug_passes;
|
||||
let debug_handle = match debug_map.get(pass_name) {
|
||||
Some(PassDebugOptionsDescriptor { opts }) => {
|
||||
let ptr = &mut $comp;
|
||||
ptr.cur_debug_options = opts.clone();
|
||||
Some(ptr)
|
||||
}
|
||||
_ => None
|
||||
};
|
||||
$pass($state, $input, debug_handle)
|
||||
};
|
||||
match output {
|
||||
Ok(result) => pass_chain_helper! { ($state, $comp, $options); result $(, $rest)* },
|
||||
Err(err) => {
|
||||
$comp.output(Err(format!("Pass {} failed with {:?}", pass_name, err)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// Done
|
||||
(($state:expr, $comp:expr, $options:expr); $final_output:expr) => {
|
||||
{
|
||||
let final_output: FinishedComputation = $comp.finish(Ok($final_output));
|
||||
final_output
|
||||
}
|
||||
};
|
||||
fn get_doc(&self, _commands: &Vec<&str>) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,11 @@ extern crate phf;
|
||||
|
||||
use std::path::Path;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::io::Read;
|
||||
use std::process::exit;
|
||||
use std::default::Default;
|
||||
use std::fmt::Write as FmtWrite;
|
||||
|
||||
use colored::*;
|
||||
use itertools::Itertools;
|
||||
|
||||
mod repl;
|
||||
mod language;
|
||||
mod webapp;
|
||||
pub mod llvm_wrap;
|
||||
@@ -84,7 +81,7 @@ pub fn repl_main(generators: Vec<PLIGenerator>) {
|
||||
|
||||
match option_matches.free[..] {
|
||||
[] | [_] => {
|
||||
let mut repl = Repl::new(languages, initial_index);
|
||||
let mut repl = repl::Repl::new(languages, initial_index);
|
||||
repl.run();
|
||||
}
|
||||
[_, ref filename, _..] => {
|
||||
@@ -132,359 +129,6 @@ fn run_noninteractive(filename: &str, languages: Vec<Box<ProgrammingLanguageInte
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum CommandTree {
|
||||
Terminal(String, Option<String>),
|
||||
NonTerminal(String, Vec<CommandTree>, Option<String>),
|
||||
Top(Vec<CommandTree>),
|
||||
}
|
||||
|
||||
impl CommandTree {
|
||||
fn term(s: &str, help: Option<&str>) -> CommandTree {
|
||||
CommandTree::Terminal(s.to_string(), help.map(|x| x.to_string()))
|
||||
}
|
||||
fn get_cmd(&self) -> String {
|
||||
match self {
|
||||
CommandTree::Terminal(s, _) => s.to_string(),
|
||||
CommandTree::NonTerminal(s, _, _) => s.to_string(),
|
||||
CommandTree::Top(_) => "".to_string(),
|
||||
}
|
||||
}
|
||||
fn get_help(&self) -> String {
|
||||
match self {
|
||||
CommandTree::Terminal(_, h) => h.as_ref().map(|h| h.clone()).unwrap_or(format!("")),
|
||||
CommandTree::NonTerminal(_, _, h) => h.as_ref().map(|h| h.clone()).unwrap_or(format!("")),
|
||||
CommandTree::Top(_) => "".to_string(),
|
||||
}
|
||||
}
|
||||
fn get_children(&self) -> Vec<String> {
|
||||
match self {
|
||||
CommandTree::Terminal(_, _) => vec![],
|
||||
CommandTree::NonTerminal(_, children, _) => children.iter().map(|x| x.get_cmd()).collect(),
|
||||
CommandTree::Top(children) => children.iter().map(|x| x.get_cmd()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TabCompleteHandler {
|
||||
sigil: char,
|
||||
top_level_commands: CommandTree,
|
||||
}
|
||||
|
||||
use linefeed::complete::{Completion, Completer};
|
||||
use linefeed::terminal::Terminal;
|
||||
|
||||
impl TabCompleteHandler {
|
||||
fn new(sigil: char, top_level_commands: CommandTree) -> TabCompleteHandler {
|
||||
TabCompleteHandler {
|
||||
top_level_commands,
|
||||
sigil,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Terminal> Completer<T> for TabCompleteHandler {
|
||||
fn complete(&self, word: &str, prompter: &linefeed::prompter::Prompter<T>, start: usize, _end: usize) -> Option<Vec<Completion>> {
|
||||
let line = prompter.buffer();
|
||||
|
||||
if line.starts_with(&format!("{}", self.sigil)) {
|
||||
let mut words = line[1..(if start == 0 { 1 } else { start })].split_whitespace();
|
||||
let mut completions = Vec::new();
|
||||
let mut command_tree: Option<&CommandTree> = Some(&self.top_level_commands);
|
||||
|
||||
loop {
|
||||
match words.next() {
|
||||
None => {
|
||||
let top = match command_tree {
|
||||
Some(CommandTree::Top(_)) => true,
|
||||
_ => false
|
||||
};
|
||||
let word = if top { word.get(1..).unwrap() } else { word };
|
||||
for cmd in command_tree.map(|x| x.get_children()).unwrap_or(vec![]).into_iter() {
|
||||
if cmd.starts_with(word) {
|
||||
completions.push(Completion {
|
||||
completion: format!("{}{}", if top { ":" } else { "" }, cmd),
|
||||
display: Some(cmd.clone()),
|
||||
suffix: linefeed::complete::Suffix::Some(' ')
|
||||
})
|
||||
}
|
||||
}
|
||||
break;
|
||||
},
|
||||
Some(s) => {
|
||||
let new_ptr: Option<&CommandTree> = command_tree.and_then(|cm| match cm {
|
||||
CommandTree::Top(children) => children.iter().find(|c| c.get_cmd() == s),
|
||||
CommandTree::NonTerminal(_, children, _) => children.iter().find(|c| c.get_cmd() == s),
|
||||
CommandTree::Terminal(_, _) => None,
|
||||
});
|
||||
command_tree = new_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(completions)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Repl {
|
||||
options: EvalOptions,
|
||||
languages: Vec<Box<ProgrammingLanguageInterface>>,
|
||||
current_language_index: usize,
|
||||
interpreter_directive_sigil: char,
|
||||
line_reader: linefeed::interface::Interface<linefeed::terminal::DefaultTerminal>,
|
||||
}
|
||||
|
||||
impl Repl {
|
||||
fn new(languages: Vec<Box<ProgrammingLanguageInterface>>, initial_index: usize) -> Repl {
|
||||
use linefeed::Interface;
|
||||
let i = if initial_index < languages.len() { initial_index } else { 0 };
|
||||
|
||||
let line_reader = Interface::new("schala-repl").unwrap();
|
||||
|
||||
Repl {
|
||||
options: Repl::get_options(),
|
||||
languages: languages,
|
||||
current_language_index: i,
|
||||
interpreter_directive_sigil: ':',
|
||||
line_reader
|
||||
}
|
||||
}
|
||||
|
||||
fn get_cur_language(&self) -> &ProgrammingLanguageInterface {
|
||||
self.languages[self.current_language_index].as_ref()
|
||||
}
|
||||
|
||||
fn get_options() -> EvalOptions {
|
||||
File::open(".schala_repl")
|
||||
.and_then(|mut file| {
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
Ok(contents)
|
||||
})
|
||||
.and_then(|contents| {
|
||||
let options: EvalOptions = serde_json::from_str(&contents)?;
|
||||
Ok(options)
|
||||
}).unwrap_or(EvalOptions::default())
|
||||
}
|
||||
|
||||
fn save_options(&self) {
|
||||
let ref options = self.options;
|
||||
let read = File::create(".schala_repl")
|
||||
.and_then(|mut file| {
|
||||
let buf = serde_json::to_string(options).unwrap();
|
||||
file.write_all(buf.as_bytes())
|
||||
});
|
||||
|
||||
if let Err(err) = read {
|
||||
println!("Error saving .schala_repl file {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&mut self) {
|
||||
use linefeed::ReadResult;
|
||||
|
||||
println!("Schala MetaInterpreter version {}", VERSION_STRING);
|
||||
println!("Type {}help for help with the REPL", self.interpreter_directive_sigil);
|
||||
|
||||
self.line_reader.load_history(".schala_history").unwrap_or(());
|
||||
|
||||
loop {
|
||||
let language_name = self.languages[self.current_language_index].get_language_name();
|
||||
let directives = self.get_directives();
|
||||
let tab_complete_handler = TabCompleteHandler::new(self.interpreter_directive_sigil, directives);
|
||||
self.line_reader.set_completer(std::sync::Arc::new(tab_complete_handler));
|
||||
|
||||
let prompt_str = format!("{} >> ", language_name);
|
||||
self.line_reader.set_prompt(&prompt_str);
|
||||
|
||||
match self.line_reader.read_line() {
|
||||
Err(e) => {
|
||||
println!("Terminal read error: {}", e);
|
||||
},
|
||||
Ok(ReadResult::Eof) => break,
|
||||
Ok(ReadResult::Signal(_)) => break,
|
||||
Ok(ReadResult::Input(ref input)) => {
|
||||
self.line_reader.add_history_unique(input.to_string());
|
||||
let output = match input.chars().nth(0) {
|
||||
Some(ch) if ch == self.interpreter_directive_sigil => self.handle_interpreter_directive(input),
|
||||
_ => Some(self.input_handler(input)),
|
||||
};
|
||||
if let Some(o) = output {
|
||||
println!("=> {}", o);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.line_reader.save_history(".schala_history").unwrap_or(());
|
||||
self.save_options();
|
||||
println!("Exiting...");
|
||||
}
|
||||
|
||||
fn input_handler(&mut self, input: &str) -> String {
|
||||
let ref mut language = self.languages[self.current_language_index];
|
||||
let interpreter_output = language.execute_pipeline(input, &self.options);
|
||||
interpreter_output.to_repl()
|
||||
}
|
||||
|
||||
fn get_directives(&self) -> CommandTree {
|
||||
let ref passes = self.get_cur_language().get_passes();
|
||||
|
||||
let passes_directives: Vec<CommandTree> = passes.iter()
|
||||
.map(|pass_descriptor| {
|
||||
let name = &pass_descriptor.name;
|
||||
if pass_descriptor.debug_options.len() == 0 {
|
||||
CommandTree::term(name, None)
|
||||
} else {
|
||||
let sub_opts: Vec<CommandTree> = pass_descriptor.debug_options.iter()
|
||||
.map(|o| CommandTree::term(o, None)).collect();
|
||||
CommandTree::NonTerminal(
|
||||
name.clone(),
|
||||
sub_opts,
|
||||
None
|
||||
)
|
||||
}
|
||||
}).collect();
|
||||
|
||||
CommandTree::Top(vec![
|
||||
CommandTree::term("exit", Some("exit the REPL")),
|
||||
CommandTree::term("quit", Some("exit the REPL")),
|
||||
CommandTree::term("help", Some("Print this help message")),
|
||||
CommandTree::NonTerminal(format!("debug"), vec![
|
||||
CommandTree::term("passes", None),
|
||||
CommandTree::NonTerminal(format!("show"), passes_directives.clone(), None),
|
||||
CommandTree::NonTerminal(format!("hide"), passes_directives.clone(), None),
|
||||
], Some(format!("show or hide pass info for a given pass, or display the names of all passes"))),
|
||||
CommandTree::NonTerminal(format!("lang"), vec![
|
||||
CommandTree::term("next", None),
|
||||
CommandTree::term("prev", None),
|
||||
CommandTree::NonTerminal(format!("go"), vec![], None)//TODO
|
||||
], Some(format!("switch between languages, or go directly to a langauge by name"))),
|
||||
])
|
||||
}
|
||||
|
||||
fn handle_interpreter_directive(&mut self, input: &str) -> Option<String> {
|
||||
let mut iter = input.chars();
|
||||
iter.next();
|
||||
let commands: Vec<&str> = iter
|
||||
.as_str()
|
||||
.split_whitespace()
|
||||
.collect();
|
||||
|
||||
let cmd: &str = match commands.get(0).clone() {
|
||||
None => return None,
|
||||
Some(s) => s
|
||||
};
|
||||
|
||||
match cmd {
|
||||
"exit" | "quit" => {
|
||||
self.save_options();
|
||||
exit(0)
|
||||
},
|
||||
"lang" | "language" => match commands.get(1) {
|
||||
Some(&"show") => {
|
||||
let mut buf = String::new();
|
||||
for (i, lang) in self.languages.iter().enumerate() {
|
||||
write!(buf, "{}{}\n", if i == self.current_language_index { "* "} else { "" }, lang.get_language_name()).unwrap();
|
||||
}
|
||||
Some(buf)
|
||||
},
|
||||
Some(&"go") => match commands.get(2) {
|
||||
None => Some(format!("Must specify a language name")),
|
||||
Some(&desired_name) => {
|
||||
for (i, _) in self.languages.iter().enumerate() {
|
||||
let lang_name = self.languages[i].get_language_name();
|
||||
if lang_name.to_lowercase() == desired_name.to_lowercase() {
|
||||
self.current_language_index = i;
|
||||
return Some(format!("Switching to {}", self.languages[self.current_language_index].get_language_name()));
|
||||
}
|
||||
}
|
||||
Some(format!("Language {} not found", desired_name))
|
||||
}
|
||||
},
|
||||
Some(&"next") | Some(&"n") => {
|
||||
self.current_language_index = (self.current_language_index + 1) % self.languages.len();
|
||||
Some(format!("Switching to {}", self.languages[self.current_language_index].get_language_name()))
|
||||
},
|
||||
Some(&"previous") | Some(&"p") | Some(&"prev") => {
|
||||
self.current_language_index = if self.current_language_index == 0 { self.languages.len() - 1 } else { self.current_language_index - 1 };
|
||||
Some(format!("Switching to {}", self.languages[self.current_language_index].get_language_name()))
|
||||
},
|
||||
Some(e) => Some(format!("Bad `lang(uage)` argument: {}", e)),
|
||||
None => Some(format!("Valid arguments for `lang(uage)` are `show`, `next`|`n`, `previous`|`prev`|`n`"))
|
||||
},
|
||||
"help" => {
|
||||
let mut buf = String::new();
|
||||
let ref lang = self.languages[self.current_language_index];
|
||||
let directives = match self.get_directives() {
|
||||
CommandTree::Top(children) => children,
|
||||
_ => panic!("Top-level CommandTree not Top")
|
||||
};
|
||||
|
||||
writeln!(buf, "MetaInterpreter options").unwrap();
|
||||
writeln!(buf, "-----------------------").unwrap();
|
||||
|
||||
for directive in directives {
|
||||
let trailer = " ";
|
||||
writeln!(buf, "{}{}- {}", directive.get_cmd(), trailer, directive.get_help()).unwrap();
|
||||
}
|
||||
|
||||
writeln!(buf, "").unwrap();
|
||||
writeln!(buf, "Language-specific help for {}", lang.get_language_name()).unwrap();
|
||||
writeln!(buf, "-----------------------").unwrap();
|
||||
writeln!(buf, "{}", lang.custom_interpreter_directives_help()).unwrap();
|
||||
Some(buf)
|
||||
},
|
||||
"debug" => self.handle_debug(commands),
|
||||
e => self.languages[self.current_language_index]
|
||||
.handle_custom_interpreter_directives(&commands)
|
||||
.or(Some(format!("Unknown command: {}", e)))
|
||||
}
|
||||
}
|
||||
fn handle_debug(&mut self, commands: Vec<&str>) -> Option<String> {
|
||||
let passes = self.get_cur_language().get_passes();
|
||||
match commands.get(1) {
|
||||
Some(&"passes") => Some(
|
||||
passes.into_iter()
|
||||
.map(|desc| {
|
||||
if self.options.debug_passes.contains_key(&desc.name) {
|
||||
let color = "green";
|
||||
format!("*{}", desc.name.color(color))
|
||||
} else {
|
||||
desc.name
|
||||
}
|
||||
})
|
||||
.intersperse(format!(" -> "))
|
||||
.collect()),
|
||||
b @ Some(&"show") | b @ Some(&"hide") => {
|
||||
let show = b == Some(&"show");
|
||||
let debug_pass: String = match commands.get(2) {
|
||||
Some(s) => s.to_string(),
|
||||
None => return Some(format!("Must specify a stage to debug")),
|
||||
};
|
||||
let pass_opt = commands.get(3);
|
||||
if let Some(desc) = passes.iter().find(|desc| desc.name == debug_pass) {
|
||||
let mut opts = vec![];
|
||||
if let Some(opt) = pass_opt {
|
||||
opts.push(opt.to_string());
|
||||
}
|
||||
let msg = format!("{} debug for pass {}", if show { "Enabling" } else { "Disabling" }, debug_pass);
|
||||
if show {
|
||||
self.options.debug_passes.insert(desc.name.clone(), PassDebugOptionsDescriptor { opts });
|
||||
} else {
|
||||
self.options.debug_passes.remove(&desc.name);
|
||||
}
|
||||
Some(msg)
|
||||
} else {
|
||||
Some(format!("Couldn't find stage: {}", debug_pass))
|
||||
}
|
||||
},
|
||||
_ => Some(format!("Unknown debug command"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn compilation_sequence(llvm_code: LLVMCodeString, sourcefile: &str) {
|
||||
use std::process::Command;
|
||||
|
||||
53
schala-repl/src/repl/command_tree.rs
Normal file
53
schala-repl/src/repl/command_tree.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum CommandTree {
|
||||
Terminal {
|
||||
name: String,
|
||||
help_msg: Option<String>,
|
||||
function: Option<Box<(fn() -> Option<String>)>>,
|
||||
},
|
||||
NonTerminal {
|
||||
name: String,
|
||||
children: Vec<CommandTree>,
|
||||
help_msg: Option<String>,
|
||||
function: Option<Box<(fn() -> Option<String>)>>,
|
||||
},
|
||||
Top(Vec<CommandTree>),
|
||||
}
|
||||
|
||||
impl CommandTree {
|
||||
pub fn term(s: &str, help: Option<&str>) -> CommandTree {
|
||||
CommandTree::Terminal {name: s.to_string(), help_msg: help.map(|x| x.to_string()), function: None }
|
||||
}
|
||||
|
||||
pub fn nonterm(s: &str, help: Option<&str>, children: Vec<CommandTree>) -> CommandTree {
|
||||
CommandTree::NonTerminal {
|
||||
name: s.to_string(),
|
||||
help_msg: help.map(|x| x.to_string()),
|
||||
children,
|
||||
function: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cmd(&self) -> &str {
|
||||
match self {
|
||||
CommandTree::Terminal { name, .. } => name.as_str(),
|
||||
CommandTree::NonTerminal {name, ..} => name.as_str(),
|
||||
CommandTree::Top(_) => "",
|
||||
}
|
||||
}
|
||||
pub fn get_help(&self) -> &str {
|
||||
match self {
|
||||
CommandTree::Terminal { help_msg, ..} => help_msg.as_ref().map(|s| s.as_str()).unwrap_or(""),
|
||||
CommandTree::NonTerminal { help_msg, .. } => help_msg.as_ref().map(|s| s.as_str()).unwrap_or(""),
|
||||
CommandTree::Top(_) => ""
|
||||
}
|
||||
}
|
||||
pub fn get_children(&self) -> Vec<&str> {
|
||||
match self {
|
||||
CommandTree::Terminal { .. } => vec![],
|
||||
CommandTree::NonTerminal { children, .. } => children.iter().map(|x| x.get_cmd()).collect(),
|
||||
CommandTree::Top(children) => children.iter().map(|x| x.get_cmd()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
355
schala-repl/src/repl/mod.rs
Normal file
355
schala-repl/src/repl/mod.rs
Normal file
@@ -0,0 +1,355 @@
|
||||
use std::fmt::Write as FmtWrite;
|
||||
use std::io::{Read, Write};
|
||||
use std::fs::File;
|
||||
use std::sync::Arc;
|
||||
|
||||
use colored::*;
|
||||
use itertools::Itertools;
|
||||
use language::{ProgrammingLanguageInterface, EvalOptions,
|
||||
PassDebugOptionsDescriptor};
|
||||
mod command_tree;
|
||||
use self::command_tree::CommandTree;
|
||||
|
||||
const HISTORY_SAVE_FILE: &'static str = ".schala_history";
|
||||
const OPTIONS_SAVE_FILE: &'static str = ".schala_repl";
|
||||
|
||||
pub struct Repl {
|
||||
options: EvalOptions,
|
||||
languages: Vec<Box<ProgrammingLanguageInterface>>,
|
||||
current_language_index: usize,
|
||||
interpreter_directive_sigil: char,
|
||||
line_reader: ::linefeed::interface::Interface<::linefeed::terminal::DefaultTerminal>,
|
||||
}
|
||||
|
||||
impl Repl {
|
||||
pub fn new(languages: Vec<Box<ProgrammingLanguageInterface>>, initial_index: usize) -> Repl {
|
||||
use linefeed::Interface;
|
||||
let i = if initial_index < languages.len() { initial_index } else { 0 };
|
||||
|
||||
let line_reader = Interface::new("schala-repl").unwrap();
|
||||
|
||||
Repl {
|
||||
options: Repl::get_options(),
|
||||
languages: languages,
|
||||
current_language_index: i,
|
||||
interpreter_directive_sigil: ':',
|
||||
line_reader
|
||||
}
|
||||
}
|
||||
|
||||
fn get_cur_language(&self) -> &ProgrammingLanguageInterface {
|
||||
self.languages[self.current_language_index].as_ref()
|
||||
}
|
||||
|
||||
fn get_options() -> EvalOptions {
|
||||
File::open(OPTIONS_SAVE_FILE)
|
||||
.and_then(|mut file| {
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
Ok(contents)
|
||||
})
|
||||
.and_then(|contents| {
|
||||
let options: EvalOptions = ::serde_json::from_str(&contents)?;
|
||||
Ok(options)
|
||||
}).unwrap_or(EvalOptions::default())
|
||||
}
|
||||
|
||||
fn save_options(&self) {
|
||||
let ref options = self.options;
|
||||
let read = File::create(OPTIONS_SAVE_FILE)
|
||||
.and_then(|mut file| {
|
||||
let buf = ::serde_json::to_string(options).unwrap();
|
||||
file.write_all(buf.as_bytes())
|
||||
});
|
||||
|
||||
if let Err(err) = read {
|
||||
println!("Error saving {} file {}", OPTIONS_SAVE_FILE, err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
use linefeed::ReadResult;
|
||||
|
||||
println!("Schala MetaInterpreter version {}", ::VERSION_STRING);
|
||||
println!("Type {}help for help with the REPL", self.interpreter_directive_sigil);
|
||||
|
||||
self.line_reader.load_history(HISTORY_SAVE_FILE).unwrap_or(());
|
||||
|
||||
loop {
|
||||
let language_name = self.get_cur_language().get_language_name();
|
||||
let directives = self.get_directives();
|
||||
let tab_complete_handler = TabCompleteHandler::new(self.interpreter_directive_sigil, directives);
|
||||
self.line_reader.set_completer(Arc::new(tab_complete_handler));
|
||||
|
||||
let prompt_str = format!("{} >> ", language_name);
|
||||
self.line_reader.set_prompt(&prompt_str).unwrap();
|
||||
|
||||
match self.line_reader.read_line() {
|
||||
Err(e) => {
|
||||
println!("Terminal read error: {}", e);
|
||||
},
|
||||
Ok(ReadResult::Eof) => break,
|
||||
Ok(ReadResult::Signal(_)) => break,
|
||||
Ok(ReadResult::Input(ref input)) => {
|
||||
self.line_reader.add_history_unique(input.to_string());
|
||||
let output = match input.chars().nth(0) {
|
||||
Some(ch) if ch == self.interpreter_directive_sigil => self.handle_interpreter_directive(input),
|
||||
_ => Some(self.input_handler(input)),
|
||||
};
|
||||
if let Some(o) = output {
|
||||
println!("=> {}", o);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.line_reader.save_history(HISTORY_SAVE_FILE).unwrap_or(());
|
||||
self.save_options();
|
||||
println!("Exiting...");
|
||||
}
|
||||
|
||||
fn input_handler(&mut self, input: &str) -> String {
|
||||
let ref mut language = self.languages[self.current_language_index];
|
||||
let interpreter_output = language.execute_pipeline(input, &self.options);
|
||||
interpreter_output.to_repl()
|
||||
}
|
||||
|
||||
fn get_directives(&self) -> CommandTree {
|
||||
let ref passes = self.get_cur_language().get_passes();
|
||||
|
||||
let passes_directives: Vec<CommandTree> = passes.iter()
|
||||
.map(|pass_descriptor| {
|
||||
let name = &pass_descriptor.name;
|
||||
if pass_descriptor.debug_options.len() == 0 {
|
||||
CommandTree::term(name, None)
|
||||
} else {
|
||||
let children: Vec<CommandTree> = pass_descriptor.debug_options.iter()
|
||||
.map(|o| CommandTree::term(o, None)).collect();
|
||||
CommandTree::NonTerminal {
|
||||
name: name.clone(),
|
||||
children,
|
||||
help_msg: None,
|
||||
function: None,
|
||||
}
|
||||
}
|
||||
}).collect();
|
||||
|
||||
CommandTree::Top(vec![
|
||||
CommandTree::term("exit", Some("exit the REPL")),
|
||||
CommandTree::term("quit", Some("exit the REPL")),
|
||||
CommandTree::term("help", Some("Print this help message")),
|
||||
CommandTree::nonterm("debug",
|
||||
Some("show or hide pass debug info for a given pass, or display the names of all passes, or turn timing on/off"),
|
||||
vec![
|
||||
CommandTree::term("passes", None),
|
||||
CommandTree::nonterm("show", None, passes_directives.clone()),
|
||||
CommandTree::nonterm("hide", None, passes_directives.clone()),
|
||||
CommandTree::nonterm("timing", None, vec![
|
||||
CommandTree::term("on", None),
|
||||
CommandTree::term("off", None),
|
||||
])
|
||||
]
|
||||
),
|
||||
CommandTree::nonterm("lang",
|
||||
Some("switch between languages, or go directly to a langauge by name"),
|
||||
vec![
|
||||
CommandTree::term("next", None),
|
||||
CommandTree::term("prev", None),
|
||||
CommandTree::nonterm("go", None, vec![]),
|
||||
]
|
||||
),
|
||||
CommandTree::term("doc", Some("Get language-specific help for an item")),
|
||||
])
|
||||
}
|
||||
|
||||
fn handle_interpreter_directive(&mut self, input: &str) -> Option<String> {
|
||||
let mut iter = input.chars();
|
||||
iter.next();
|
||||
let commands: Vec<&str> = iter
|
||||
.as_str()
|
||||
.split_whitespace()
|
||||
.collect();
|
||||
|
||||
let initial_cmd: &str = match commands.get(0).clone() {
|
||||
None => return None,
|
||||
Some(s) => s
|
||||
};
|
||||
|
||||
match initial_cmd {
|
||||
"exit" | "quit" => {
|
||||
self.save_options();
|
||||
::std::process::exit(0)
|
||||
},
|
||||
"lang" | "language" => match commands.get(1) {
|
||||
Some(&"show") => {
|
||||
let mut buf = String::new();
|
||||
for (i, lang) in self.languages.iter().enumerate() {
|
||||
write!(buf, "{}{}\n", if i == self.current_language_index { "* "} else { "" }, lang.get_language_name()).unwrap();
|
||||
}
|
||||
Some(buf)
|
||||
},
|
||||
Some(&"go") => match commands.get(2) {
|
||||
None => Some(format!("Must specify a language name")),
|
||||
Some(&desired_name) => {
|
||||
for (i, _) in self.languages.iter().enumerate() {
|
||||
let lang_name = self.languages[i].get_language_name();
|
||||
if lang_name.to_lowercase() == desired_name.to_lowercase() {
|
||||
self.current_language_index = i;
|
||||
return Some(format!("Switching to {}", self.languages[self.current_language_index].get_language_name()));
|
||||
}
|
||||
}
|
||||
Some(format!("Language {} not found", desired_name))
|
||||
}
|
||||
},
|
||||
Some(&"next") | Some(&"n") => {
|
||||
self.current_language_index = (self.current_language_index + 1) % self.languages.len();
|
||||
Some(format!("Switching to {}", self.languages[self.current_language_index].get_language_name()))
|
||||
},
|
||||
Some(&"previous") | Some(&"p") | Some(&"prev") => {
|
||||
self.current_language_index = if self.current_language_index == 0 { self.languages.len() - 1 } else { self.current_language_index - 1 };
|
||||
Some(format!("Switching to {}", self.languages[self.current_language_index].get_language_name()))
|
||||
},
|
||||
Some(e) => Some(format!("Bad `lang(uage)` argument: {}", e)),
|
||||
None => Some(format!("Valid arguments for `lang(uage)` are `show`, `next`|`n`, `previous`|`prev`|`n`"))
|
||||
},
|
||||
"help" => {
|
||||
let mut buf = String::new();
|
||||
let ref lang = self.languages[self.current_language_index];
|
||||
let directives = match self.get_directives() {
|
||||
CommandTree::Top(children) => children,
|
||||
_ => panic!("Top-level CommandTree not Top")
|
||||
};
|
||||
|
||||
writeln!(buf, "MetaInterpreter options").unwrap();
|
||||
writeln!(buf, "-----------------------").unwrap();
|
||||
|
||||
for directive in directives {
|
||||
let trailer = " ";
|
||||
writeln!(buf, "{}{}- {}", directive.get_cmd(), trailer, directive.get_help()).unwrap();
|
||||
}
|
||||
|
||||
writeln!(buf, "").unwrap();
|
||||
writeln!(buf, "Language-specific help for {}", lang.get_language_name()).unwrap();
|
||||
writeln!(buf, "-----------------------").unwrap();
|
||||
writeln!(buf, "{}", lang.custom_interpreter_directives_help()).unwrap();
|
||||
Some(buf)
|
||||
},
|
||||
"debug" => self.handle_debug(commands),
|
||||
"doc" => self.languages[self.current_language_index]
|
||||
.get_doc(&commands)
|
||||
.or(Some(format!("No docs implemented"))),
|
||||
e => {
|
||||
self.languages[self.current_language_index]
|
||||
.handle_custom_interpreter_directives(&commands)
|
||||
.or(Some(format!("Unknown command: {}", e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
fn handle_debug(&mut self, commands: Vec<&str>) -> Option<String> {
|
||||
let passes = self.get_cur_language().get_passes();
|
||||
match commands.get(1) {
|
||||
Some(&"timing") => match commands.get(2) {
|
||||
Some(&"on") => { self.options.debug_timing = true; None }
|
||||
Some(&"off") => { self.options.debug_timing = false; None }
|
||||
_ => return Some(format!(r#"Argument to "timing" must be "on" or "off""#)),
|
||||
},
|
||||
Some(&"passes") => Some(
|
||||
passes.into_iter()
|
||||
.map(|desc| {
|
||||
if self.options.debug_passes.contains_key(&desc.name) {
|
||||
let color = "green";
|
||||
format!("*{}", desc.name.color(color))
|
||||
} else {
|
||||
desc.name
|
||||
}
|
||||
})
|
||||
.intersperse(format!(" -> "))
|
||||
.collect()),
|
||||
b @ Some(&"show") | b @ Some(&"hide") => {
|
||||
let show = b == Some(&"show");
|
||||
let debug_pass: String = match commands.get(2) {
|
||||
Some(s) => s.to_string(),
|
||||
None => return Some(format!("Must specify a stage to debug")),
|
||||
};
|
||||
let pass_opt = commands.get(3);
|
||||
if let Some(desc) = passes.iter().find(|desc| desc.name == debug_pass) {
|
||||
let mut opts = vec![];
|
||||
if let Some(opt) = pass_opt {
|
||||
opts.push(opt.to_string());
|
||||
}
|
||||
let msg = format!("{} debug for pass {}", if show { "Enabling" } else { "Disabling" }, debug_pass);
|
||||
if show {
|
||||
self.options.debug_passes.insert(desc.name.clone(), PassDebugOptionsDescriptor { opts });
|
||||
} else {
|
||||
self.options.debug_passes.remove(&desc.name);
|
||||
}
|
||||
Some(msg)
|
||||
} else {
|
||||
Some(format!("Couldn't find stage: {}", debug_pass))
|
||||
}
|
||||
},
|
||||
_ => Some(format!("Unknown debug command"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TabCompleteHandler {
|
||||
sigil: char,
|
||||
top_level_commands: CommandTree,
|
||||
}
|
||||
|
||||
use linefeed::complete::{Completion, Completer};
|
||||
use linefeed::terminal::Terminal;
|
||||
|
||||
impl TabCompleteHandler {
|
||||
fn new(sigil: char, top_level_commands: CommandTree) -> TabCompleteHandler {
|
||||
TabCompleteHandler {
|
||||
top_level_commands,
|
||||
sigil,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Terminal> Completer<T> for TabCompleteHandler {
|
||||
fn complete(&self, word: &str, prompter: &::linefeed::prompter::Prompter<T>, start: usize, _end: usize) -> Option<Vec<Completion>> {
|
||||
let line = prompter.buffer();
|
||||
|
||||
if line.starts_with(&format!("{}", self.sigil)) {
|
||||
let mut words = line[1..(if start == 0 { 1 } else { start })].split_whitespace();
|
||||
let mut completions = Vec::new();
|
||||
let mut command_tree: Option<&CommandTree> = Some(&self.top_level_commands);
|
||||
|
||||
loop {
|
||||
match words.next() {
|
||||
None => {
|
||||
let top = match command_tree {
|
||||
Some(CommandTree::Top(_)) => true,
|
||||
_ => false
|
||||
};
|
||||
let word = if top { word.get(1..).unwrap() } else { word };
|
||||
for cmd in command_tree.map(|x| x.get_children()).unwrap_or(vec![]).into_iter() {
|
||||
if cmd.starts_with(word) {
|
||||
completions.push(Completion {
|
||||
completion: format!("{}{}", if top { ":" } else { "" }, cmd),
|
||||
display: Some(cmd.to_string()),
|
||||
suffix: ::linefeed::complete::Suffix::Some(' ')
|
||||
})
|
||||
}
|
||||
}
|
||||
break;
|
||||
},
|
||||
Some(s) => {
|
||||
let new_ptr: Option<&CommandTree> = command_tree.and_then(|cm| match cm {
|
||||
CommandTree::Top(children) => children.iter().find(|c| c.get_cmd() == s),
|
||||
CommandTree::NonTerminal { children, .. } => children.iter().find(|c| c.get_cmd() == s),
|
||||
CommandTree::Terminal { .. } => None,
|
||||
});
|
||||
command_tree = new_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(completions)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
|
||||
const c = 10
|
||||
let c = 10
|
||||
|
||||
fn add(a, b) {
|
||||
const c = a + b
|
||||
let c = a + b
|
||||
c
|
||||
}
|
||||
|
||||
var b = 20
|
||||
let mut b = 20
|
||||
|
||||
println(add(1,2))
|
||||
println(c + b)
|
||||
@@ -1,10 +1,15 @@
|
||||
fn main() {
|
||||
const a = 10
|
||||
const b = 20
|
||||
let a = 10
|
||||
let b = 20
|
||||
a + b
|
||||
}
|
||||
|
||||
//foo
|
||||
//this is a one-line comment
|
||||
|
||||
/* this is
|
||||
a multiline
|
||||
comment
|
||||
*/
|
||||
|
||||
print(main())
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ fn sua(x): Int {
|
||||
}
|
||||
|
||||
|
||||
//const a = getline()
|
||||
//let a = getline()
|
||||
|
||||
/*
|
||||
if a == "true" {
|
||||
|
||||
Reference in New Issue
Block a user