Compare commits

...

26 Commits

Author SHA1 Message Date
Jesse Brault
2dd3bf5a06 Merge remote-tracking branch 'origin/grammar-overhaul' into grammar-overhaul
# Conflicts:
#	src/parser/ast.yaml
2025-09-16 00:01:16 -05:00
Jesse Brault
fc2912edd2 Start adding ast build tests. 2025-09-16 00:00:26 -05:00
Jesse Brault
de8e2ba397 Small bugs. 2025-09-15 21:47:10 -05:00
Jesse Brault
ac9ff6ecec Squash bugs with ast gen. 2025-09-15 21:41:36 -05:00
Jesse Brault
608d89645e Add some Default impl. 2025-09-15 21:17:58 -05:00
Jesse Brault
f3c3e40eb2 Big refactor of ast gen. 2025-09-15 21:12:38 -05:00
Jesse Brault
5d640ca585 Refactor struct build-fn gen. 2025-09-15 20:10:59 -05:00
Jesse Brault
b5cdb8dd29 WIP refactor of deserialization and ast code generation. 2025-09-15 18:20:09 -05:00
Jesse Brault
2aee2cdd4e Refactor of ast yaml and schema. 2025-09-15 11:48:30 -05:00
Jesse Brault
4c2ee8f929 WIP on ast gen schema. 2025-09-14 21:19:33 -05:00
Jesse Brault
e9ccb0a5bd Add default rule match panic branch. 2025-09-14 21:15:48 -05:00
Jesse Brault
799d8762cd Fix no Pair import. 2025-09-14 21:10:55 -05:00
Jesse Brault
44f6ab10af WIP on enum generation and solving generated errors. 2025-09-14 21:06:58 -05:00
Jesse Brault
434df5642a Much work on ast gen, leaf enums and leaf structs. 2025-09-14 19:34:38 -05:00
Jesse Brault
42cc6720d1 Move around util fn and set up enum build fn mod. 2025-09-14 18:24:56 -05:00
Jesse Brault
0842690e6f More generation of node types. 2025-09-14 16:18:39 -05:00
Jesse Brault
c2c885d85b Get less errors in name_analysis module. 2025-09-14 15:59:18 -05:00
Jesse Brault
300e65a8d3 Add generation for node ast file. 2025-09-14 15:57:06 -05:00
Jesse Brault
b75e51ee41 Fill out build-fn generation for ast nodes. 2025-09-14 15:40:39 -05:00
Jesse Brault
968b950436 Handle deserialization of leaf_enum nodes. 2025-09-14 09:36:38 -05:00
Jesse Brault
0704e7d504 Skeleton code for leaf_enum specs. 2025-09-14 09:12:00 -05:00
Jesse Brault
152f5a6150 Start outputting build.rs file from ast gen. 2025-09-14 08:54:06 -05:00
Jesse Brault
e802fc70d8 Move all parser tests to generated tests. 2025-09-14 08:28:06 -05:00
Jesse Brault
c453557deb Add rerun if changed for tests parser tests directory. 2025-09-14 08:14:49 -05:00
Jesse Brault
9c43e28f32 Finish adding generated parser test generation. 2025-09-14 08:11:33 -05:00
Jesse Brault
024baf2064 Work on auto CST parser tests. 2025-09-13 18:36:18 -05:00
62 changed files with 3585 additions and 2860 deletions

12
Cargo.lock generated
View File

@ -169,6 +169,17 @@ dependencies = [
"typenum",
]
[[package]]
name = "cst-test-generator"
version = "0.1.0"
dependencies = [
"convert_case",
"prettyplease",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "deimos"
version = "0.1.0"
@ -176,6 +187,7 @@ dependencies = [
"ast-generator",
"clap",
"codespan-reporting",
"cst-test-generator",
"indoc",
"log",
"pest",

View File

@ -21,7 +21,8 @@ indoc = "2.0.6"
[build-dependencies]
ast-generator = { path = "ast-generator" }
cst-test-generator = { path = "cst-test-generator" }
[workspace]
resolver = "3"
members = ["ast-generator"]
members = ["ast-generator", "cst-test-generator"]

View File

@ -1,246 +0,0 @@
use crate::spec::{
BuildBooleanOn, ChildSpec, SingleBooleanChildToBuild, SingleChildToBuild,
SingleTypeChildToBuild, StructBuildSpec, VecChild, VecChildToBuild,
};
use convert_case::{Case, Casing};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
pub fn make_build_fn_name(s: &str) -> String {
format!("build_{}", s.to_case(Case::Snake))
}
fn make_vec_child_holder(vec_child: &VecChild) -> TokenStream {
let (child_ident, child_type_ident) = match vec_child.build() {
VecChildToBuild::Type(vec_type_child) => (
format_ident!("{}", vec_type_child.var_name()),
format_ident!("{}", vec_type_child.build()),
),
};
quote! {
let mut #child_ident: Vec<Box<#child_type_ident>> = vec![]
}
}
fn make_single_type_child_holder(single_type_child: &SingleTypeChildToBuild) -> TokenStream {
let child_ident = format_ident!("{}", single_type_child.var_name());
let child_type_ident = format_ident!("{}", single_type_child.build());
quote! {
let mut #child_ident: Option<Box<#child_type_ident>> = None
}
}
fn make_single_boolean_child_holder(
single_boolean_child: &SingleBooleanChildToBuild,
) -> TokenStream {
let child_ident = format_ident!("{}", single_boolean_child.var_name());
quote! {
let mut #child_ident: bool = false
}
}
fn make_child_holder(child_spec: &ChildSpec) -> Option<TokenStream> {
match child_spec {
ChildSpec::SkipChild(_) => None,
ChildSpec::VecChild(vec_child) => Some(make_vec_child_holder(vec_child)),
ChildSpec::SingleChild(single_child) => match single_child.build() {
SingleChildToBuild::Type(single_type_child) => {
Some(make_single_type_child_holder(single_type_child))
}
SingleChildToBuild::Boolean(boolean_child) => {
Some(make_single_boolean_child_holder(boolean_child))
}
},
}
}
fn make_match_action(child_spec: &ChildSpec) -> TokenStream {
match child_spec {
ChildSpec::SkipChild(_) => quote! {},
ChildSpec::VecChild(vec_child) => {
let (child_name_ident, build_fn_ident) = match vec_child.build() {
VecChildToBuild::Type(vec_type_child) => (
format_ident!("{}", vec_type_child.var_name()),
format_ident!("{}", vec_type_child.with()),
),
};
quote! {
#child_name_ident.push(Box::new(#build_fn_ident(inner_pair)))
}
}
ChildSpec::SingleChild(single_child) => match single_child.build() {
SingleChildToBuild::Type(single_type_child) => {
let child_name_ident = format_ident!("{}", single_type_child.var_name());
let build_fn_ident = format_ident!("{}", single_type_child.with());
quote! {
#child_name_ident = Some(Box::new(#build_fn_ident(inner_pair)))
}
}
SingleChildToBuild::Boolean(single_boolean_child) => {
let child_name_ident = format_ident!("{}", single_boolean_child.var_name());
match single_boolean_child.on() {
BuildBooleanOn::RulePresent => quote! {
#child_name_ident = true
},
}
}
},
}
}
fn make_rule_matcher(child_spec: &ChildSpec) -> TokenStream {
let rule_ident = match child_spec {
ChildSpec::SkipChild(skip_child) => format_ident!("{}", skip_child.rule()),
ChildSpec::VecChild(vec_child) => format_ident!("{}", vec_child.rule()),
ChildSpec::SingleChild(single_child) => format_ident!("{}", single_child.rule()),
};
let action = make_match_action(child_spec);
quote! {
Rule::#rule_ident => {
#action;
}
}
}
fn make_child_arg(child_spec: &ChildSpec) -> Option<TokenStream> {
match child_spec {
ChildSpec::SkipChild(_) => None,
ChildSpec::VecChild(vec_child) => {
let child_ident = match vec_child.build() {
VecChildToBuild::Type(vec_type_child) => {
format_ident!("{}", vec_type_child.var_name())
}
};
Some(quote! { #child_ident })
}
ChildSpec::SingleChild(single_child) => match single_child.build() {
SingleChildToBuild::Type(single_type_child) => {
let child_ident = format_ident!("{}", single_type_child.var_name());
if single_type_child.optional() {
Some(quote! { #child_ident })
} else if let Some(or_else) = single_type_child.or_else() {
let child_type_ident = format_ident!("{}", single_type_child.build());
let or_else_ident = format_ident!("{}", or_else);
Some(quote! {
#child_ident.unwrap_or_else(|| Box::new(#child_type_ident::#or_else_ident()))
})
} else {
Some(quote! { #child_ident.unwrap() })
}
}
SingleChildToBuild::Boolean(single_boolean_child) => {
let child_ident = format_ident!("{}", single_boolean_child.var_name());
Some(quote! { #child_ident })
}
},
}
}
fn make_return_value_stream(build_spec: &StructBuildSpec) -> TokenStream {
let type_ident = format_ident!("{}", build_spec.build());
let child_args = build_spec
.children()
.iter()
.map(|child| make_child_arg(child))
.filter(|child_arg| child_arg.is_some())
.map(|child_arg| child_arg.unwrap())
.collect::<Vec<_>>();
quote! {
#type_ident::new(
#(#child_args,)*
)
}
}
pub fn make_struct_build_fn(build_spec: &StructBuildSpec) -> TokenStream {
let build_fn_ident = format_ident!("{}", build_spec.with());
let pair_ident = format_ident!("{}_pair", build_spec.build().to_case(Case::Snake));
let return_type_ident = format_ident!("{}", build_spec.build());
let child_holders = build_spec
.children()
.iter()
.map(|child_spec| make_child_holder(child_spec))
.filter(|child_holder| child_holder.is_some())
.map(|child_holder| child_holder.unwrap())
.collect::<Vec<_>>();
let rule_matchers = build_spec
.children()
.iter()
.map(|child_spec| make_rule_matcher(child_spec))
.collect::<Vec<_>>();
let iter_stream = quote! {
for inner_pair in #pair_ident.into_inner() {
match inner_pair.as_rule() {
#(#rule_matchers)*
}
}
};
let new_stream = make_return_value_stream(build_spec);
quote! {
fn #build_fn_ident(#pair_ident: Pair<Rule>) -> #return_type_ident {
#(#child_holders;)*
#iter_stream
#new_stream
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::spec::VecTypeChildToBuild;
#[test]
fn vec_child_holder() {
let vec_child = VecChild::new(
"test_child",
"Test",
VecChildToBuild::Type(VecTypeChildToBuild::new(
"TestType",
"test_child",
"build_test_child",
)),
);
assert_eq!(
make_vec_child_holder(&vec_child).to_string(),
quote! {
let mut test_child: Vec<Box<TestType>> = vec![]
}
.to_string()
);
}
#[test]
fn single_type_child_holder() {
let single_type_child = SingleTypeChildToBuild::from_build_or_rule("TestType", None, false);
assert_eq!(
make_single_type_child_holder(&single_type_child).to_string(),
quote! {
let mut test_type: Option<Box<TestType>> = None
}
.to_string()
);
}
#[test]
fn single_boolean_child_holder() {
let single_boolean_child =
SingleBooleanChildToBuild::new("test_child", BuildBooleanOn::RulePresent);
assert_eq!(
make_single_boolean_child_holder(&single_boolean_child).to_string(),
quote! {
let mut test_child: bool = false
}
.to_string()
);
}
}

View File

@ -1,188 +1,285 @@
use crate::build_fn_gen::make_build_fn_name;
use crate::spec::{
BuildBooleanOn, BuildSpec, ChildSpec, EnumBuildSpec, EnumRule, SingleBooleanChildToBuild,
SingleChild, SingleChildToBuild, SingleTypeChildToBuild, SkipChild, StructBuildSpec, VecChild,
VecChildToBuild, VecTypeChildToBuild,
};
use crate::spec::{BooleanChildToBuild, BuildSpec, EnumBuildSpec, EnumRule, EnumRuleChild, EnumRuleChildKind, EnumRuleNodeChild, LeafEnumBuildSpec, LeafEnumRule, LeafStructBuildSpec, LeafStructMember, LeafStructMemberKind, MemberChild, MemberChildToBuild, NodeChildToBuild, ProductionBuildSpec, ProductionKind, ProductionStringFrom, SkipChild, StructBuildSpec, StructChildSpec, VecChild, VecChildToBuild, VecNodeChildToBuild};
use convert_case::{Case, Casing};
use yaml_rust2::{Yaml, YamlLoader};
fn get_skip(skip: &Yaml) -> bool {
skip.as_bool().unwrap_or_else(|| false)
fn get_as_bool(yaml: &Yaml) -> bool {
yaml.as_bool().unwrap_or_else(|| false)
}
fn get_vec(vec: &Yaml) -> bool {
vec.as_bool().unwrap_or_else(|| false)
fn unwrap_single_member_hash(hash: &Yaml) -> (String, &Yaml) {
let as_hash = hash.as_hash().unwrap();
if as_hash.is_empty() {
panic!("empty hash");
} else if as_hash.len() > 1 {
panic!("hash contains more than one key");
}
let (member_key, member_value) = as_hash.iter().collect::<Vec<(&Yaml, &Yaml)>>()[0];
let key_as_string = member_key.as_str().unwrap().to_string();
(key_as_string, member_value)
}
fn get_vec_child_to_build(build: &Yaml, name: &str, rule: &str) -> VecChildToBuild {
if build.is_hash() {
let build_type = build["build"].as_str().unwrap();
let var_name = build["var"]
.as_str()
.map(|s| s.to_string())
.unwrap_or_else(|| build_type.to_case(Case::Snake));
let with = build["with"]
.as_str()
.map(|s| s.to_string())
.unwrap_or_else(|| make_build_fn_name(build_type));
VecChildToBuild::Type(VecTypeChildToBuild::new(build_type, &var_name, &with))
} else {
let build_as_str = build.as_str().unwrap_or(rule);
VecChildToBuild::Type(VecTypeChildToBuild::new(
build_as_str,
name,
make_build_fn_name(build_as_str).as_str(),
))
}
}
fn get_vec_child(name: &str, rule: &str, build: &Yaml) -> ChildSpec {
ChildSpec::VecChild(VecChild::new(
name,
rule,
get_vec_child_to_build(build, name, rule),
))
}
fn get_single_child_to_build(name: &str, rule: &str, optional: bool, build: &Yaml) -> SingleChildToBuild {
if build.is_hash() {
match build["type"].as_str() {
Some(r#type) => {
let var_name = build["var"]
.as_str()
.map(|s| s.to_string())
.unwrap_or(name.to_string());
let on = build["on"].as_str().unwrap();
if r#type.eq("boolean") && on.eq("rule_present") {
SingleChildToBuild::Boolean(SingleBooleanChildToBuild::new(
&var_name,
BuildBooleanOn::RulePresent,
))
} else {
todo!("currently on boolean types with on: rule_present are supported")
}
fn deserialize_production_spec(rule: &str, production_yaml: &Yaml) -> ProductionBuildSpec {
let kind = match production_yaml["kind"].as_str().unwrap() {
"int" => ProductionKind::Int,
"long" => ProductionKind::Long,
"double" => ProductionKind::Double,
"boolean" => ProductionKind::Boolean,
"string" => {
let from = match production_yaml["from"].as_str().unwrap() {
"string_inner" => ProductionStringFrom::StringInner,
"whole_pair" => ProductionStringFrom::WholePair,
_ => panic!("invalid from: {}", production_yaml["from"].as_str().unwrap()),
};
ProductionKind::String(from)
},
None => {
let or_else = build["or_else"]
.as_str()
.map(|s| s.to_string())
.or_else(|| {
let or_else_default = build["or_else_default"]
.as_bool()
.unwrap_or_else(|| false);
if or_else_default {
Some(String::from("default"))
} else {
None
}
});
SingleChildToBuild::Type(SingleTypeChildToBuild::from_build_or_rule(
rule,
or_else,
optional,
))
}
}
} else {
match build.as_str() {
Some(s) => SingleChildToBuild::Type(SingleTypeChildToBuild::from_build_or_rule(s, None, optional)),
None => SingleChildToBuild::Type(SingleTypeChildToBuild::from_build_or_rule(rule, None, optional)),
}
}
_ => panic!("invalid kind: {}", production_yaml["kind"].as_str().unwrap()),
};
ProductionBuildSpec::new(rule, kind)
}
fn get_single_child(name: &str, rule: &str, optional: bool, build: &Yaml) -> ChildSpec {
ChildSpec::SingleChild(SingleChild::new(
name,
rule,
get_single_child_to_build(name, rule, optional, build),
))
fn deserialize_leaf_enum_rule(rule_yaml: &Yaml) -> Box<LeafEnumRule> {
Box::new(LeafEnumRule::new(rule_yaml.as_str().unwrap()))
}
fn get_child_specs(children: &Yaml) -> Vec<ChildSpec> {
children
fn deserialize_leaf_enum_rules(rules_yaml: &Yaml) -> Vec<Box<LeafEnumRule>> {
rules_yaml
.as_vec()
.unwrap()
.iter()
.map(|child_spec| {
if child_spec.is_hash() {
let as_hash = child_spec.as_hash().unwrap();
let (name, props) = as_hash
.map(|rule_yaml| {
deserialize_leaf_enum_rule(rule_yaml)
})
.collect()
}
fn deserialize_enum_rule_custom_child(rule_props: &Yaml) -> Option<Box<EnumRuleChild>> {
if !rule_props["child"].as_bool().unwrap_or(true) {
None
} else {
let kind = match rule_props["kind"].as_str().unwrap() {
"int" => EnumRuleChildKind::Int,
"long" => EnumRuleChildKind::Long,
"double" => EnumRuleChildKind::Double,
"usize" => EnumRuleChildKind::USize,
"string" => EnumRuleChildKind::String,
"boolean" => EnumRuleChildKind::Boolean,
_ => panic!("unsupported enum rule kind: {}", rule_props["kind"].as_str().unwrap()),
};
Some(Box::new(EnumRuleChild::new(Box::new(kind))))
}
}
fn deserialize_enum_rule_node_child(rule: &str) -> Box<EnumRuleChild> {
Box::new(EnumRuleChild::new(Box::new(
EnumRuleChildKind::Node(EnumRuleNodeChild::new(rule)),
)))
}
fn deserialize_enum_rule(rule_yaml: &Yaml) -> Box<EnumRule> {
if rule_yaml.is_hash() {
let (rule, rule_props) = unwrap_single_member_hash(rule_yaml);
Box::new(EnumRule::new(
&rule,
deserialize_enum_rule_custom_child(rule_props)
))
} else {
let rule_as_str = rule_yaml.as_str().unwrap();
Box::new(EnumRule::new(
rule_as_str,
Some(deserialize_enum_rule_node_child(rule_as_str)),
))
}
}
fn deserialize_enum_rules(rules_yaml: &Yaml) -> Vec<Box<EnumRule>> {
rules_yaml
.as_vec()
.unwrap()
.iter()
.next()
.map(|(name, props)| (name.as_str().unwrap(), props))
.unwrap();
.map(|rule_yaml| deserialize_enum_rule(rule_yaml))
.collect()
}
fn deserialize_leaf_struct_member(name: &str, props: &Yaml) -> Box<LeafStructMember> {
let kind = props["kind"].as_str().unwrap();
if kind == "string" {
Box::new(LeafStructMember::new(name, LeafStructMemberKind::String))
} else {
panic!("invalid member kind: {}", kind);
}
}
fn deserialize_leaf_struct_members(members_yaml: &Yaml) -> Vec<Box<LeafStructMember>> {
members_yaml
.as_vec()
.unwrap()
.iter()
.map(|member| {
let (member_name, member_props) = unwrap_single_member_hash(member);
deserialize_leaf_struct_member(&member_name, member_props)
})
.collect()
}
fn deserialize_member_node_to_build(
rule: &str,
build_props: &Yaml,
optional: bool,
) -> Box<MemberChildToBuild> {
let or_else = build_props["or_else"]
.as_str()
.or_else(|| {
if get_as_bool(&build_props["or_else_default"]) {
Some("default")
} else {
None
}
})
.map(ToString::to_string);
Box::new(MemberChildToBuild::Node(NodeChildToBuild::new(
rule, or_else, optional,
)))
}
fn deserialize_member_boolean_to_build(name: &str) -> Box<MemberChildToBuild> {
Box::new(MemberChildToBuild::Boolean(BooleanChildToBuild::new(name)))
}
fn deserialize_member_child_to_build(
name: &str,
rule: &str,
props: &Yaml,
optional: bool,
) -> Box<MemberChildToBuild> {
if props["build"].is_hash() {
let build_props = &props["build"];
let kind = build_props["kind"].as_str().or(Some("node")).unwrap();
if kind == "node" {
deserialize_member_node_to_build(rule, build_props, optional)
} else if kind == "boolean" {
deserialize_member_boolean_to_build(name)
} else {
panic!("unsupported kind: {}", kind)
}
} else {
let optional = get_as_bool(&props["optional"]);
Box::new(MemberChildToBuild::Node(NodeChildToBuild::new(rule, None, optional)))
}
}
fn deserialize_member_child(
name: &str,
rule: &str,
optional: bool,
props: &Yaml,
) -> Box<StructChildSpec> {
Box::new(StructChildSpec::MemberChild(MemberChild::new(
name,
rule,
deserialize_member_child_to_build(name, rule, props, optional),
)))
}
fn deserialize_vec_node_child(name: &str, props: &Yaml) -> Box<StructChildSpec> {
let rule = props["rule"].as_str().unwrap();
Box::new(StructChildSpec::VecChild(VecChild::new(
name,
rule,
Box::new(VecChildToBuild::Node(VecNodeChildToBuild::new(rule))),
)))
}
fn deserialize_vec_string_child(name: &str, props: &Yaml) -> Box<StructChildSpec> {
let rule = props["rule"].as_str().unwrap();
Box::new(StructChildSpec::VecChild(VecChild::new(
name,
rule,
Box::new(VecChildToBuild::String),
)))
}
fn deserialize_vec_child(name: &str, props: &Yaml) -> Box<StructChildSpec> {
let kind = props["kind"].as_str().or_else(|| Some("node")).unwrap();
if kind == "node" {
deserialize_vec_node_child(name, props)
} else if kind == "string" {
deserialize_vec_string_child(name, props)
} else {
panic!("invalid kind: {}", kind);
}
}
fn deserialize_skip_child(name: &str, rule: &str) -> Box<StructChildSpec> {
Box::new(StructChildSpec::SkipChild(SkipChild::new(name, rule)))
}
fn deserialize_struct_hash_child(child: &Yaml) -> Box<StructChildSpec> {
let (name, props) = unwrap_single_member_hash(child);
let rule = props["rule"]
.as_str()
.map(|s| s.to_string())
.unwrap_or(name.to_case(Case::Pascal));
if get_skip(&props["skip"]) {
return ChildSpec::SkipChild(SkipChild::new(name, &rule));
}
let build = &props["build"];
if get_vec(&props["vec"]) {
get_vec_child(name, &rule, build)
if get_as_bool(&props["skip"]) {
deserialize_skip_child(&name, &rule)
} else {
let optional = props["optional"]
.as_bool()
.unwrap_or_else(|| false);
get_single_child(name, &rule, optional, build)
}
if get_as_bool(&props["vec"]) {
deserialize_vec_child(&name, props)
} else {
ChildSpec::SingleChild(SingleChild::from_name_snake(child_spec.as_str().unwrap()))
let optional = get_as_bool(&props["optional"]);
deserialize_member_child(&name, &rule, optional, props)
}
}
})
.collect()
}
fn get_enum_rule_specs(rule_specs: &Yaml) -> Vec<EnumRule> {
rule_specs
fn deserialize_struct_string_child(child: &Yaml) -> Box<StructChildSpec> {
let child_as_str = child.as_str().unwrap();
let child_name_pascal = child_as_str.to_case(Case::Pascal);
Box::new(StructChildSpec::MemberChild(MemberChild::new(
child_as_str,
&child_name_pascal,
Box::new(MemberChildToBuild::Node(NodeChildToBuild::new(
&child_name_pascal,
None,
false,
))),
)))
}
fn deserialize_struct_children(children: &Yaml) -> Vec<Box<StructChildSpec>> {
children
.as_vec()
.unwrap()
.iter()
.map(|rule_spec| {
if rule_spec.is_hash() {
let rule = rule_spec["rule"].as_str().unwrap();
let build = rule_spec["build"].as_str().unwrap();
let with = if !rule_spec["with"].is_badvalue() {
rule_spec.as_str().unwrap().to_string()
.map(|child_spec| {
if child_spec.is_hash() {
deserialize_struct_hash_child(child_spec)
} else {
make_build_fn_name(build)
};
EnumRule::new(rule, build, &with)
} else {
EnumRule::from_rule(rule_spec.as_str().unwrap())
deserialize_struct_string_child(child_spec)
}
})
.collect()
}
fn deserialize_build_spec(build_spec_name: &Yaml, build_spec: &Yaml) -> BuildSpec {
let build_spec_name_pascal = build_spec_name.as_str().unwrap();
let children = &build_spec["children"];
if children.is_array() {
let child_specs = get_child_specs(children);
BuildSpec::Struct(StructBuildSpec::from_name(
build_spec_name_pascal,
child_specs,
))
fn deserialize_build_spec(build_spec_name: &str, build_spec: &Yaml) -> BuildSpec {
if build_spec["children"].is_array() {
let children = deserialize_struct_children(&build_spec["children"]);
BuildSpec::Struct(StructBuildSpec::new(build_spec_name, children))
} else if build_spec["members"].is_array() {
let members = deserialize_leaf_struct_members(&build_spec["members"]);
BuildSpec::LeafStruct(LeafStructBuildSpec::new(build_spec_name, members))
} else if build_spec["rules"].is_array() {
let rules = deserialize_enum_rules(&build_spec["rules"]);
BuildSpec::Enum(EnumBuildSpec::new(build_spec_name, rules))
} else if build_spec["leaf_rules"].is_array() {
let leaf_rules = deserialize_leaf_enum_rules(&build_spec["leaf_rules"]);
BuildSpec::LeafEnum(LeafEnumBuildSpec::new(build_spec_name, leaf_rules))
} else if build_spec["produce"].is_hash() {
BuildSpec::Production(deserialize_production_spec(build_spec_name, &build_spec["produce"]))
} else {
let rule_specs = &build_spec["rules"];
if rule_specs.is_array() {
let enum_rules = get_enum_rule_specs(rule_specs);
BuildSpec::Enum(EnumBuildSpec::from_name(build_spec_name_pascal, enum_rules))
} else {
panic!("either children or rules must be present on the build spec");
}
panic!("Expected a node spec for either a struct, leaf_struct, enum, leaf_enum node type, or a production type.");
}
}
@ -193,6 +290,9 @@ pub fn deserialize_yaml_spec(yaml: &str) -> Vec<BuildSpec> {
doc.as_hash()
.unwrap()
.iter()
.map(|(build_spec_name, build_spec)| deserialize_build_spec(build_spec_name, build_spec))
.map(|(build_spec_name, build_spec)| {
let name_as_str = build_spec_name.as_str().unwrap();
deserialize_build_spec(name_as_str, build_spec)
})
.collect()
}

View File

@ -0,0 +1,48 @@
use crate::spec::{EnumBuildSpec, EnumRuleChildKind};
use crate::util::{make_build_fn_name, make_build_pair};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
pub fn make_enum_build_fn(enum_build_spec: &EnumBuildSpec) -> TokenStream {
let build_fn_ident = format_ident!("{}", make_build_fn_name(enum_build_spec.build()));
let pair_ident = format_ident!("{}", make_build_pair(enum_build_spec.build()));
let return_type_ident = format_ident!("{}", enum_build_spec.build());
let rule_branches = enum_build_spec
.rules()
.map(|enum_rule| {
let rule_ident = format_ident!("{}", enum_rule.rule());
if let Some(child) = enum_rule.child() {
let inner_builder = match child.kind() {
EnumRuleChildKind::Node(node_child) => {
let inner_build_fn_ident =
format_ident!("{}", make_build_fn_name(node_child.build()));
quote! { #inner_build_fn_ident(inner_pair) }
}
_ => {
let inner_build_fn_ident =
format_ident!("{}", make_build_fn_name(enum_rule.rule()));
quote! { #inner_build_fn_ident(inner_pair) }
}
};
quote! {
Rule::#rule_ident => #return_type_ident::#rule_ident(#inner_builder)
}
} else {
quote! {
Rule::#rule_ident => #return_type_ident::#rule_ident
}
}
})
.collect::<Vec<_>>();
quote! {
fn #build_fn_ident(#pair_ident: Pair<Rule>) -> #return_type_ident {
let inner_pair = #pair_ident.into_inner().next().unwrap();
match inner_pair.as_rule() {
#(#rule_branches,)*
_ => unreachable!()
}
}
}
}

View File

@ -0,0 +1,29 @@
use crate::spec::LeafEnumBuildSpec;
use crate::util::{make_build_fn_name, make_build_pair};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
pub fn make_leaf_enum_build_fn(leaf_enum_build_spec: &LeafEnumBuildSpec) -> TokenStream {
let build_fn_ident = format_ident!("{}", make_build_fn_name(leaf_enum_build_spec.build()));
let pair_ident = format_ident!("{}", make_build_pair(leaf_enum_build_spec.build()));
let return_type_ident = format_ident!("{}", leaf_enum_build_spec.build());
let rule_branches = leaf_enum_build_spec.rules()
.map(|leaf_enum_rule| {
let rule_ident = format_ident!("{}", leaf_enum_rule.rule());
quote! {
Rule::#rule_ident => #return_type_ident::#rule_ident
}
})
.collect::<Vec<_>>();
quote! {
fn #build_fn_ident(#pair_ident: Pair<Rule>) -> #return_type_ident {
let inner_pair = #pair_ident.into_inner().next().unwrap();
match inner_pair.as_rule() {
#(#rule_branches,)*
_ => unreachable!()
}
}
}
}

View File

@ -0,0 +1,38 @@
use crate::spec::{LeafStructBuildSpec, LeafStructMemberKind};
use crate::util::{make_build_fn_name, make_build_pair};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
pub fn make_leaf_struct_build_fn(build_spec: &LeafStructBuildSpec) -> TokenStream {
let build_fn_ident = format_ident!("{}", make_build_fn_name(build_spec.build()));
let pair_ident = format_ident!("{}", make_build_pair(build_spec.build()));
let return_type_ident = format_ident!("{}", build_spec.build());
let child_builders = build_spec
.members()
.map(|member| {
let child_ident = format_ident!("{}", member.name());
match member.kind() {
LeafStructMemberKind::String => {
quote! {
let #child_ident = #pair_ident.as_str()
}
}
}
})
.collect::<Vec<_>>();
let child_args = build_spec
.members()
.map(|member| format_ident!("{}", member.name()))
.collect::<Vec<_>>();
quote! {
fn #build_fn_ident(#pair_ident: Pair<Rule>) -> #return_type_ident {
#(#child_builders;)*
#return_type_ident::new(
#(#child_args,)*
)
}
}
}

View File

@ -1,24 +1,44 @@
mod build_fn_gen;
mod deserialize;
pub mod deserialize;
mod enum_build_fn;
mod leaf_enum_build_fn;
mod leaf_struct_build_fn;
mod production_build_fn;
mod spec;
mod struct_build_fn;
mod type_gen;
mod util;
use crate::build_fn_gen::make_struct_build_fn;
use crate::enum_build_fn::make_enum_build_fn;
use crate::leaf_enum_build_fn::make_leaf_enum_build_fn;
use crate::leaf_struct_build_fn::make_leaf_struct_build_fn;
use crate::production_build_fn::make_production_build_fn;
use crate::struct_build_fn::make_struct_build_fn;
use crate::type_gen::make_type;
use proc_macro2::TokenStream;
use quote::quote;
use spec::BuildSpec;
use syn::File;
use syn::spanned::Spanned;
fn debug_built_spec(build_spec: &BuildSpec, token_stream: &TokenStream) {
println!("*** BuildSpec ***");
match build_spec {
BuildSpec::Enum(enum_build_spec) => {
println!("Spec name: {}", enum_build_spec.name());
println!("Enum Spec - build: {}", enum_build_spec.build());
}
BuildSpec::LeafEnum(leaf_enum_build_spec) => {
println!("Leaf Enum Spec - build: {}", leaf_enum_build_spec.build());
}
BuildSpec::Struct(struct_build_spec) => {
println!("Spec name: {}", struct_build_spec.name());
println!("Struct Spec - build: {}", struct_build_spec.build());
}
BuildSpec::LeafStruct(leaf_struct_build_spec) => {
println!(
"Leaf Struct Spec - build: {}",
leaf_struct_build_spec.build()
);
}
BuildSpec::Production(production_build_spec) => {
println!("Production Spec - rule: {}", production_build_spec.rule())
}
}
println!("{:#?}", token_stream);
@ -26,30 +46,74 @@ fn debug_built_spec(build_spec: &BuildSpec, token_stream: &TokenStream) {
println!("{}", prettyplease::unparse(&parsed));
}
pub fn test_dump() -> String {
let build_specs = deserialize::deserialize_yaml_spec(include_str!("../../src/parser/ast.yaml"));
let mut streams: Vec<TokenStream> = vec![];
for build_spec in &build_specs {
let type_stream = make_type(build_spec);
debug_built_spec(build_spec, &type_stream);
streams.push(type_stream);
pub struct AstGeneratedFile {
pub name: String,
pub contents: String,
}
for build_spec in &build_specs {
match build_spec {
BuildSpec::Enum(_) => {}
BuildSpec::Struct(struct_spec) => {
let struct_build_fn_stream = make_struct_build_fn(struct_spec);
debug_built_spec(build_spec, &struct_build_fn_stream);
streams.push(struct_build_fn_stream);
}
}
}
let combined = quote! {
#(#streams)*
};
let file: File = syn::parse2(combined).unwrap();
fn token_stream_to_string(token_stream: TokenStream) -> String {
let file: File = syn::parse2(token_stream).unwrap();
prettyplease::unparse(&file)
}
fn generate_build_file(build_specs: &[BuildSpec]) -> AstGeneratedFile {
let build_fns = build_specs
.iter()
.map(|build_spec| match build_spec {
BuildSpec::Enum(enum_build_spec) => {
let stream = make_enum_build_fn(enum_build_spec);
debug_built_spec(build_spec, &stream);
stream
}
BuildSpec::LeafEnum(leaf_enum_build_spec) => {
let stream = make_leaf_enum_build_fn(leaf_enum_build_spec);
debug_built_spec(build_spec, &stream);
stream
}
BuildSpec::Struct(struct_build_spec) => {
let stream = make_struct_build_fn(struct_build_spec);
debug_built_spec(build_spec, &stream);
stream
}
BuildSpec::LeafStruct(leaf_struct_build_spec) => {
let stream = make_leaf_struct_build_fn(leaf_struct_build_spec);
debug_built_spec(build_spec, &stream);
stream
}
BuildSpec::Production(production_build_spec) => {
let stream = make_production_build_fn(production_build_spec);
debug_built_spec(build_spec, &stream);
stream
}
})
.collect::<Vec<_>>();
let combined = quote! {
#(#build_fns)*
};
AstGeneratedFile {
name: String::from("build.rs"),
contents: token_stream_to_string(combined),
}
}
fn generate_node_file(build_specs: &[BuildSpec]) -> AstGeneratedFile {
let types = build_specs
.iter()
.map(|build_spec| make_type(build_spec))
.filter(Option::is_some)
.collect::<Vec<_>>();
let combined = quote! {
#(#types)*
};
AstGeneratedFile {
name: String::from("node.rs"),
contents: token_stream_to_string(combined),
}
}
pub fn generate_files(build_specs: &[BuildSpec]) -> Vec<AstGeneratedFile> {
vec![
generate_build_file(build_specs),
generate_node_file(build_specs),
]
}

View File

@ -1,6 +0,0 @@
use ast_generator::test_dump;
fn main() {
let s = test_dump();
println!("{}", s);
}

View File

@ -0,0 +1,87 @@
use crate::spec::{ProductionBuildSpec, ProductionKind, ProductionStringFrom};
use crate::util::{make_build_fn_name, make_build_pair};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
pub fn make_production_build_fn(production_build_spec: &ProductionBuildSpec) -> TokenStream {
let build_fn_ident = format_ident!("{}", make_build_fn_name(production_build_spec.rule()));
let return_type_ident = match production_build_spec.kind() {
ProductionKind::Int => format_ident!("i32"),
ProductionKind::Long => format_ident!("i64"),
ProductionKind::Double => format_ident!("f64"),
ProductionKind::String(_) => format_ident!("String"),
ProductionKind::Boolean => format_ident!("bool"),
};
let pair_ident = format_ident!("{}", make_build_pair(production_build_spec.rule()));
let pair_mapper = match production_build_spec.kind() {
ProductionKind::Int => quote! {
let number_base_pair = #pair_ident.into_inner()
.next()
.unwrap();
let inner_number_base_pair = number_base_pair.into_inner()
.next()
.unwrap();
match inner_number_base_pair.as_rule() {
Rule::BinaryBase => {
todo!()
}
Rule::HexadecimalBase => {
todo!()
}
Rule::DecimalBase => {
inner_number_base_pair.as_str().parse::<i32>().unwrap()
}
_ => panic!()
}
},
ProductionKind::Long => quote! {
let number_base_pair = #pair_ident.into_inner()
.next()
.unwrap();
let inner_number_base_pair = number_base_pair.into_inner()
.next()
.unwrap();
match inner_number_base_pair.as_rule() {
Rule::BinaryBase => {
todo!()
}
Rule::HexadecimalBase => {
todo!()
}
Rule::DecimalBase => {
inner_number_base_pair.as_str().parse::<i64>().unwrap()
}
_ => panic!()
}
},
ProductionKind::Double => quote! {
#pair_ident.as_str().parse::<f64>().unwrap()
},
ProductionKind::String(from) => match from {
ProductionStringFrom::StringInner => {
quote! {
#pair_ident.into_inner()
.next()
.unwrap()
.as_str()
.to_string()
}
}
ProductionStringFrom::WholePair => {
quote! {
#pair_ident.as_str().to_string()
}
}
},
ProductionKind::Boolean => quote! {
#pair_ident.as_str().parse::<bool>().unwrap()
},
};
quote! {
fn #build_fn_ident(#pair_ident: Pair<Rule>) -> #return_type_ident {
#pair_mapper
}
}
}

View File

@ -1,62 +1,47 @@
use crate::build_fn_gen::make_build_fn_name;
use convert_case::{Case, Casing};
pub enum BuildSpec {
Enum(EnumBuildSpec),
LeafEnum(LeafEnumBuildSpec),
Struct(StructBuildSpec),
LeafStruct(LeafStructBuildSpec),
Production(ProductionBuildSpec),
}
// Enum build spec
pub struct EnumBuildSpec {
name: String,
build: String,
rules: Vec<EnumRule>,
rules: Vec<Box<EnumRule>>,
}
impl EnumBuildSpec {
pub fn from_name(name: &str, rules: Vec<EnumRule>) -> Self {
pub fn new(build: &str, rules: Vec<Box<EnumRule>>) -> Self {
EnumBuildSpec {
name: name.to_string(),
build: name.to_string(),
build: build.to_string(),
rules,
}
}
/// The top-level key for the build spec in the yaml file.
pub fn name(&self) -> &str {
&self.name
}
/// The enum type to be built, in Pascal case.
pub fn build(&self) -> &str {
&self.build
}
/// The individual rule specs.
pub fn rules(&self) -> &[EnumRule] {
&self.rules
pub fn rules(&self) -> impl Iterator<Item = &EnumRule> {
self.rules.iter().map(Box::as_ref)
}
}
pub struct EnumRule {
rule: String,
build: String,
with: String,
child: Option<Box<EnumRuleChild>>
}
impl EnumRule {
pub fn from_rule(rule: &str) -> Self {
pub fn new(rule: &str, child: Option<Box<EnumRuleChild>>) -> Self {
Self {
rule: rule.to_string(),
build: rule.to_string(),
with: make_build_fn_name(rule),
}
}
pub fn new(rule: &str, build: &str, with: &str) -> Self {
Self {
rule: rule.to_string(),
build: build.to_string(),
with: with.to_string(),
child
}
}
@ -65,66 +50,125 @@ impl EnumRule {
&self.rule
}
/// The type to build, in Pascal case.
pub fn child(&self) -> Option<&EnumRuleChild> {
if let Some(child) = &self.child {
Some(child.as_ref())
} else {
None
}
}
}
pub struct EnumRuleChild {
kind: Box<EnumRuleChildKind>
}
impl EnumRuleChild {
pub fn new(kind: Box<EnumRuleChildKind>) -> Self {
Self { kind }
}
pub fn kind(&self) -> &EnumRuleChildKind {
&self.kind
}
}
pub enum EnumRuleChildKind {
Node(EnumRuleNodeChild),
Int,
Long,
Double,
USize,
String,
Boolean
}
pub struct EnumRuleNodeChild {
build: String
}
impl EnumRuleNodeChild {
pub fn new(build: &str) -> Self {
Self {
build: build.to_string(),
}
}
pub fn build(&self) -> &str {
&self.build
}
}
// Leaf enum build spec
pub struct LeafEnumBuildSpec {
build: String,
rules: Vec<Box<LeafEnumRule>>,
}
impl LeafEnumBuildSpec {
pub fn new(build: &str, rules: Vec<Box<LeafEnumRule>>) -> Self {
Self {
build: build.to_string(),
rules
}
}
pub fn build(&self) -> &str {
&self.build
}
/// The build-fn name, in snake case.
pub fn with(&self) -> &str {
&self.with
pub fn rules(&self) -> impl Iterator<Item = &LeafEnumRule> {
self.rules.iter().map(Box::as_ref)
}
}
pub struct LeafEnumRule {
rule: String,
}
impl LeafEnumRule {
pub fn new(rule: &str) -> Self {
Self {
rule: rule.to_string(),
}
}
pub fn rule(&self) -> &str {
&self.rule
}
}
// Struct build spec
pub struct StructBuildSpec {
name: String,
build: String,
var_name: String,
with: String,
children: Vec<ChildSpec>,
children: Vec<Box<StructChildSpec>>,
}
impl StructBuildSpec {
pub fn from_name(name: &str, child_specs: Vec<ChildSpec>) -> Self {
pub fn new(build: &str, children: Vec<Box<StructChildSpec>>) -> Self {
Self {
name: name.to_string(),
build: name.to_string(),
var_name: name.to_case(Case::Snake),
with: make_build_fn_name(name),
children: child_specs,
build: build.to_string(),
children
}
}
/// The top-level name of this build spec.
pub fn name(&self) -> &str {
&self.name
}
/// The type to be built, in Pascal case.
pub fn build(&self) -> &str {
&self.build
}
/// The name of the variable to be built, in snake case.
pub fn var_name(&self) -> &str {
&self.var_name
}
/// The build-fn name, in snake case.
pub fn with(&self) -> &str {
&self.with
}
/// The children for this build spec.
pub fn children(&self) -> &[ChildSpec] {
&self.children
pub fn children(&self) -> impl Iterator<Item = &StructChildSpec> {
self.children.iter().map(Box::as_ref)
}
}
pub enum ChildSpec {
pub enum StructChildSpec {
SkipChild(SkipChild),
VecChild(VecChild),
SingleChild(SingleChild),
MemberChild(MemberChild),
}
pub struct SkipChild {
@ -154,11 +198,11 @@ impl SkipChild {
pub struct VecChild {
name: String,
rule: String,
build: VecChildToBuild,
build: Box<VecChildToBuild>,
}
impl VecChild {
pub fn new(name: &str, rule: &str, build: VecChildToBuild) -> Self {
pub fn new(name: &str, rule: &str, build: Box<VecChildToBuild>) -> Self {
Self {
name: name.to_string(),
rule: rule.to_string(),
@ -184,62 +228,35 @@ impl VecChild {
#[derive(Debug)]
pub enum VecChildToBuild {
Type(VecTypeChildToBuild),
Node(VecNodeChildToBuild),
String
}
#[derive(Debug)]
pub struct VecTypeChildToBuild {
pub struct VecNodeChildToBuild {
build: String,
var_name: String,
with: String,
}
impl VecTypeChildToBuild {
pub fn new(build: &str, var_name: &str, with: &str) -> Self {
Self {
build: build.to_string(),
var_name: var_name.to_string(),
with: with.to_string(),
}
impl VecNodeChildToBuild {
pub fn new(build: &str) -> Self {
Self { build: build.to_string() }
}
/// The type to build, in Pascal case.
pub fn build(&self) -> &str {
&self.build
}
/// The name of the variable to build, in snake case.
pub fn var_name(&self) -> &str {
&self.var_name
}
/// The build-fn name.
pub fn with(&self) -> &str {
&self.with
}
}
#[derive(Debug)]
pub struct SingleChild {
pub struct MemberChild {
name: String,
rule: String,
build: SingleChildToBuild,
build: Box<MemberChildToBuild>,
}
impl SingleChild {
pub fn from_name_snake(name: &str) -> Self {
Self {
name: name.to_string(),
rule: name.to_case(Case::Pascal),
build: SingleChildToBuild::Type(SingleTypeChildToBuild::from_build_or_rule(
&name.to_case(Case::Pascal),
None,
false,
)),
}
}
pub fn new(name: &str, rule: &str, build: SingleChildToBuild) -> Self {
impl MemberChild {
pub fn new(name: &str, rule: &str, build: Box<MemberChildToBuild>) -> Self {
Self {
name: name.to_string(),
rule: rule.to_string(),
@ -258,36 +275,32 @@ impl SingleChild {
}
/// The specification for what to actually build.
pub fn build(&self) -> &SingleChildToBuild {
pub fn build(&self) -> &MemberChildToBuild {
&self.build
}
}
#[derive(Debug)]
pub enum SingleChildToBuild {
Type(SingleTypeChildToBuild),
Boolean(SingleBooleanChildToBuild),
pub enum MemberChildToBuild {
Node(NodeChildToBuild),
Boolean(BooleanChildToBuild),
}
#[derive(Debug)]
pub struct SingleTypeChildToBuild {
pub struct NodeChildToBuild {
build: String,
var_name: String,
with: String,
or_else: Option<String>,
optional: bool,
}
impl SingleTypeChildToBuild {
pub fn from_build_or_rule(
build_or_rule: &str,
impl NodeChildToBuild {
pub fn new(
build: &str,
or_else: Option<String>,
optional: bool,
) -> Self {
Self {
build: build_or_rule.to_string(),
var_name: build_or_rule.to_case(Case::Snake),
with: make_build_fn_name(build_or_rule),
build: build.to_string(),
or_else,
optional,
}
@ -298,16 +311,6 @@ impl SingleTypeChildToBuild {
&self.build
}
/// The variable name to build, in snake case.
pub fn var_name(&self) -> &str {
&self.var_name
}
/// The build-fn name.
pub fn with(&self) -> &str {
&self.with
}
/// The default fn to call when unwrapping the child (before passing as arg to new).
pub fn or_else(&self) -> Option<&str> {
self.or_else.as_deref()
@ -320,29 +323,103 @@ impl SingleTypeChildToBuild {
}
#[derive(Debug)]
pub struct SingleBooleanChildToBuild {
var_name: String,
on: BuildBooleanOn,
pub struct BooleanChildToBuild {
name: String
}
impl SingleBooleanChildToBuild {
pub fn new(var_name: &str, on: BuildBooleanOn) -> Self {
impl BooleanChildToBuild {
pub fn new(name: &str) -> Self {
Self {
var_name: var_name.to_string(),
on,
name: name.to_string()
}
}
pub fn var_name(&self) -> &str {
&self.var_name
}
pub fn on(&self) -> &BuildBooleanOn {
&self.on
pub fn name(&self) -> &str {
&self.name
}
}
#[derive(Debug)]
pub enum BuildBooleanOn {
RulePresent,
// Leaf Struct build spec
pub struct LeafStructBuildSpec {
build: String,
members: Vec<Box<LeafStructMember>>,
}
impl LeafStructBuildSpec {
pub fn new(build: &str, members: Vec<Box<LeafStructMember>>) -> Self {
Self {
build: build.to_string(),
members,
}
}
pub fn build(&self) -> &str {
&self.build
}
pub fn members(&self) -> impl Iterator<Item = &LeafStructMember> {
self.members.iter().map(Box::as_ref)
}
}
pub struct LeafStructMember {
name: String,
kind: LeafStructMemberKind,
}
impl LeafStructMember {
pub fn new(name: &str, kind: LeafStructMemberKind) -> Self {
Self {
name: name.to_string(),
kind,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn kind(&self) -> &LeafStructMemberKind {
&self.kind
}
}
pub enum LeafStructMemberKind {
String,
}
pub struct ProductionBuildSpec {
rule: String,
kind: ProductionKind,
}
impl ProductionBuildSpec {
pub fn new(rule: &str, kind: ProductionKind) -> Self {
Self {
rule: rule.to_string(),
kind,
}
}
pub fn rule(&self) -> &str {
&self.rule
}
pub fn kind(&self) -> &ProductionKind {
&self.kind
}
}
pub enum ProductionKind {
Int,
Long,
Double,
String(ProductionStringFrom),
Boolean
}
pub enum ProductionStringFrom {
StringInner,
WholePair
}

View File

@ -0,0 +1,216 @@
use crate::spec::{BooleanChildToBuild, MemberChildToBuild, NodeChildToBuild, StructBuildSpec, StructChildSpec, VecChild, VecChildToBuild};
use crate::util::{make_build_fn_name, make_build_pair};
use convert_case::{Case, Casing};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
fn make_var_name(s: &str) -> String {
s.to_case(Case::Snake)
}
fn make_vec_child_holder(vec_child: &VecChild) -> TokenStream {
let child_ident = format_ident!("{}", vec_child.name());
match vec_child.build() {
VecChildToBuild::Node(node_child) => {
let child_type_ident = format_ident!("{}", node_child.build());
quote! {
let mut #child_ident: Vec<Box<#child_type_ident >> = vec![]
}
},
VecChildToBuild::String => quote! {
let mut #child_ident: Vec<String> = vec![]
}
}
}
fn make_node_child_holder(name: &str, node_child: &NodeChildToBuild) -> TokenStream {
let child_ident = format_ident!("{}", name);
let child_type_ident = format_ident!("{}", node_child.build());
quote! {
let mut #child_ident: Option<Box<#child_type_ident>> = None
}
}
fn make_boolean_child_holder(boolean_child: &BooleanChildToBuild) -> TokenStream {
let child_ident = format_ident!("{}", boolean_child.name());
quote! {
let mut #child_ident: bool = false
}
}
fn make_child_holder(child_spec: &StructChildSpec) -> Option<TokenStream> {
match child_spec {
StructChildSpec::SkipChild(_) => None,
StructChildSpec::VecChild(vec_child) => Some(make_vec_child_holder(vec_child)),
StructChildSpec::MemberChild(member_child) => match member_child.build() {
MemberChildToBuild::Node(node_child) => {
Some(make_node_child_holder(member_child.name(), node_child))
}
MemberChildToBuild::Boolean(boolean_child) => {
Some(make_boolean_child_holder(boolean_child))
}
},
}
}
fn make_vec_child_match_action(vec_child: &VecChild) -> TokenStream {
let child_name_ident = format_ident!("{}", vec_child.name());
match vec_child.build() {
VecChildToBuild::Node(vec_node_child) => {
let build_fn_ident = format_ident!("{}", make_build_fn_name(vec_node_child.build()));
quote! {
#child_name_ident.push(Box::new(#build_fn_ident(inner_pair)))
}
},
VecChildToBuild::String => {
let build_fn_ident = format_ident!("{}", make_build_fn_name(vec_child.rule()));
quote! {
#child_name_ident.push(#build_fn_ident(inner_pair))
}
}
}
}
fn make_node_member_child_match_action(name: &str, node_child: &NodeChildToBuild) -> TokenStream {
let child_name_ident = format_ident!("{}", name);
let build_fn_ident = format_ident!("{}", make_build_fn_name(node_child.build()));
quote! {
#child_name_ident = Some(Box::new(#build_fn_ident(inner_pair)))
}
}
fn make_boolean_member_child_match_action(boolean_child: &BooleanChildToBuild) -> TokenStream {
let child_name_ident = format_ident!("{}", make_var_name(boolean_child.name()));
quote! {
#child_name_ident = true
}
}
fn make_match_action(child_spec: &StructChildSpec) -> TokenStream {
match child_spec {
StructChildSpec::SkipChild(_) => quote! {},
StructChildSpec::VecChild(vec_child) => {
make_vec_child_match_action(vec_child)
}
StructChildSpec::MemberChild(member_child) => match member_child.build() {
MemberChildToBuild::Node(node_child) => {
make_node_member_child_match_action(member_child.name(), node_child)
}
MemberChildToBuild::Boolean(boolean_child) => {
make_boolean_member_child_match_action(boolean_child)
}
},
}
}
fn make_rule_matcher(child_spec: &StructChildSpec) -> TokenStream {
let rule_ident = match child_spec {
StructChildSpec::SkipChild(skip_child) => format_ident!("{}", skip_child.rule()),
StructChildSpec::VecChild(vec_child) => format_ident!("{}", vec_child.rule()),
StructChildSpec::MemberChild(single_child) => format_ident!("{}", single_child.rule()),
};
let action = make_match_action(child_spec);
quote! {
Rule::#rule_ident => {
#action;
}
}
}
fn make_vec_child_arg(vec_child: &VecChild) -> TokenStream {
let child_ident = format_ident!("{}", vec_child.name());
quote! { #child_ident }
}
fn make_node_member_child_arg(name: &str, node_child: &NodeChildToBuild) -> TokenStream {
let child_ident = format_ident!("{}", name);
if node_child.optional() {
quote! { #child_ident }
} else if let Some(or_else) = node_child.or_else() {
let child_type_ident = format_ident!("{}", node_child.build());
let or_else_ident = format_ident!("{}", or_else);
quote! {
#child_ident.unwrap_or_else(|| Box::new(#child_type_ident::#or_else_ident()))
}
} else {
quote! { #child_ident.unwrap() }
}
}
fn make_boolean_member_child_arg(boolean_child: &BooleanChildToBuild) -> TokenStream {
let child_ident = format_ident!("{}", boolean_child.name());
quote! { #child_ident }
}
fn make_child_arg(child_spec: &StructChildSpec) -> Option<TokenStream> {
match child_spec {
StructChildSpec::SkipChild(_) => None,
StructChildSpec::VecChild(vec_child) => {
Some(make_vec_child_arg(vec_child))
}
StructChildSpec::MemberChild(member_child) => match member_child.build() {
MemberChildToBuild::Node(single_type_child) => {
Some(make_node_member_child_arg(member_child.name(), single_type_child))
}
MemberChildToBuild::Boolean(boolean_child) => {
Some(make_boolean_member_child_arg(boolean_child))
}
},
}
}
fn make_return_value_stream(build_spec: &StructBuildSpec) -> TokenStream {
let type_ident = format_ident!("{}", build_spec.build());
let child_args = build_spec
.children()
.map(|child| make_child_arg(child))
.filter(|child_arg| child_arg.is_some())
.map(|child_arg| child_arg.unwrap())
.collect::<Vec<_>>();
quote! {
#type_ident::new(
#(#child_args,)*
)
}
}
pub fn make_struct_build_fn(build_spec: &StructBuildSpec) -> TokenStream {
let build_fn_ident = format_ident!("{}", make_build_fn_name(build_spec.build()));
let pair_ident = format_ident!("{}", make_build_pair(build_spec.build()));
let return_type_ident = format_ident!("{}", build_spec.build());
let child_holders = build_spec
.children()
.map(|child_spec| make_child_holder(child_spec))
.filter(|child_holder| child_holder.is_some())
.map(|child_holder| child_holder.unwrap())
.collect::<Vec<_>>();
let rule_matchers = build_spec
.children()
.map(|child_spec| make_rule_matcher(child_spec))
.collect::<Vec<_>>();
let iter_stream = quote! {
for inner_pair in #pair_ident.into_inner() {
match inner_pair.as_rule() {
#(#rule_matchers)*
_ => panic!("Unexpected rule: {:?}", inner_pair.as_rule())
}
}
};
let new_stream = make_return_value_stream(build_spec);
quote! {
fn #build_fn_ident(#pair_ident: Pair<Rule>) -> #return_type_ident {
#(#child_holders;)*
#iter_stream
#new_stream
}
}
}

View File

@ -1,6 +1,7 @@
use crate::spec::{
BuildSpec, ChildSpec, EnumBuildSpec, SingleBooleanChildToBuild, SingleChildToBuild,
SingleTypeChildToBuild, StructBuildSpec, VecChild, VecChildToBuild,
BooleanChildToBuild, BuildSpec, EnumBuildSpec, EnumRuleChildKind, LeafEnumBuildSpec,
LeafStructBuildSpec, LeafStructMemberKind, MemberChildToBuild, NodeChildToBuild,
StructBuildSpec, StructChildSpec, VecChild, VecChildToBuild,
};
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
@ -8,12 +9,27 @@ use quote::{format_ident, quote};
fn make_enum_type(build_spec: &EnumBuildSpec) -> TokenStream {
let children: Vec<TokenStream> = build_spec
.rules()
.iter()
.map(|rule| {
let member_name_ident = format_ident!("{}", rule.rule());
let child_name_ident = format_ident!("{}", rule.build());
.map(|enum_rule| {
let member_name_ident = format_ident!("{}", enum_rule.rule());
if let Some(enum_rule_child) = enum_rule.child() {
let child_type_ident = match enum_rule_child.kind() {
EnumRuleChildKind::Node(node_child) => {
format_ident!("{}", node_child.build())
}
EnumRuleChildKind::Int => format_ident!("i32"),
EnumRuleChildKind::Long => format_ident!("i64"),
EnumRuleChildKind::Double => format_ident!("f64"),
EnumRuleChildKind::USize => format_ident!("usize"),
EnumRuleChildKind::String => format_ident!("String"),
EnumRuleChildKind::Boolean => format_ident!("bool"),
};
quote! {
#member_name_ident(#child_name_ident)
#member_name_ident(#child_type_ident)
}
} else {
quote! {
#member_name_ident
}
}
})
.collect();
@ -25,24 +41,64 @@ fn make_enum_type(build_spec: &EnumBuildSpec) -> TokenStream {
}
}
fn make_leaf_enum_type(build_spec: &LeafEnumBuildSpec) -> TokenStream {
let type_name_ident = format_ident!("{}", build_spec.build());
let children = build_spec
.rules()
.map(|leaf_enum_rule| {
let rule_name_ident = format_ident!("{}", leaf_enum_rule.rule());
quote! {
#rule_name_ident
}
})
.collect::<Vec<_>>();
quote! {
pub enum #type_name_ident {
#(#children),*
}
}
}
fn handle_vec_child(
vec_child: &VecChild,
member_names: &mut Vec<Ident>,
annotated_members: &mut Vec<TokenStream>,
member_args: &mut Vec<TokenStream>,
accessors: &mut Vec<TokenStream>,
) {
let (child_ident, child_ident_mut, child_type_ident) = match vec_child.build() {
VecChildToBuild::Type(vec_type_child) => (
format_ident!("{}", vec_type_child.var_name()),
format_ident!("{}_mut", vec_type_child.var_name()),
format_ident!("{}", vec_type_child.build()),
),
let (child_ident, child_ident_mut) = (
format_ident!("{}", vec_child.name()),
format_ident!("{}_mut", vec_child.name()),
);
let child_type_ident = match vec_child.build() {
VecChildToBuild::Node(vec_node_child) => format_ident!("{}", vec_node_child.build()),
VecChildToBuild::String => format_ident!("{}", "String"),
};
member_names.push(child_ident.clone());
match vec_child.build() {
VecChildToBuild::Node(_) => {
annotated_members.push(quote! {
#child_ident: Vec<Box<#child_type_ident>>
});
member_args.push(quote! {
#child_ident: Vec<Box<#child_type_ident>>
});
}
VecChildToBuild::String => {
annotated_members.push(quote! {
#child_ident: Vec<String>
});
member_args.push(quote! {
#child_ident: Vec<String>
})
}
}
match vec_child.build() {
VecChildToBuild::Node(_) => {
accessors.push(quote! {
pub fn #child_ident(&self) -> impl Iterator<Item = &#child_type_ident> {
self.#child_ident.iter().map(Box::as_ref)
@ -53,26 +109,62 @@ fn handle_vec_child(
}
});
}
VecChildToBuild::String => accessors.push(quote! {
pub fn #child_ident(&self) -> impl Iterator<Item = &str> {
self.#child_ident.iter().map(String::as_str)
}
}),
}
}
fn handle_single_type_child(
single_type_child: &SingleTypeChildToBuild,
fn handle_node_child(
name: &str,
node_child: &NodeChildToBuild,
member_names: &mut Vec<Ident>,
annotated_members: &mut Vec<TokenStream>,
member_args: &mut Vec<TokenStream>,
accessors: &mut Vec<TokenStream>,
) {
let child_ident = format_ident!("{}", single_type_child.var_name());
let child_ident_mut = format_ident!("{}_mut", single_type_child.var_name());
let child_type_ident = format_ident!("{}", single_type_child.build());
let child_ident = format_ident!("{}", name);
let child_ident_mut = format_ident!("{}_mut", name);
let child_type_ident = format_ident!("{}", node_child.build());
member_names.push(child_ident.clone());
if single_type_child.optional() {
if node_child.optional() {
annotated_members.push(quote! {
#child_ident: Option<Box<#child_type_ident>>
});
member_args.push(quote! {
#child_ident: Option<Box<#child_type_ident>>
});
} else {
annotated_members.push(quote! {
#child_ident: Box<#child_type_ident>
});
member_args.push(quote! {
#child_ident: Box<#child_type_ident>
})
}
if node_child.optional() {
accessors.push(quote! {
pub fn #child_ident(&self) -> Option<&#child_type_ident> {
if let Some(#child_ident) = &self.#child_ident {
Some(#child_ident.as_ref())
} else {
None
}
}
pub fn #child_ident_mut(&mut self) -> Option<&mut #child_type_ident> {
if let Some(#child_ident) = &mut self.#child_ident {
Some(#child_ident.as_mut())
} else {
None
}
}
});
} else {
accessors.push(quote! {
pub fn #child_ident(&self) -> &#child_type_ident {
self.#child_ident.as_ref()
@ -83,56 +175,66 @@ fn handle_single_type_child(
}
});
}
}
fn handle_single_boolean_child(
single_boolean_child: &SingleBooleanChildToBuild,
fn handle_boolean_child(
single_boolean_child: &BooleanChildToBuild,
member_names: &mut Vec<Ident>,
annotated_members: &mut Vec<TokenStream>,
member_args: &mut Vec<TokenStream>,
accessors: &mut Vec<TokenStream>,
) {
let child_ident = format_ident!("{}", single_boolean_child.var_name());
let child_ident = format_ident!("{}", single_boolean_child.name());
member_names.push(child_ident.clone());
annotated_members.push(quote! {
#child_ident: bool
});
member_args.push(quote! {
#child_ident: bool
});
accessors.push(quote! {
pub fn #child_ident(&self) -> bool {
self.#child_ident
}
})
});
}
fn make_struct_type(build_spec: &StructBuildSpec) -> TokenStream {
let mut member_names: Vec<Ident> = vec![];
let mut annotated_members: Vec<TokenStream> = vec![];
let mut member_args: Vec<TokenStream> = vec![];
let mut accessors: Vec<TokenStream> = vec![];
for child_spec in build_spec.children().iter() {
for child_spec in build_spec.children() {
match child_spec {
ChildSpec::SkipChild(_) => {}
ChildSpec::VecChild(vec_child) => {
StructChildSpec::SkipChild(_) => {}
StructChildSpec::VecChild(vec_child) => {
handle_vec_child(
vec_child,
&mut member_names,
&mut annotated_members,
&mut member_args,
&mut accessors,
);
}
ChildSpec::SingleChild(single_child) => {
match single_child.build() {
SingleChildToBuild::Type(single_type_child) => {
handle_single_type_child(
single_type_child,
StructChildSpec::MemberChild(member_child) => {
match member_child.build() {
MemberChildToBuild::Node(node_child) => {
handle_node_child(
member_child.name(),
node_child,
&mut member_names,
&mut annotated_members,
&mut member_args,
&mut accessors,
);
}
SingleChildToBuild::Boolean(single_boolean_child) => {
handle_single_boolean_child(
single_boolean_child,
MemberChildToBuild::Boolean(boolean_child) => {
handle_boolean_child(
boolean_child,
&mut member_names,
&mut annotated_members,
&mut member_args,
&mut accessors,
);
}
@ -160,9 +262,91 @@ fn make_struct_type(build_spec: &StructBuildSpec) -> TokenStream {
}
}
pub fn make_type(build_spec: &BuildSpec) -> TokenStream {
fn make_leaf_struct_type(build_spec: &LeafStructBuildSpec) -> TokenStream {
let type_ident = format_ident!("{}", build_spec.build());
let annotated_members = build_spec
.members()
.map(|member| {
let name_ident = format_ident!("{}", member.name());
let type_ident = match member.kind() {
LeafStructMemberKind::String => format_ident!("{}", "String"),
};
quote! {
#name_ident: #type_ident
}
})
.collect::<Vec<_>>();
let member_args = build_spec.members().map(|member| {
let name_ident = format_ident!("{}", member.name());
let type_stream = match member.kind() {
LeafStructMemberKind::String => {
quote! { &str }
}
};
quote! {
#name_ident: #type_stream
}
});
let initializers = build_spec
.members()
.map(|leaf_struct_member| {
let member_ident = format_ident!("{}", leaf_struct_member.name());
match leaf_struct_member.kind() {
LeafStructMemberKind::String => {
quote! {
#member_ident: #member_ident.to_string()
}
}
}
})
.collect::<Vec<_>>();
let accessors = build_spec
.members()
.map(|member| {
let name_ident = format_ident!("{}", member.name());
match member.kind() {
LeafStructMemberKind::String => {
quote! {
pub fn #name_ident(&self) -> &str {
&self.#name_ident
}
}
}
}
})
.collect::<Vec<_>>();
quote! {
pub struct #type_ident {
#(#annotated_members),*
}
impl #type_ident {
pub fn new(#(#member_args),*) -> Self {
Self {
#(#initializers),*
}
}
#(#accessors)*
}
}
}
pub fn make_type(build_spec: &BuildSpec) -> Option<TokenStream> {
match build_spec {
BuildSpec::Enum(enum_build_spec) => make_enum_type(enum_build_spec),
BuildSpec::Struct(struct_build_spec) => make_struct_type(struct_build_spec),
BuildSpec::Enum(enum_build_spec) => Some(make_enum_type(enum_build_spec)),
BuildSpec::LeafEnum(leaf_enum_build_spec) => {
Some(make_leaf_enum_type(leaf_enum_build_spec))
}
BuildSpec::Struct(struct_build_spec) => Some(make_struct_type(struct_build_spec)),
BuildSpec::LeafStruct(leaf_struct_build_spec) => {
Some(make_leaf_struct_type(leaf_struct_build_spec))
}
BuildSpec::Production(_) => None,
}
}

View File

@ -0,0 +1,9 @@
use convert_case::{Case, Casing};
pub fn make_build_fn_name(s: &str) -> String {
format!("build_{}", s.to_case(Case::Snake))
}
pub fn make_build_pair(s: &str) -> String {
format!("{}_pair", s.to_case(Case::Snake))
}

View File

@ -1,9 +1,40 @@
fn main() -> std::io::Result<()> {
println!("cargo:rerun-if-changed=src/parser/deimos.pest");
// let out_dir = env::var("OUT_DIR").unwrap();
// let out_dir_path = Path::new(&out_dir);
// let testing_txt_path = out_dir_path.join("testing.rs");
// let output = test_dump();
// write(&testing_txt_path, output)?;
use ast_generator::{deserialize, generate_files};
use cst_test_generator::generate_test_files;
use std::env;
use std::fs;
use std::io;
use std::path::Path;
fn generate_parser_tests(out_dir: &Path) -> io::Result<()> {
let parser_tests_dir = out_dir.join("src").join("parser").join("tests");
fs::create_dir_all(&parser_tests_dir)?;
let test_suites_file = generate_test_files(Path::new("src/parser/tests"))?;
let file_path = parser_tests_dir.join(&test_suites_file.file_name);
fs::write(file_path, &test_suites_file.contents)?;
Ok(())
}
fn generate_ast_files(out_dir: &Path) -> io::Result<()> {
let gen_ast_dir = out_dir.join("src").join("ast");
fs::create_dir_all(&gen_ast_dir)?;
let ast_yaml = include_str!("src/parser/ast.yaml");
let build_specs = deserialize::deserialize_yaml_spec(ast_yaml);
let generated_files = generate_files(&build_specs);
for generated_file in &generated_files {
let path = gen_ast_dir.join(&generated_file.name);
fs::write(path, &generated_file.contents)?;
}
Ok(())
}
fn main() -> io::Result<()> {
println!("cargo:rerun-if-changed=src/parser/tests");
println!("cargo:rerun-if-changed=src/parser/ast.yaml");
println!("cargo:rerun-if-changed=src/parser/deimos.pest");
let out_dir = env::var_os("OUT_DIR").unwrap();
let out_dir_path = Path::new(&out_dir);
generate_parser_tests(out_dir_path)?;
generate_ast_files(out_dir_path)?;
Ok(())
}

View File

@ -0,0 +1,11 @@
[package]
name = "cst-test-generator"
version = "0.1.0"
edition = "2024"
[dependencies]
convert_case = "0.8.0"
prettyplease = "0.2.37"
proc-macro2 = "1.0.101"
quote = "1.0.40"
syn = "2.0.106"

View File

@ -0,0 +1,72 @@
use convert_case::{Case, Casing};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use std::path::Path;
use std::{fs, io};
use syn::File;
pub struct ParserTestSuitesFile {
pub file_name: String,
pub contents: String,
}
pub fn generate_test_files(tests_dir: &Path) -> io::Result<ParserTestSuitesFile> {
let mut test_suites: Vec<TokenStream> = vec![];
// generate test file for each sub dir
for sub_dir in fs::read_dir(tests_dir)? {
let sub_dir = sub_dir?;
let sub_dir_path = sub_dir.path();
if sub_dir_path.is_dir() {
let sub_dir_file_name = sub_dir.file_name();
let sub_dir_string = sub_dir_file_name.to_string_lossy();
let sub_dir_pascal = sub_dir_string.to_case(Case::Pascal);
let rule_ident = format_ident!("{}", sub_dir_pascal);
let mut tests: Vec<TokenStream> = vec![];
for test_file in fs::read_dir(sub_dir_path)? {
let test_file = test_file?;
let test_file_name = test_file.file_name();
let test_ident = format_ident!("{}", test_file_name.to_string_lossy());
let src_input = fs::read_to_string(test_file.path())?;
let test = quote! {
#[test]
fn #test_ident() {
parses_to(Rule::#rule_ident, #src_input)
}
};
tests.push(test);
}
let tests_mod_ident = format_ident!("{}_tests", sub_dir.file_name().to_string_lossy());
let test_suite = quote! {
mod #tests_mod_ident {
use super::*;
#(#tests)*
}
};
test_suites.push(test_suite);
} else {
println!("Warning: not a directory: {:?}", sub_dir_path);
}
}
// generate main test_suites file
let test_suites = quote! {
use super::*;
#(#test_suites)*
};
let test_suites_file: File = syn::parse2(test_suites).unwrap();
let test_suites_file_contents = prettyplease::unparse(&test_suites_file);
Ok(ParserTestSuitesFile {
file_name: String::from("test_suites.rs"),
contents: test_suites_file_contents,
})
}

View File

@ -0,0 +1 @@
Hello, World!

View File

@ -0,0 +1 @@
`Hello, ${world}!`

View File

@ -0,0 +1 @@
false

View File

@ -0,0 +1 @@
true

View File

@ -1,6 +1,106 @@
pub mod build;
pub mod children;
pub mod node;
pub mod pretty_print;
pub mod unparse;
pub mod walk;
pub mod node {
include!(concat!(env!("OUT_DIR"), "/src/ast/node.rs"));
impl Default for Parameters {
fn default() -> Self {
Self::new(vec![])
}
}
impl Default for GenericParameters {
fn default() -> Self {
Self::new(Box::new(IdentifierList::new(vec![])))
}
}
impl Default for GenericArguments {
fn default() -> Self {
Self::new(Box::new(TypeUseList::new(vec![])))
}
}
impl Default for ImplementsList {
fn default() -> Self {
Self::new(vec![])
}
}
impl ReturnType {
pub fn void() -> Self {
Self::new(Box::new(TypeUse::PrimitiveType(PrimitiveType::Void)))
}
}
impl Default for ClassConstructor {
fn default() -> Self {
Self::new(vec![])
}
}
impl Default for ClosureParameters {
fn default() -> Self {
Self::new(vec![])
}
}
}
pub mod build {
//noinspection RsUnusedImport
use crate::parser::Rule;
//noinspection RsUnusedImport
use pest::iterators::Pair;
//noinspection RsUnusedImport
use crate::ast::node::*;
include!(concat!(env!("OUT_DIR"), "/src/ast/build.rs"));
#[cfg(test)]
mod build_tests {
use super::*;
use crate::parser::DeimosParser;
use pest::Parser;
fn parse(rule: Rule, input: &str) -> Pair<Rule> {
let parse_result = DeimosParser::parse(rule, input);
parse_result.expect("parsing failed").next().unwrap()
}
#[test]
fn boolean_literal_true() {
let pair = parse(
Rule::BooleanLiteral,
include_str!("build_tests/boolean_literal/true"),
);
assert_eq!(true, build_boolean_literal(pair));
}
#[test]
fn boolean_literal_false() {
let pair = parse(
Rule::BooleanLiteral,
include_str!("build_tests/boolean_literal/false"),
);
assert_eq!(false, build_boolean_literal(pair));
}
#[test]
fn backtick_inner_greeting() {
let pair = parse(
Rule::BacktickInner,
include_str!("build_tests/backtick_inner/greeting"),
);
assert_eq!("Hello, World!", build_backtick_inner(pair));
}
#[test]
fn backtick_string_mixed() {
let pair = parse(
Rule::BacktickString,
include_str!("build_tests/backtick_string/mixed"),
);
let backtick_string = build_backtick_string(pair);
assert_eq!(backtick_string.inners().count(), 2);
assert_eq!(backtick_string.expressions().count(), 1);
}
}
}

View File

@ -101,45 +101,45 @@ where
f(node.as_node_ref());
}
#[cfg(test)]
mod tests {
use crate::ast::build::build_ast;
use crate::ast::children::NodeRef;
use crate::ast::walk::walk_depth_first;
use crate::parser::{DeimosParser, Rule};
use indoc::indoc;
use pest::Parser;
#[test]
fn collect_identifiers() {
let parse_result = DeimosParser::parse(
Rule::CompilationUnit,
indoc! {"
ns greeter;
class Greeter {}
fn main() {
let greeter = Greeter();
}
"},
);
match parse_result {
Ok(cu_pairs) => {
let cu = build_ast("greeter.dm", 0, cu_pairs.into_iter().next().unwrap());
let mut identifier_count = 0;
walk_depth_first(&cu, &mut |node_ref| match node_ref {
NodeRef::Identifier(identifier) => {
dbg!(identifier);
identifier_count += 1;
}
_ => {}
});
assert_eq!(identifier_count, 5);
}
Err(err) => {
panic!("{}", err);
}
}
}
}
// #[cfg(test)]
// mod tests {
// use crate::ast::build::build_ast;
// use crate::ast::children::NodeRef;
// use crate::ast::walk::walk_depth_first;
// use crate::parser::{DeimosParser, Rule};
// use indoc::indoc;
// use pest::Parser;
//
// #[test]
// fn collect_identifiers() {
// let parse_result = DeimosParser::parse(
// Rule::CompilationUnit,
// indoc! {"
// ns greeter;
//
// class Greeter {}
//
// fn main() {
// let greeter = Greeter();
// }
// "},
// );
// match parse_result {
// Ok(cu_pairs) => {
// let cu = build_ast("greeter.dm", 0, cu_pairs.into_iter().next().unwrap());
// let mut identifier_count = 0;
// walk_depth_first(&cu, &mut |node_ref| match node_ref {
// NodeRef::Identifier(identifier) => {
// dbg!(identifier);
// identifier_count += 1;
// }
// _ => {}
// });
// assert_eq!(identifier_count, 5);
// }
// Err(err) => {
// panic!("{}", err);
// }
// }
// }
// }

View File

@ -1,54 +1,58 @@
mod name_analysis;
mod p3;
mod unparse;
use std::path::PathBuf;
use crate::name_analysis::name_analysis;
use crate::p3::pretty_print_parse;
use crate::unparse::unparse;
use clap::{Parser, Subcommand};
#[derive(Debug, Parser)]
#[command(name = "dmc")]
#[command(about = "Deimos Compiler", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Debug, Subcommand)]
enum Commands {
#[command(arg_required_else_help = true)]
Unparse {
paths: Vec<PathBuf>,
},
P3 {
paths: Vec<PathBuf>,
},
NameAnalysis {
paths: Vec<PathBuf>,
},
}
// mod name_analysis;
// mod p3;
// mod unparse;
//
// use std::path::PathBuf;
//
// use crate::name_analysis::name_analysis;
// use crate::p3::pretty_print_parse;
// use crate::unparse::unparse;
// use clap::{Parser, Subcommand};
//
// #[derive(Debug, Parser)]
// #[command(name = "dmc")]
// #[command(about = "Deimos Compiler", long_about = None)]
// struct Cli {
// #[command(subcommand)]
// command: Commands,
// }
//
// #[derive(Debug, Subcommand)]
// enum Commands {
// #[command(arg_required_else_help = true)]
// Unparse {
// paths: Vec<PathBuf>,
// },
// P3 {
// paths: Vec<PathBuf>,
// },
// NameAnalysis {
// paths: Vec<PathBuf>,
// },
// }
//
// fn main() {
// let args = Cli::parse();
// match args.command {
// Commands::Unparse { paths } => {
// for path in paths {
// unparse(&path);
// }
// }
// Commands::P3 { paths } => {
// for path in paths {
// pretty_print_parse(&path)
// }
// }
// Commands::NameAnalysis { paths } => {
// let result = name_analysis(&paths);
// if let Err(e) = result {
// eprintln!("{}", e)
// }
// }
// }
// }
fn main() {
let args = Cli::parse();
match args.command {
Commands::Unparse { paths } => {
for path in paths {
unparse(&path);
}
}
Commands::P3 { paths } => {
for path in paths {
pretty_print_parse(&path)
}
}
Commands::NameAnalysis { paths } => {
let result = name_analysis(&paths);
if let Err(e) = result {
eprintln!("{}", e)
}
}
}
panic!("TODO")
}

View File

@ -5,7 +5,7 @@ extern crate core;
pub mod ast;
pub mod diagnostic;
pub mod module;
pub mod name_analysis;
// pub mod name_analysis;
pub mod object_file;
pub mod parser;
pub mod std_core;

View File

@ -4,14 +4,12 @@ pub struct DmModule {
pub enum NamespaceVisibility {
Public,
Partial {
visible_to_fqns: Vec<String>
},
Private
Partial { visible_to_fqns: Vec<String> },
Private,
}
pub struct DmNamespace {
pub name: String,
pub dependencies: Vec<String>,
pub visibility: NamespaceVisibility
pub visibility: NamespaceVisibility,
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,268 +0,0 @@
/*!
# Name Analysis
There are two phases in name analysis.
## 1. Gather
The gather phase has three responsibilities:
1. Add all declared symbols to the symbol table.
2. Set the `scope_id` property of all identifiers and fully-qualified-names.
3. For the main identifiers of `UseStatement`s (i.e., the last identifier in the `UseStatement`):
set a 'linking' symbol on the identifier, which will later be filled in with the linked-to
symbol (probably in another file, or from the standard library).
## 2. Resolve
The resolve phase has one main responsibility: resolve all references based on the identifier's
`scope_id` property.
*/
use crate::ast::node::compilation_unit::CompilationUnit;
use crate::ast::node::named::Named;
use crate::diagnostic::DmDiagnostic;
use crate::name_analysis::gather::gather_compilation_unit;
use crate::name_analysis::resolve::resolve_compilation_unit;
use crate::name_analysis::symbol_table::SymbolTable;
mod fqn_context;
mod gather;
mod resolve;
pub mod symbol;
pub mod symbol_table;
pub fn analyze_names(
compilation_units: &mut Vec<CompilationUnit>,
symbol_table: &mut SymbolTable,
) -> Vec<DmDiagnostic> {
let mut diagnostics = vec![];
// gather symbols
for compilation_unit in compilation_units.iter_mut() {
gather_compilation_unit(compilation_unit, symbol_table, &mut diagnostics);
}
// resolve symbols
for compilation_unit in compilation_units.iter_mut() {
resolve_compilation_unit(compilation_unit, symbol_table, &mut diagnostics);
}
diagnostics.into()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::build::build_ast;
use crate::ast::children::NodeRef;
use crate::ast::walk::walk_depth_first;
use crate::parser::{DeimosParser, Rule};
use crate::std_core::add_std_core_symbols;
use codespan_reporting::files::SimpleFiles;
use codespan_reporting::term;
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
use indoc::indoc;
use pest::Parser;
use std::collections::HashMap;
fn assert_number_of_diagnostics(
sources: HashMap<&str, &str>,
symbol_table: &mut SymbolTable,
n_diagnostics: usize,
) -> Vec<CompilationUnit> {
let mut files = SimpleFiles::new();
let mut compilation_units = vec![];
for (file_name, source) in sources {
let file_id = files.add(file_name, source);
let parse_result = DeimosParser::parse(Rule::CompilationUnit, source);
if let Err(err) = &parse_result {
panic!("{}", err);
}
let mut pairs = parse_result.unwrap();
if pairs.as_str().trim() != source.trim() {
panic!("Parsing did not consume entire input.");
}
compilation_units.push(build_ast(file_name, file_id, pairs.next().unwrap()));
}
let diagnostics = analyze_names(&mut compilation_units, symbol_table);
if diagnostics.len() != n_diagnostics {
let writer = StandardStream::stderr(ColorChoice::Always);
let config = term::Config::default();
for diagnostic in &diagnostics {
term::emit(&mut writer.lock(), &config, &files, &diagnostic).unwrap();
}
eprintln!("{}", symbol_table);
}
assert_eq!(n_diagnostics, diagnostics.len());
compilation_units
}
fn assert_no_diagnostics(
sources: HashMap<&str, &str>,
symbol_table: &mut SymbolTable,
) -> Vec<CompilationUnit> {
assert_number_of_diagnostics(sources, symbol_table, 0)
}
fn assert_saved_symbols(compilation_unit: &CompilationUnit) {
walk_depth_first(compilation_unit, &mut |node_ref| match node_ref {
NodeRef::Identifier(identifier) => {
if identifier.saved_symbol().is_none() {
panic!("{:?} does not have a saved symbol.", identifier)
}
}
NodeRef::FullyQualifiedName(fqn) => {
if fqn.saved_symbol().is_none() {
panic!("{:?} does not have a saved symbol.", fqn)
}
}
NodeRef::UseStatement(use_statement) => match use_statement {
_ => todo!(),
},
_ => {}
})
}
fn assert_resolved_symbols(compilation_unit: &CompilationUnit) {
walk_depth_first(compilation_unit, &mut |node_ref| match node_ref {
NodeRef::UseStatement(use_statement) => match use_statement {
_ => todo!(),
},
_ => {}
})
}
#[test]
fn params_seen() {
let sources: HashMap<&str, &str> = HashMap::from([(
"main.dm",
indoc! {"
fn main(args: Array<String>) {
let x = args;
}"},
)]);
let cus = assert_no_diagnostics(sources, &mut SymbolTable::new());
for ref cu in cus {
assert_saved_symbols(cu);
}
}
#[test]
fn two_files() {
let sources: HashMap<&str, &str> = HashMap::from([
(
"main.dm",
indoc! {"
use test::Greeter;
"},
),
(
"deps.dm",
indoc! {"
ns test;
pub class Greeter {}
"},
),
]);
let cus = assert_no_diagnostics(sources, &mut SymbolTable::new());
for ref cu in cus {
assert_saved_symbols(cu);
assert_resolved_symbols(cu);
}
}
#[test]
fn sees_std_core_println() {
let sources: HashMap<&str, &str> = HashMap::from([(
"main.dm",
indoc! {"
fn main(args: Array<String>) {
println(args)
}
"},
)]);
let mut symbol_table = SymbolTable::new();
add_std_core_symbols(&mut symbol_table).expect("Failed to add std::core symbols.");
let cus = assert_no_diagnostics(sources, &mut symbol_table);
for ref cu in cus {
assert_saved_symbols(cu);
assert_resolved_symbols(cu);
}
}
#[test]
fn sees_duplicate_fn() {
let sources: HashMap<&str, &str> = HashMap::from([(
"main.dm",
indoc! {"
fn main(args: Array<String>) {}
fn main(args: Array<String>) {}
"},
)]);
assert_number_of_diagnostics(sources, &mut SymbolTable::new(), 1);
}
#[test]
fn use_class_from_other_file() {
let sources: HashMap<&str, &str> = HashMap::from([
(
"main.dm",
indoc! {"
use greeter::Greeter;
fn test(greeter: Greeter) {}
"},
),
(
"greeter.dm",
indoc! {"
ns greeter;
class Greeter {}
"},
),
]);
let mut symbol_table = SymbolTable::new();
let cus = assert_no_diagnostics(sources, &mut symbol_table);
for ref cu in cus {
assert_saved_symbols(cu);
assert_resolved_symbols(cu);
}
}
#[test]
fn shadow_import() {
let sources: HashMap<&str, &str> = HashMap::from([
(
"main.dm",
indoc! {"
use greeter::Greeter;
class Greeter {}
"},
),
(
"greeter.dm",
indoc! {"
ns greeter;
class Greeter {}
"},
),
]);
assert_number_of_diagnostics(sources, &mut SymbolTable::new(), 1);
}
}

View File

@ -0,0 +1,267 @@
/*!
# Name Analysis
There are two phases in name analysis.
## 1. Gather
The gather phase has three responsibilities:
1. Add all declared symbols to the symbol table.
2. Set the `scope_id` property of all identifiers and fully-qualified-names.
3. For the main identifiers of `UseStatement`s (i.e., the last identifier in the `UseStatement`):
set a 'linking' symbol on the identifier, which will later be filled in with the linked-to
symbol (probably in another file, or from the standard library).
## 2. Resolve
The resolve phase has one main responsibility: resolve all references based on the identifier's
`scope_id` property.
*/
use crate::ast::node::CompilationUnit;
use crate::diagnostic::DmDiagnostic;
use crate::name_analysis::gather::gather_compilation_unit;
use crate::name_analysis::resolve::resolve_compilation_unit;
use crate::name_analysis::symbol_table::SymbolTable;
mod fqn_context;
mod gather;
mod resolve;
pub mod symbol;
pub mod symbol_table;
pub fn analyze_names(
compilation_units: &mut Vec<CompilationUnit>,
symbol_table: &mut SymbolTable,
) -> Vec<DmDiagnostic> {
let mut diagnostics = vec![];
// gather symbols
for compilation_unit in compilation_units.iter_mut() {
gather_compilation_unit(compilation_unit, symbol_table, &mut diagnostics);
}
// resolve symbols
for compilation_unit in compilation_units.iter_mut() {
resolve_compilation_unit(compilation_unit, symbol_table, &mut diagnostics);
}
diagnostics.into()
}
// #[cfg(test)]
// mod tests {
// use super::*;
// use crate::ast::build::build_ast;
// use crate::ast::children::NodeRef;
// use crate::ast::walk::walk_depth_first;
// use crate::parser::{DeimosParser, Rule};
// use crate::std_core::add_std_core_symbols;
// use codespan_reporting::files::SimpleFiles;
// use codespan_reporting::term;
// use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
// use indoc::indoc;
// use pest::Parser;
// use std::collections::HashMap;
//
// fn assert_number_of_diagnostics(
// sources: HashMap<&str, &str>,
// symbol_table: &mut SymbolTable,
// n_diagnostics: usize,
// ) -> Vec<CompilationUnit> {
// let mut files = SimpleFiles::new();
// let mut compilation_units = vec![];
//
// for (file_name, source) in sources {
// let file_id = files.add(file_name, source);
// let parse_result = DeimosParser::parse(Rule::CompilationUnit, source);
// if let Err(err) = &parse_result {
// panic!("{}", err);
// }
// let mut pairs = parse_result.unwrap();
// if pairs.as_str().trim() != source.trim() {
// panic!("Parsing did not consume entire input.");
// }
//
// compilation_units.push(build_ast(file_name, file_id, pairs.next().unwrap()));
// }
//
// let diagnostics = analyze_names(&mut compilation_units, symbol_table);
//
// if diagnostics.len() != n_diagnostics {
// let writer = StandardStream::stderr(ColorChoice::Always);
// let config = term::Config::default();
//
// for diagnostic in &diagnostics {
// term::emit(&mut writer.lock(), &config, &files, &diagnostic).unwrap();
// }
//
// eprintln!("{}", symbol_table);
// }
//
// assert_eq!(n_diagnostics, diagnostics.len());
//
// compilation_units
// }
//
// fn assert_no_diagnostics(
// sources: HashMap<&str, &str>,
// symbol_table: &mut SymbolTable,
// ) -> Vec<CompilationUnit> {
// assert_number_of_diagnostics(sources, symbol_table, 0)
// }
//
// fn assert_saved_symbols(compilation_unit: &CompilationUnit) {
// walk_depth_first(compilation_unit, &mut |node_ref| match node_ref {
// NodeRef::Identifier(identifier) => {
// if identifier.saved_symbol().is_none() {
// panic!("{:?} does not have a saved symbol.", identifier)
// }
// }
// NodeRef::FullyQualifiedName(fqn) => {
// if fqn.saved_symbol().is_none() {
// panic!("{:?} does not have a saved symbol.", fqn)
// }
// }
// NodeRef::UseStatement(use_statement) => match use_statement {
// _ => todo!(),
// },
// _ => {}
// })
// }
//
// fn assert_resolved_symbols(compilation_unit: &CompilationUnit) {
// walk_depth_first(compilation_unit, &mut |node_ref| match node_ref {
// NodeRef::UseStatement(use_statement) => match use_statement {
// _ => todo!(),
// },
// _ => {}
// })
// }
//
// #[test]
// fn params_seen() {
// let sources: HashMap<&str, &str> = HashMap::from([(
// "main.dm",
// indoc! {"
// fn main(args: Array<String>) {
// let x = args;
// }"},
// )]);
//
// let cus = assert_no_diagnostics(sources, &mut SymbolTable::new());
// for ref cu in cus {
// assert_saved_symbols(cu);
// }
// }
//
// #[test]
// fn two_files() {
// let sources: HashMap<&str, &str> = HashMap::from([
// (
// "main.dm",
// indoc! {"
// use test::Greeter;
// "},
// ),
// (
// "deps.dm",
// indoc! {"
// ns test;
//
// pub class Greeter {}
// "},
// ),
// ]);
//
// let cus = assert_no_diagnostics(sources, &mut SymbolTable::new());
// for ref cu in cus {
// assert_saved_symbols(cu);
// assert_resolved_symbols(cu);
// }
// }
//
// #[test]
// fn sees_std_core_println() {
// let sources: HashMap<&str, &str> = HashMap::from([(
// "main.dm",
// indoc! {"
// fn main(args: Array<String>) {
// println(args)
// }
// "},
// )]);
//
// let mut symbol_table = SymbolTable::new();
// add_std_core_symbols(&mut symbol_table).expect("Failed to add std::core symbols.");
// let cus = assert_no_diagnostics(sources, &mut symbol_table);
// for ref cu in cus {
// assert_saved_symbols(cu);
// assert_resolved_symbols(cu);
// }
// }
//
// #[test]
// fn sees_duplicate_fn() {
// let sources: HashMap<&str, &str> = HashMap::from([(
// "main.dm",
// indoc! {"
// fn main(args: Array<String>) {}
// fn main(args: Array<String>) {}
// "},
// )]);
// assert_number_of_diagnostics(sources, &mut SymbolTable::new(), 1);
// }
//
// #[test]
// fn use_class_from_other_file() {
// let sources: HashMap<&str, &str> = HashMap::from([
// (
// "main.dm",
// indoc! {"
// use greeter::Greeter;
//
// fn test(greeter: Greeter) {}
// "},
// ),
// (
// "greeter.dm",
// indoc! {"
// ns greeter;
//
// class Greeter {}
// "},
// ),
// ]);
// let mut symbol_table = SymbolTable::new();
// let cus = assert_no_diagnostics(sources, &mut symbol_table);
// for ref cu in cus {
// assert_saved_symbols(cu);
// assert_resolved_symbols(cu);
// }
// }
//
// #[test]
// fn shadow_import() {
// let sources: HashMap<&str, &str> = HashMap::from([
// (
// "main.dm",
// indoc! {"
// use greeter::Greeter;
//
// class Greeter {}
// "},
// ),
// (
// "greeter.dm",
// indoc! {"
// ns greeter;
//
// class Greeter {}
// "},
// ),
// ]);
// assert_number_of_diagnostics(sources, &mut SymbolTable::new(), 1);
// }
// }

View File

@ -1,29 +1,10 @@
use crate::ast::node::call_expression::*;
use crate::ast::node::class::*;
use crate::ast::node::closure::*;
use crate::ast::node::compilation_unit::*;
use crate::ast::node::d_string::*;
use crate::ast::node::expression::*;
use crate::ast::node::function::*;
use crate::ast::node::generics::*;
use crate::ast::node::implements_list::*;
use crate::ast::node::interface::*;
use crate::ast::node::level::*;
use crate::ast::node::literal::*;
use crate::ast::node::module::*;
use crate::ast::node::names::*;
use crate::ast::node::object_access::*;
use crate::ast::node::statement::*;
use crate::ast::node::tuple_arguments::*;
use crate::ast::node::type_use::*;
use crate::ast::node::use_statement::*;
use crate::ast::node::named::Named;
use crate::ast::node::*;
use crate::diagnostic::DmDiagnostic;
use crate::name_analysis::symbol::Symbol;
use crate::name_analysis::symbol_table::{SymbolLookupError, SymbolTable};
use codespan_reporting::diagnostic::{Diagnostic, Label};
use std::ops::DerefMut;
use std::range::Range;
use crate::ast::node::named::Named;
/* Type Use */
fn resolve_type_use(

View File

@ -1,6 +1,3 @@
use crate::ast::node::named::Named;
use crate::ast::node::names::Identifier;
use crate::ast::node::use_statement::UseStatement;
use std::cell::RefCell;
use std::fmt::{Debug, Display, Formatter};
use std::ops::Deref;

View File

@ -5,34 +5,65 @@ description: Top level is a map of node names in Pascal case (e.g., CompilationU
additionalProperties:
$ref: "#/$defs/NodeDefinition"
$defs:
# Top level
NodeDefinition:
type: object
additionalProperties: false
description: A definition of a node type.
description: |
A definition of a node type. The main key in this hash determines the type of node: `children` for Struct node,
`members` for Leaf-Struct node, `rules` for Enum node, `leaf_rules` for Leaf-Enum node, and `produce` for a
translate rule.
A Struct node has child nodes and other properties, and is built by looping through all the inner pairs of the
given Parser Pair.
A Leaf-Struct node does not have child nodes, but does have members. The members are built by some kind of parsing
of the string value of the the given Parser Pair (i.e., no looping through inner pairs).
An Enum node maps Parser Rules to an enum of node types. Each enum member may have a child, or not. By default, a
Rule name maps to the node type-name of a single child.
A Leaf-Enum node is like a regular enum node, but no children are allowed. Rather, the Parser Rule maps to a bare
enum member.
A translate rule simply translates the Parser rule into some kind of arbitrary type, such as a string, int, etc.
oneOf:
- $ref: "#/$defs/StructNodeDefinition"
- $ref: "#/$defs/LeafStructNodeDefinition"
- $ref: "#/$defs/EnumNodeDefinition"
- $ref: "#/$defs/LeafEnumNodeDefinition"
- $ref: "#/$defs/ProductionDefinition"
# Four main types of nodes
StructNodeDefinition:
type: object
additionalProperties: false
description: A description of a Struct node to be built.
properties:
type:
const: struct
children:
type: array
description: Ordered child fields for this node.
items:
$ref: "#/$defs/StructChildDefinition"
required:
- children
LeafStructNodeDefinition:
type: object
additionalProperties: false
description: A description of a Leaf-Struct node to be built.
properties:
members:
type: array
description: Ordered members for this node.
items:
$ref: "#/$defs/LeafStructMemberDefinition"
required:
- members
EnumNodeDefinition:
type: object
additionalProperties: false
description: A description of an Enum node to be built.
properties:
type:
const: enum
rules:
type: array
description: Alternative parse rules that build this node.
@ -45,39 +76,40 @@ $defs:
additionalProperties: false
description: A description of a leaf-enum node to be built.
properties:
type:
const: leaf_enum
rules:
leaf_rules:
type: array
description: Alternative parse rules that build this node.
items:
$ref: "#/$defs/LeafEnumChildDefinition"
type: string
required:
- type
- rules
- leaf_rules
# Struct node children
StructChildDefinition:
description: A definition of a node's child. Either a bare child name (string) in snake case, or an object.
description: |
A definition of a Struct node's child. Either a bare child name (string) in snake case, or an object. The former
is a shorthand where the child name and built type are the same; casing is automatically done. The latter allows
further customization of the built child.
oneOf:
- type: string
description: Shorthand where child name, var, build, and with are inferred from the given snake-case child name.
- $ref: "#/$defs/ChildDefinitionWrapper"
ChildDefinitionWrapper:
- $ref: "#/$defs/StructChildDefinitionWrapper"
StructChildDefinitionWrapper:
type: object
description: Single-key object mapping the child-name to its spec.
description: Single-key object mapping the child-name to its advanced definition.
minProperties: 1
maxProperties: 1
additionalProperties: false
patternProperties:
"^[a-z][a-z0-9_]*$":
$ref: "#/$defs/ChildDefinition"
ChildDefinition:
$ref: "#/$defs/StructChildAdvancedDefinition"
StructChildAdvancedDefinition:
type: object
description: One of skip/vec/single child specs.
oneOf:
- $ref: "#/$defs/SkipChildDefinition"
- $ref: "#/$defs/VecChildDefinition"
- $ref: "#/$defs/SingleChildDefinition"
SkipChildDefinition:
- $ref: "#/$defs/StructChildSkipChildDefinition"
- $ref: "#/$defs/StructChildVecChildDefinition"
- $ref: "#/$defs/StructChildMemberDefinition"
StructChildSkipChildDefinition:
type: object
additionalProperties: false
description: A definition for a child rule that does nothing, i.e., is skipped.
@ -85,49 +117,121 @@ $defs:
rule:
type: string
skip:
type: boolean # note: must be true
type: boolean
const: true
required:
- rule
- skip
VecChildDefinition:
StructChildVecChildDefinition:
type: object
additionalProperties: false
description: A definition for a child rule that can be matched multiple times.
properties:
rule:
type: string
kind:
type: string
enum:
- node # default
- string
vec:
type: boolean
const: true
required:
- rule
- vec
SingleChildDefinition:
StructChildMemberDefinition:
type: object
additionalProperties: false
description: A definition for a child rule that builds one item.
properties:
rule:
type: string
description: The type to build, in Pascal case.
description: The rule to match.
optional:
type: boolean
description: If true, this child will be stored as an Option.
build:
oneOf:
- type: string
- $ref: "#/$defs/SingleChildBuildDefinition"
SingleChildBuildDefinition:
- $ref: "#/$defs/StructChildMemberBuildDefinition"
StructChildMemberBuildDefinition:
type: object
additionalProperties: false
description: A definition of what exactly to build for a given child rule.
oneOf:
- $ref: "#/$defs/BuildSingleTypeChild"
- $ref: "#/$defs/BuildBooleanChild"
- $ref: "#/$defs/BuildStringChild"
- $ref: "#/$defs/BuildDoubleChild"
- $ref: "#/$defs/BuildIntChild"
- $ref: "#/$defs/BuildLongChild"
BuildSingleTypeChild:
- $ref: "#/$defs/BuildNode"
- $ref: "#/$defs/BuildBoolean"
# Leaf Struct children
LeafStructMemberDefinition:
type: object
description: Single-key object mapping the member-name to what is to be built by parsing the Parser Pair.
minProperties: 1
maxProperties: 1
additionalProperties: false
patternProperties:
"^[a-z][a-z0-9_]*$":
oneOf:
- $ref: "#/$defs/BuildMember"
BuildMember:
type: object
description: A specification for a member to build.
additionalProperties: false
properties:
kind:
enum:
- string
from:
enum:
- whole_pair
required:
- kind
- from
# Enum children
EnumChildDefinition:
oneOf:
- type: string
description: Shorthand where child name, var, build, and with are inferred from the given Pascal-case rule name.
- $ref: "#/$defs/LongEnumChildWrapper"
LongEnumChildWrapper:
type: object
minProperties: 1
maxProperties: 1
additionalProperties: false
patternProperties:
"^([A-Z][a-z]*)*$":
type: string
enum:
- int
- long
- double
- usize
- string
- boolean
# Production definition
ProductionDefinition:
type: object
properties:
produce:
type: object
properties:
kind:
enum:
- int
- long
- double
- string
- boolean
from:
enum:
- string_inner
- whole_pair
- parse_whole_pair
# Common things to build
BuildNode:
type: object
additionalProperties: false
description: A definition of a single-type child to build.
@ -138,107 +242,15 @@ $defs:
or_else_default:
type: boolean
description: Whether to call the default method on the built-type if the rule is not found.
BuildBooleanChild:
BuildBoolean:
type: object
additionalProperties: false
description: A definition for building a boolean child.
description: A boolean member to be built.
properties:
type:
kind:
type: string
enum:
- boolean
const: boolean
on:
type: string
enum:
- rule_present
from:
type: string
enum:
- parse_whole_pair
BuildStringChild:
type: object
additionalProperties: false
description: A definition for building a string child.
properties:
type:
const: string
from:
type: string
enum:
- whole_pair
BuildDoubleChild:
type: object
additionalProperties: false
description: A definition for building a Double child.
properties:
type:
const: f64
from:
type: string
enum:
- parse_whole_pair
BuildIntChild:
type: object
additionalProperties: false
description: A definition for building an Int child.
properties:
type:
const: i32
from:
type: string
enum:
- parse_number_base
BuildLongChild:
type: object
additionalProperties: false
description: A definition for building a Long child.
properties:
type:
const: i64
from:
type: string
enum:
- parse_number_base
EnumChildDefinition:
description: A definition of an enum node's child. Either a bare rule (string) in Pascal case, or an object.
oneOf:
- type: string
description: Shorthand where child name, var, build, and with are inferred from the given Pascal-case rule name.
- $ref: "#/$defs/LongEnumChildDefinition"
LongEnumChildDefinition:
type: object
additionalProperties: false
description: A format for specifying more specific information for an enum child.
properties:
rule:
type: string
build:
type: string
required:
- rule
- build
LeafEnumChildDefinition:
description: A definition of a leaf-enum node's child. Either a bare rule-string in Pascal case, or an object.
oneOf:
- type: string
description: Shorthand where the rule name maps onto an empty enum rule.
- $ref: "#/$defs/LongLeafEnumChildDefinitionWrapper"
LongLeafEnumChildDefinitionWrapper:
type: object
description: Single-key object mapping the child-name to its spec.
minProperties: 1
maxProperties: 1
additionalProperties: false
patternProperties:
"^([A-Z][a-z0-9]*)*$":
$ref: "#/$defs/LongLeafEnumChildDefinition"
LongLeafEnumChildDefinition:
type: object
additionalProperties: false
description: A format for specifying more specific information about a leaf-enum child.
properties:
child:
type: boolean
description: If true, a node of the same name is built as the lone member of the enum child.
required:
- child

View File

@ -1,28 +1,159 @@
# $schema: ./ast.schema.yaml
# Operators
Operator:
leaf_rules:
- Or
- And
- EqualTo
- NotEqualTo
- Greater
- Less
- GreaterEqual
- LessEqual
- Add
- Subtract
- Multiply
- Divide
- Modulo
- LeftShift
- RightShift
- Spread
- Star
- Not
- Negative
- PlusPlus
- MinusMinus
- CallOp
- Index
# Names
Identifier:
children:
- literal:
build:
type: string
from: parse_whole_pair
members:
- name:
kind: string
from: whole_pair
FullyQualifiedName:
children:
- identifiers:
rule: Identifier
vec: true
# Lists
TypeUseList:
children:
- type_uses:
rule: TypeUse
vec: true
IdentifierList:
children:
- identifiers:
rule: Identifier
vec: true
ParenthesesOptionalTypeUseList:
children:
- type_use_list:
optional: true
# Type Use
TypeUse:
rules:
- PrimitiveType
- InterfaceOrClassTypeUse
- TupleTypeUse
- FunctionTypeUse
PrimitiveType:
leaf_rules:
- Byte
- Short
- Char
- Int
- Long
- Double
- Bool
- String
- TypedArray
- Any
- Void
TypedArray:
children:
- array_kw:
rule: Array
skip: true
- generic_arguments
InterfaceOrClassTypeUse:
children:
- fully_qualified_name
- generic_arguments:
build:
or_else_default: true
TupleTypeUse:
children:
- tuple_arguments
FunctionTypeUse:
children:
- fn_kw:
rule: Fn
skip: true
- generic_parameters:
build:
or_else_default: true
- parameters:
build:
or_else_default: true
- return_type
# Generic Arguments
GenericArguments:
children:
- type_use_list
# Generic Parameters
GenericParameters:
children:
- identifier_list
# Tuple Arguments
TupleArguments:
children:
- parentheses_optional_type_use_list
# Implements List
ImplementsList:
children:
- type_uses:
rule: TypeUse
vec: true
# Parameters
Parameters:
children:
- parameters:
rule: Parameter
vec: true
Parameter:
children:
- identifier
- type_use
# Return Type
ReturnType:
children:
- type_use
# Top-level constructs
CompilationUnit:
children:
- parent_mod
- parent_mod:
optional: true
- use_statements:
rule: UseStatement
vec: true
- module_level_declarations:
rule: ModuleLevelDeclaration
vec: true
- eoi:
rule: EOI
skip: true
ParentMod:
children:
- mod_kw:
@ -46,8 +177,8 @@ UseStatementPrefix:
UseStatementSuffix:
rules:
- Identifier
- rule: Star
build: UseStatementStarSuffix
- Star:
child: false
- UseList
UseList:
children:
@ -87,7 +218,7 @@ Module:
- is_public:
rule: Pub
build:
type: boolean
kind: boolean
on: rule_present
- mod_kw:
rule: Mod
@ -119,7 +250,7 @@ Interface:
- is_public:
rule: Pub
build:
type: boolean
kind: boolean
on: rule_present
- int_kw:
rule: IntKw
@ -142,7 +273,7 @@ Class:
- is_public:
rule: Pub
build:
type: boolean
kind: boolean
on: rule_present
- class_kw:
rule: ClassKw
@ -167,7 +298,7 @@ Function:
- is_public:
rule: Pub
build:
type: boolean
kind: boolean
on: rule_present
- fn_kw:
rule: Fn
@ -185,7 +316,7 @@ OperatorFunction:
- is_public:
rule: Pub
build:
type: boolean
kind: boolean
on: rule_present
- op_kw:
rule: Op
@ -205,7 +336,7 @@ PlatformFunction:
- is_public:
rule: Pub
build:
type: boolean
kind: boolean
on: rule_present
- platform_kw:
rule: Platform
@ -316,12 +447,12 @@ Member:
- is_public:
rule: Pub
build:
type: boolean
kind: boolean
on: rule_present
- is_mut:
rule: Mut
build:
type: boolean
kind: boolean
on: rule_present
- identifier
- type_use
@ -333,8 +464,7 @@ Statement:
- AssignmentStatement
- ExpressionStatement
- UseStatement
- IfElseStatement
- IfStatementStatement
- IfStatement
- WhileStatement
- ForStatement
VariableDeclaration:
@ -345,7 +475,7 @@ VariableDeclaration:
- is_mut:
rule: Mut
build:
type: boolean
kind: boolean
on: rule_present
- identifier
- type_use:
@ -430,7 +560,7 @@ ForStatement:
vec: true
- end_kw:
rule: End
vec: true
skip: true
# Expressions
Expression:
@ -482,7 +612,7 @@ ComparisonExpression:
rule: Expression
optional: true
ComparisonOperator:
rules:
leaf_rules:
- Greater
- Less
- GreaterEqual
@ -498,8 +628,9 @@ ShiftExpression:
optional: true
- right:
rule: Expression
optional: true
ShiftOperator:
rules:
leaf_rules:
- LeftShift
- RightShift
AdditiveExpression:
@ -512,6 +643,10 @@ AdditiveExpression:
- right:
rule: Expression
optional: true
AdditiveOperator:
leaf_rules:
- Add
- Subtract
MultiplicativeExpression:
children:
- left:
@ -521,9 +656,9 @@ MultiplicativeExpression:
optional: true
- right:
rule: Expression
optional: true
MultiplicativeOperator:
type: leaf_enum
rules:
leaf_rules:
- Multiply
- Divide
- Modulo
@ -532,9 +667,10 @@ PrefixExpression:
- operators:
rule: PrefixOperator
vec: true
- right:
rule: SuffixExpression
PrefixOperator:
type: leaf_enum
rules:
leaf_rules:
- Spread
- Not
- Negative
@ -546,16 +682,14 @@ SuffixExpression:
rule: SuffixOperator
vec: true
SuffixOperator:
type: leaf_enum
rules:
- PlusPlus
- MinusMinus
- ObjectProperty:
child: true
- ObjectIndex:
child: true
- Call:
child: true
- PlusPlus:
child: false
- MinusMinus:
child: false
- ObjectProperty
- ObjectIndex
- Call
ObjectProperty:
children:
- identifier
@ -625,91 +759,47 @@ ClosureParameter:
# Literals
Literal:
rules:
- NumberLiteral
- StringLiteral
- BooleanLiteral
NumberLiteral:
rules:
- DoubleLiteral
- LongLiteral
- IntLiteral
IntLiteral:
children:
- number_base
- literal:
build:
type: i32
from: parse_number_base
LongLiteral:
children:
- number_base
- literal:
build:
type: i64
from: parse_number_base
DoubleLiteral:
children:
- literal:
build:
type: f64
from: parse_whole_pair
NumberBase:
rules:
- BinaryBase
- HexadecimalBase
- DecimalBase
DecimalBase:
children:
- literal:
build:
type: string
from: whole_pair
BinaryBase:
children:
- binary_digits
BinaryDigits:
children:
- literal:
build:
type: string
from: whole_pair
HexadecimalBase:
children:
- hexadecimal_digits
HexadecimalDigits:
children:
- literal:
build:
type: string
from: whole_pair
StringLiteral:
rules:
- SingleQuoteString
- DoubleQuoteString
- IntLiteral:
kind: int
- LongLiteral:
kind: long
- DoubleLiteral:
kind: double
- SingleQuoteString:
kind: string
- DString
- BacktickString
- BooleanLiteral:
kind: boolean
# Numbers
IntLiteral:
produce:
kind: int
LongLiteral:
produce:
kind: long
DoubleLiteral:
produce:
kind: double
# Strings
SingleQuoteString:
children:
- string_inner:
optional: true
DoubleQuoteString:
produce:
kind: string
from: string_inner
DString:
children:
- inners:
rule: DStringInner
kind: string
vec: true
- expressions:
rule: DStringExpression
vec: true
StringInner:
children:
- literal:
build:
type: string
from: whole_pair
DStringInner:
children:
- literal:
build:
type: string
produce:
kind: string
from: whole_pair
DStringExpression:
children:
@ -718,19 +808,16 @@ BacktickString:
children:
- inners:
rule: BacktickInner
kind: string
vec: true
- expressions:
rule: DStringExpression
vec: true
BacktickInner:
children:
- literal:
build:
type: string
produce:
kind: string
from: whole_pair
BooleanLiteral:
children:
- literal:
build:
type: boolean
produce:
kind: boolean
from: parse_whole_pair

View File

@ -148,8 +148,6 @@ Operator = {
| RightShift
// unary prefix
| Spread
| BorrowMut
| Borrow
| Star
| Not
| Negative
@ -197,18 +195,14 @@ IdentifierList = {
~ ( "," ~ Identifier )*
}
ParenthesesTypeUseList = {
"("
~ TypeUseList
~ ")"
}
ParenthesesOptionalTypeUseList = {
"("
~ TypeUseList?
~ ")"
}
// Type Use
// In general:
// Arguments = usage
// Parameters = declaration
@ -229,28 +223,27 @@ PrimitiveType = {
| Double
| Bool
| String
| Array ~ GenericArguments?
| TypedArray
| Any
| Void
}
TypedArray = {
Array
~ GenericArguments?
}
InterfaceOrClassTypeUse = {
Borrow*
~ Mut?
~ FullyQualifiedName
FullyQualifiedName
~ GenericArguments?
}
TupleTypeUse = {
Borrow*
~ Mut?
~ TupleArguments
TupleArguments
}
FunctionTypeUse = {
Borrow*
~ FunctionTypeModifier?
~ Fn
Fn
~ GenericParameters?
~ Parameters
~ ReturnType
@ -286,15 +279,6 @@ ImplementsList = {
~ ( "+" ~ TypeUse )*
}
// Function type modifier
FunctionTypeModifier = {
Cons
| Mut ~ Ref
| Mut
| Ref
}
// Parameters
Parameters = {
@ -317,13 +301,6 @@ Parameter = {
ReturnType = {
"->"
~ TypeUse
~ RefList?
}
RefList = {
Ref
~ Identifier
~ ( "," ~ Identifier )
}
// Top-level constructs
@ -808,15 +785,13 @@ ClosureParameter = {
// Literals
Literal = {
NumberLiteral
| StringLiteral
| BooleanLiteral
}
NumberLiteral = {
DoubleLiteral
| LongLiteral
| IntLiteral
| SingleQuoteString
| DString
| BacktickString
| BooleanLiteral
}
IntLiteral = { NumberBase }
@ -845,15 +820,9 @@ HexadecimalDigits = @{ HexadecimalDigit+ }
HexadecimalDigit = { '0'..'9' | 'a'..'f' }
StringLiteral = {
SingleQuoteString
| DoubleQuoteString
| BacktickString
}
SingleQuoteString = { "'" ~ StringInner? ~ "'" }
DoubleQuoteString = {
DString = {
"\""
~ ( DStringInner? ~ DStringExpression )*
~ DStringInner?

View File

@ -7,7 +7,6 @@ pub struct DeimosParser;
#[cfg(test)]
mod deimos_parser_tests {
use crate::parser::{DeimosParser, Rule};
use indoc::indoc;
use pest::Parser;
macro_rules! fail_rule {
@ -45,160 +44,7 @@ mod deimos_parser_tests {
}
}
#[test]
fn hex_int() {
parses_to(Rule::IntLiteral, "0x1234abcd");
}
#[test]
fn hex_long() {
parses_to(Rule::LongLiteral, "0x123456789abcdefL");
}
#[test]
fn suffix_expression_call_single_identifier() {
parses_to(Rule::SuffixExpression, "foo()");
}
#[test]
fn simple_interface() {
parses_to(Rule::CompilationUnit, "pub int Simple { fn foo() -> Void }");
}
#[test]
fn interface_with_op() {
parses_to(
Rule::CompilationUnit,
"pub int Callable { op () () -> Void }",
);
}
#[test]
fn interface_with_alias() {
parses_to(
Rule::CompilationUnit,
indoc! {"
pub int Callable {
fn call() -> Void
def op () () -> Void alias call
}
"},
);
}
#[test]
fn index_identifier() {
parses_to(Rule::SuffixExpression, "foo[0]");
}
#[test]
fn chained_index_call_on_identifier() {
parses_to(Rule::SuffixExpression, "foo[0]()");
}
#[test]
fn if_statement() {
parses_to(
Rule::IfStatement,
indoc! {"
if (foo == 42) {
bar()
}"},
)
}
#[test]
fn if_else_statement() {
parses_to(
Rule::IfElseStatement,
indoc! {"
if (foo == 42) {
bar()
} else {
baz()
}"},
)
}
#[test]
fn if_else_if_statement() {
parses_to(
Rule::IfElseStatement,
indoc! {"
if (foo == 42) {
bar()
} else if (foo == 16) {
baz()
}"},
)
}
#[test]
fn if_else_if_else_statement() {
parses_to(
Rule::IfElseStatement,
indoc! {"
if (foo == 42) {
foo()
} else if (foo == 16) {
baz()
} else {
fizz()
}"},
)
}
#[test]
fn while_statement() {
parses_to(Rule::WhileStatement, "while (foo) { bar() }");
}
#[test]
fn for_statement() {
parses_to(Rule::ForStatement, "for (foo in bar) { baz(foo); }");
}
#[test]
fn if_statement_with_call_condition() {
parses_to(
Rule::IfStatement,
indoc! {"
if (foo()) {
bar()
}
"},
)
}
#[test]
fn while_statement_with_call_condition() {
parses_to(Rule::WhileStatement, "while (foo()) { bar(); }")
}
#[test]
fn for_statement_with_call_iterator() {
parses_to(
Rule::ForStatement,
indoc! {"
for (foo in bar()) {
baz(foo);
}
"},
)
}
#[test]
fn use_statement() {
parses_to(Rule::UseStatement, "use std::core::println");
}
#[test]
fn use_star() {
parses_to(Rule::UseStatement, "use std::core::*")
}
#[test]
fn use_list() {
parses_to(Rule::UseStatement, "use std::core::{print, println}");
mod generated_tests {
include!(concat!(env!("OUT_DIR"), "/src/parser/tests/test_suites.rs"));
}
}

View File

@ -0,0 +1 @@
``

View File

@ -0,0 +1 @@
`${greeting}, ${world}!`

View File

@ -0,0 +1 @@
`Hello, World!`

View File

@ -0,0 +1 @@
0x1234abcd

View File

@ -0,0 +1,3 @@
pub int Simple
fn foo() -> Void
end

View File

@ -0,0 +1,4 @@
pub int Callable
fn call() -> Void
def op () () -> Void alias call
end

View File

@ -0,0 +1,3 @@
pub int Callable
op () () -> Void
end

View File

@ -0,0 +1 @@
0x123456789abcdefL

View File

@ -1,14 +1,14 @@
use crate::name_analysis::symbol::{FunctionSymbol, ParameterSymbol};
use crate::name_analysis::symbol_table::{SymbolInsertError, SymbolTable};
pub fn add_std_core_symbols(symbol_table: &mut SymbolTable) -> Result<(), SymbolInsertError> {
symbol_table.insert_function_symbol(
FunctionSymbol::new("std::core::println", "println", true, true, None)
.with_parameters(vec![ParameterSymbol::new("msg", None)]),
)?;
symbol_table.insert_function_symbol(
FunctionSymbol::new("std::core::print", "print", true, true, None)
.with_parameters(vec![ParameterSymbol::new("msg", None)]),
)?;
Ok(())
}
// use crate::name_analysis::symbol::{FunctionSymbol, ParameterSymbol};
// use crate::name_analysis::symbol_table::{SymbolInsertError, SymbolTable};
//
// pub fn add_std_core_symbols(symbol_table: &mut SymbolTable) -> Result<(), SymbolInsertError> {
// symbol_table.insert_function_symbol(
// FunctionSymbol::new("std::core::println", "println", true, true, None)
// .with_parameters(vec![ParameterSymbol::new("msg", None)]),
// )?;
// symbol_table.insert_function_symbol(
// FunctionSymbol::new("std::core::print", "print", true, true, None)
// .with_parameters(vec![ParameterSymbol::new("msg", None)]),
// )?;
// Ok(())
// }

View File

@ -5,7 +5,11 @@ pub struct IndentWriter {
}
impl IndentWriter {
pub fn new(start_level: usize, indent_string: &str, out: Box<dyn std::io::Write>) -> IndentWriter {
pub fn new(
start_level: usize,
indent_string: &str,
out: Box<dyn std::io::Write>,
) -> IndentWriter {
IndentWriter {
indent_level: start_level,
indent_string: indent_string.to_string(),

View File

@ -1,7 +1,7 @@
#[derive(Debug, Clone)]
pub enum DvmConstant {
String(String),
Array(DvmConstantArray)
Array(DvmConstantArray),
}
#[derive(Debug, Clone)]
@ -13,5 +13,5 @@ pub enum DvmConstantArray {
USizes(Vec<usize>),
Booleans(Vec<bool>),
Strings(Vec<String>),
Arrays(Vec<DvmConstantArray>)
Arrays(Vec<DvmConstantArray>),
}

View File

@ -111,8 +111,8 @@ pub enum Instruction {
},
Return,
DumpState {
message: String
}
message: String,
},
}
#[derive(Debug, Clone)]

View File

@ -10,7 +10,7 @@ impl SourceCodeLocation {
SourceCodeLocation {
source_file_name: source_file_name.to_string(),
line,
col
col,
}
}
}

View File

@ -11,5 +11,5 @@ pub enum DvmType {
// Other
Object,
Array,
ConstantPointer
ConstantPointer,
}