Compare commits

..

No commits in common. "2dd3bf5a069252219e7c7e4f6a06434b3b6fad6c" and "cde6d18e5c0e892923c2facb83be1832901c9e12" have entirely different histories.

62 changed files with 2854 additions and 3579 deletions

12
Cargo.lock generated
View File

@ -169,17 +169,6 @@ 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"
@ -187,7 +176,6 @@ dependencies = [
"ast-generator",
"clap",
"codespan-reporting",
"cst-test-generator",
"indoc",
"log",
"pest",

View File

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

View File

@ -0,0 +1,246 @@
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,285 +1,188 @@
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 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 convert_case::{Case, Casing};
use yaml_rust2::{Yaml, YamlLoader};
fn get_as_bool(yaml: &Yaml) -> bool {
yaml.as_bool().unwrap_or_else(|| false)
fn get_skip(skip: &Yaml) -> bool {
skip.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(vec: &Yaml) -> bool {
vec.as_bool().unwrap_or_else(|| false)
}
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)
},
_ => panic!("invalid kind: {}", production_yaml["kind"].as_str().unwrap()),
};
ProductionBuildSpec::new(rule, kind)
}
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));
fn deserialize_leaf_enum_rule(rule_yaml: &Yaml) -> Box<LeafEnumRule> {
Box::new(LeafEnumRule::new(rule_yaml.as_str().unwrap()))
}
fn deserialize_leaf_enum_rules(rules_yaml: &Yaml) -> Vec<Box<LeafEnumRule>> {
rules_yaml
.as_vec()
.unwrap()
.iter()
.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
VecChildToBuild::Type(VecTypeChildToBuild::new(build_type, &var_name, &with))
} 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)),
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 deserialize_enum_rules(rules_yaml: &Yaml) -> Vec<Box<EnumRule>> {
rules_yaml
.as_vec()
.unwrap()
.iter()
.map(|rule_yaml| deserialize_enum_rule(rule_yaml))
.collect()
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 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
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")
}
},
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,
))
}
})
.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_as_bool(&props["skip"]) {
deserialize_skip_child(&name, &rule)
} else {
if get_as_bool(&props["vec"]) {
deserialize_vec_child(&name, props)
} else {
let optional = get_as_bool(&props["optional"]);
deserialize_member_child(&name, &rule, optional, props)
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)),
}
}
}
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 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_struct_children(children: &Yaml) -> Vec<Box<StructChildSpec>> {
fn get_child_specs(children: &Yaml) -> Vec<ChildSpec> {
children
.as_vec()
.unwrap()
.iter()
.map(|child_spec| {
if child_spec.is_hash() {
deserialize_struct_hash_child(child_spec)
let as_hash = child_spec.as_hash().unwrap();
let (name, props) = as_hash
.iter()
.next()
.map(|(name, props)| (name.as_str().unwrap(), props))
.unwrap();
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)
} else {
let optional = props["optional"]
.as_bool()
.unwrap_or_else(|| false);
get_single_child(name, &rule, optional, build)
}
} else {
deserialize_struct_string_child(child_spec)
ChildSpec::SingleChild(SingleChild::from_name_snake(child_spec.as_str().unwrap()))
}
})
.collect()
}
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"]))
fn get_enum_rule_specs(rule_specs: &Yaml) -> Vec<EnumRule> {
rule_specs
.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()
} else {
make_build_fn_name(build)
};
EnumRule::new(rule, build, &with)
} else {
EnumRule::from_rule(rule_spec.as_str().unwrap())
}
})
.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,
))
} else {
panic!("Expected a node spec for either a struct, leaf_struct, enum, leaf_enum node type, or a production type.");
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");
}
}
}
@ -290,9 +193,6 @@ pub fn deserialize_yaml_spec(yaml: &str) -> Vec<BuildSpec> {
doc.as_hash()
.unwrap()
.iter()
.map(|(build_spec_name, build_spec)| {
let name_as_str = build_spec_name.as_str().unwrap();
deserialize_build_spec(name_as_str, build_spec)
})
.map(|(build_spec_name, build_spec)| deserialize_build_spec(build_spec_name, build_spec))
.collect()
}

View File

@ -1,48 +0,0 @@
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

@ -1,29 +0,0 @@
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

@ -1,38 +0,0 @@
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,44 +1,24 @@
pub mod deserialize;
mod enum_build_fn;
mod leaf_enum_build_fn;
mod leaf_struct_build_fn;
mod production_build_fn;
mod build_fn_gen;
mod deserialize;
mod spec;
mod struct_build_fn;
mod type_gen;
mod util;
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::build_fn_gen::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!("Enum Spec - build: {}", enum_build_spec.build());
}
BuildSpec::LeafEnum(leaf_enum_build_spec) => {
println!("Leaf Enum Spec - build: {}", leaf_enum_build_spec.build());
println!("Spec name: {}", enum_build_spec.name());
}
BuildSpec::Struct(struct_build_spec) => {
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!("Spec name: {}", struct_build_spec.name());
}
}
println!("{:#?}", token_stream);
@ -46,74 +26,30 @@ fn debug_built_spec(build_spec: &BuildSpec, token_stream: &TokenStream) {
println!("{}", prettyplease::unparse(&parsed));
}
pub struct AstGeneratedFile {
pub name: String,
pub contents: String,
}
pub fn test_dump() -> String {
let build_specs = deserialize::deserialize_yaml_spec(include_str!("../../src/parser/ast.yaml"));
let mut streams: Vec<TokenStream> = vec![];
fn token_stream_to_string(token_stream: TokenStream) -> String {
let file: File = syn::parse2(token_stream).unwrap();
for build_spec in &build_specs {
let type_stream = make_type(build_spec);
debug_built_spec(build_spec, &type_stream);
streams.push(type_stream);
}
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();
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

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

View File

@ -1,87 +0,0 @@
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,47 +1,62 @@
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<Box<EnumRule>>,
rules: Vec<EnumRule>,
}
impl EnumBuildSpec {
pub fn new(build: &str, rules: Vec<Box<EnumRule>>) -> Self {
pub fn from_name(name: &str, rules: Vec<EnumRule>) -> Self {
EnumBuildSpec {
build: build.to_string(),
name: name.to_string(),
build: name.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) -> impl Iterator<Item = &EnumRule> {
self.rules.iter().map(Box::as_ref)
pub fn rules(&self) -> &[EnumRule] {
&self.rules
}
}
pub struct EnumRule {
rule: String,
child: Option<Box<EnumRuleChild>>
build: String,
with: String,
}
impl EnumRule {
pub fn new(rule: &str, child: Option<Box<EnumRuleChild>>) -> Self {
pub fn from_rule(rule: &str) -> Self {
Self {
rule: rule.to_string(),
child
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(),
}
}
@ -49,126 +64,67 @@ impl EnumRule {
pub fn rule(&self) -> &str {
&self.rule
}
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
}
}
/// The type to build, in Pascal case.
pub fn build(&self) -> &str {
&self.build
}
pub fn rules(&self) -> impl Iterator<Item = &LeafEnumRule> {
self.rules.iter().map(Box::as_ref)
/// The build-fn name, in snake case.
pub fn with(&self) -> &str {
&self.with
}
}
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,
children: Vec<Box<StructChildSpec>>,
var_name: String,
with: String,
children: Vec<ChildSpec>,
}
impl StructBuildSpec {
pub fn new(build: &str, children: Vec<Box<StructChildSpec>>) -> Self {
pub fn from_name(name: &str, child_specs: Vec<ChildSpec>) -> Self {
Self {
build: build.to_string(),
children
name: name.to_string(),
build: name.to_string(),
var_name: name.to_case(Case::Snake),
with: make_build_fn_name(name),
children: child_specs,
}
}
/// 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) -> impl Iterator<Item = &StructChildSpec> {
self.children.iter().map(Box::as_ref)
pub fn children(&self) -> &[ChildSpec] {
&self.children
}
}
pub enum StructChildSpec {
pub enum ChildSpec {
SkipChild(SkipChild),
VecChild(VecChild),
MemberChild(MemberChild),
SingleChild(SingleChild),
}
pub struct SkipChild {
@ -198,11 +154,11 @@ impl SkipChild {
pub struct VecChild {
name: String,
rule: String,
build: Box<VecChildToBuild>,
build: VecChildToBuild,
}
impl VecChild {
pub fn new(name: &str, rule: &str, build: Box<VecChildToBuild>) -> Self {
pub fn new(name: &str, rule: &str, build: VecChildToBuild) -> Self {
Self {
name: name.to_string(),
rule: rule.to_string(),
@ -228,35 +184,62 @@ impl VecChild {
#[derive(Debug)]
pub enum VecChildToBuild {
Node(VecNodeChildToBuild),
String
Type(VecTypeChildToBuild),
}
#[derive(Debug)]
pub struct VecNodeChildToBuild {
pub struct VecTypeChildToBuild {
build: String,
var_name: String,
with: String,
}
impl VecNodeChildToBuild {
pub fn new(build: &str) -> Self {
Self { build: build.to_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(),
}
}
/// 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 MemberChild {
pub struct SingleChild {
name: String,
rule: String,
build: Box<MemberChildToBuild>,
build: SingleChildToBuild,
}
impl MemberChild {
pub fn new(name: &str, rule: &str, build: Box<MemberChildToBuild>) -> Self {
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 {
Self {
name: name.to_string(),
rule: rule.to_string(),
@ -275,32 +258,36 @@ impl MemberChild {
}
/// The specification for what to actually build.
pub fn build(&self) -> &MemberChildToBuild {
pub fn build(&self) -> &SingleChildToBuild {
&self.build
}
}
#[derive(Debug)]
pub enum MemberChildToBuild {
Node(NodeChildToBuild),
Boolean(BooleanChildToBuild),
pub enum SingleChildToBuild {
Type(SingleTypeChildToBuild),
Boolean(SingleBooleanChildToBuild),
}
#[derive(Debug)]
pub struct NodeChildToBuild {
pub struct SingleTypeChildToBuild {
build: String,
var_name: String,
with: String,
or_else: Option<String>,
optional: bool,
}
impl NodeChildToBuild {
pub fn new(
build: &str,
impl SingleTypeChildToBuild {
pub fn from_build_or_rule(
build_or_rule: &str,
or_else: Option<String>,
optional: bool,
) -> Self {
Self {
build: build.to_string(),
build: build_or_rule.to_string(),
var_name: build_or_rule.to_case(Case::Snake),
with: make_build_fn_name(build_or_rule),
or_else,
optional,
}
@ -311,6 +298,16 @@ impl NodeChildToBuild {
&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()
@ -323,103 +320,29 @@ impl NodeChildToBuild {
}
#[derive(Debug)]
pub struct BooleanChildToBuild {
name: String
pub struct SingleBooleanChildToBuild {
var_name: String,
on: BuildBooleanOn,
}
impl BooleanChildToBuild {
pub fn new(name: &str) -> Self {
impl SingleBooleanChildToBuild {
pub fn new(var_name: &str, on: BuildBooleanOn) -> Self {
Self {
name: name.to_string()
var_name: var_name.to_string(),
on,
}
}
pub fn name(&self) -> &str {
&self.name
pub fn var_name(&self) -> &str {
&self.var_name
}
pub fn on(&self) -> &BuildBooleanOn {
&self.on
}
}
// 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
#[derive(Debug)]
pub enum BuildBooleanOn {
RulePresent,
}

View File

@ -1,216 +0,0 @@
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,7 +1,6 @@
use crate::spec::{
BooleanChildToBuild, BuildSpec, EnumBuildSpec, EnumRuleChildKind, LeafEnumBuildSpec,
LeafStructBuildSpec, LeafStructMemberKind, MemberChildToBuild, NodeChildToBuild,
StructBuildSpec, StructChildSpec, VecChild, VecChildToBuild,
BuildSpec, ChildSpec, EnumBuildSpec, SingleBooleanChildToBuild, SingleChildToBuild,
SingleTypeChildToBuild, StructBuildSpec, VecChild, VecChildToBuild,
};
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
@ -9,27 +8,12 @@ use quote::{format_ident, quote};
fn make_enum_type(build_spec: &EnumBuildSpec) -> TokenStream {
let children: Vec<TokenStream> = build_spec
.rules()
.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_type_ident)
}
} else {
quote! {
#member_name_ident
}
.iter()
.map(|rule| {
let member_name_ident = format_ident!("{}", rule.rule());
let child_name_ident = format_ident!("{}", rule.build());
quote! {
#member_name_ident(#child_name_ident)
}
})
.collect();
@ -41,200 +25,114 @@ 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) = (
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"),
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()),
),
};
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>>
});
annotated_members.push(quote! {
#child_ident: Vec<Box<#child_type_ident>>
});
accessors.push(quote! {
pub fn #child_ident(&self) -> impl Iterator<Item = &#child_type_ident> {
self.#child_ident.iter().map(Box::as_ref)
}
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)
}
pub fn #child_ident_mut(&mut self) -> impl Iterator<Item = &mut #child_type_ident> {
self.#child_ident.iter_mut().map(Box::as_mut)
}
});
pub fn #child_ident_mut(&mut self) -> impl Iterator<Item = &mut #child_type_ident> {
self.#child_ident.iter_mut().map(Box::as_mut)
}
VecChildToBuild::String => accessors.push(quote! {
pub fn #child_ident(&self) -> impl Iterator<Item = &str> {
self.#child_ident.iter().map(String::as_str)
}
}),
}
});
}
fn handle_node_child(
name: &str,
node_child: &NodeChildToBuild,
fn handle_single_type_child(
single_type_child: &SingleTypeChildToBuild,
member_names: &mut Vec<Ident>,
annotated_members: &mut Vec<TokenStream>,
member_args: &mut Vec<TokenStream>,
accessors: &mut Vec<TokenStream>,
) {
let child_ident = format_ident!("{}", name);
let child_ident_mut = format_ident!("{}_mut", name);
let child_type_ident = format_ident!("{}", node_child.build());
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());
member_names.push(child_ident.clone());
if node_child.optional() {
if single_type_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
}
}
accessors.push(quote! {
pub fn #child_ident(&self) -> &#child_type_ident {
self.#child_ident.as_ref()
}
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()
}
pub fn #child_ident_mut(&mut self) -> &mut #child_type_ident {
self.#child_ident.as_mut()
}
});
}
pub fn #child_ident_mut(&mut self) -> &mut #child_type_ident {
self.#child_ident.as_mut()
}
});
}
fn handle_boolean_child(
single_boolean_child: &BooleanChildToBuild,
fn handle_single_boolean_child(
single_boolean_child: &SingleBooleanChildToBuild,
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.name());
let child_ident = format_ident!("{}", single_boolean_child.var_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() {
for child_spec in build_spec.children().iter() {
match child_spec {
StructChildSpec::SkipChild(_) => {}
StructChildSpec::VecChild(vec_child) => {
ChildSpec::SkipChild(_) => {}
ChildSpec::VecChild(vec_child) => {
handle_vec_child(
vec_child,
&mut member_names,
&mut annotated_members,
&mut member_args,
&mut accessors,
);
}
StructChildSpec::MemberChild(member_child) => {
match member_child.build() {
MemberChildToBuild::Node(node_child) => {
handle_node_child(
member_child.name(),
node_child,
ChildSpec::SingleChild(single_child) => {
match single_child.build() {
SingleChildToBuild::Type(single_type_child) => {
handle_single_type_child(
single_type_child,
&mut member_names,
&mut annotated_members,
&mut member_args,
&mut accessors,
);
}
MemberChildToBuild::Boolean(boolean_child) => {
handle_boolean_child(
boolean_child,
SingleChildToBuild::Boolean(single_boolean_child) => {
handle_single_boolean_child(
single_boolean_child,
&mut member_names,
&mut annotated_members,
&mut member_args,
&mut accessors,
);
}
@ -262,91 +160,9 @@ fn make_struct_type(build_spec: &StructBuildSpec) -> 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> {
pub fn make_type(build_spec: &BuildSpec) -> TokenStream {
match 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,
BuildSpec::Enum(enum_build_spec) => make_enum_type(enum_build_spec),
BuildSpec::Struct(struct_build_spec) => make_struct_type(struct_build_spec),
}
}

View File

@ -1,9 +0,0 @@
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,40 +1,9 @@
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");
fn main() -> std::io::Result<()> {
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)?;
// 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)?;
Ok(())
}

View File

@ -1,11 +0,0 @@
[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

@ -1,72 +0,0 @@
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

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

View File

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

View File

@ -1 +0,0 @@
false

View File

@ -1 +0,0 @@
true

View File

@ -1,106 +1,6 @@
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);
}
}
}
pub mod build;
pub mod children;
pub mod node;
pub mod pretty_print;
pub mod unparse;
pub mod walk;

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,58 +1,54 @@
// 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)
// }
// }
// }
// }
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() {
panic!("TODO")
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)
}
}
}
}

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,12 +4,14 @@ 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
}

1338
src/name_analysis/gather.rs Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

268
src/name_analysis/mod.rs Normal file
View File

@ -0,0 +1,268 @@
/*!
# 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

@ -1,267 +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::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,10 +1,29 @@
use crate::ast::node::named::Named;
use crate::ast::node::*;
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::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,3 +1,6 @@
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;
@ -25,7 +28,7 @@ impl SourceDefinition {
range: borrowed.range(),
}
}
pub fn file_id(&self) -> usize {
self.file_id
}

View File

@ -5,65 +5,34 @@ 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. 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.
description: A definition of a node type.
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.
@ -76,40 +45,39 @@ $defs:
additionalProperties: false
description: A description of a leaf-enum node to be built.
properties:
leaf_rules:
type:
const: leaf_enum
rules:
type: array
description: Alternative parse rules that build this node.
items:
type: string
$ref: "#/$defs/LeafEnumChildDefinition"
required:
- leaf_rules
# Struct node children
- type
- rules
StructChildDefinition:
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.
description: A definition of a node's child. Either a bare child name (string) in snake case, or an object.
oneOf:
- type: string
- $ref: "#/$defs/StructChildDefinitionWrapper"
StructChildDefinitionWrapper:
description: Shorthand where child name, var, build, and with are inferred from the given snake-case child name.
- $ref: "#/$defs/ChildDefinitionWrapper"
ChildDefinitionWrapper:
type: object
description: Single-key object mapping the child-name to its advanced definition.
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/StructChildAdvancedDefinition"
StructChildAdvancedDefinition:
$ref: "#/$defs/ChildDefinition"
ChildDefinition:
type: object
description: One of skip/vec/single child specs.
oneOf:
- $ref: "#/$defs/StructChildSkipChildDefinition"
- $ref: "#/$defs/StructChildVecChildDefinition"
- $ref: "#/$defs/StructChildMemberDefinition"
StructChildSkipChildDefinition:
- $ref: "#/$defs/SkipChildDefinition"
- $ref: "#/$defs/VecChildDefinition"
- $ref: "#/$defs/SingleChildDefinition"
SkipChildDefinition:
type: object
additionalProperties: false
description: A definition for a child rule that does nothing, i.e., is skipped.
@ -117,121 +85,49 @@ $defs:
rule:
type: string
skip:
type: boolean
const: true
type: boolean # note: must be true
required:
- rule
- skip
StructChildVecChildDefinition:
VecChildDefinition:
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
StructChildMemberDefinition:
SingleChildDefinition:
type: object
additionalProperties: false
description: A definition for a child rule that builds one item.
properties:
rule:
type: string
description: The rule to match.
description: The type to build, in Pascal case.
optional:
type: boolean
description: If true, this child will be stored as an Option.
build:
oneOf:
- type: string
- $ref: "#/$defs/StructChildMemberBuildDefinition"
StructChildMemberBuildDefinition:
- $ref: "#/$defs/SingleChildBuildDefinition"
SingleChildBuildDefinition:
type: object
additionalProperties: false
description: A definition of what exactly to build for a given child rule.
oneOf:
- $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:
- $ref: "#/$defs/BuildSingleTypeChild"
- $ref: "#/$defs/BuildBooleanChild"
- $ref: "#/$defs/BuildStringChild"
- $ref: "#/$defs/BuildDoubleChild"
- $ref: "#/$defs/BuildIntChild"
- $ref: "#/$defs/BuildLongChild"
BuildSingleTypeChild:
type: object
additionalProperties: false
description: A definition of a single-type child to build.
@ -242,15 +138,107 @@ $defs:
or_else_default:
type: boolean
description: Whether to call the default method on the built-type if the rule is not found.
BuildBoolean:
BuildBooleanChild:
type: object
additionalProperties: false
description: A boolean member to be built.
description: A definition for building a boolean child.
properties:
kind:
type:
type: string
const: boolean
enum:
- boolean
on:
type: string
enum:
- rule_present
- 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,159 +1,28 @@
# $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:
members:
- name:
kind: string
from: whole_pair
children:
- literal:
build:
type: string
from: parse_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:
optional: true
- parent_mod
- use_statements:
rule: UseStatement
vec: true
- module_level_declarations:
rule: ModuleLevelDeclaration
vec: true
- eoi:
rule: EOI
skip: true
ParentMod:
children:
- mod_kw:
@ -177,8 +46,8 @@ UseStatementPrefix:
UseStatementSuffix:
rules:
- Identifier
- Star:
child: false
- rule: Star
build: UseStatementStarSuffix
- UseList
UseList:
children:
@ -218,7 +87,7 @@ Module:
- is_public:
rule: Pub
build:
kind: boolean
type: boolean
on: rule_present
- mod_kw:
rule: Mod
@ -250,7 +119,7 @@ Interface:
- is_public:
rule: Pub
build:
kind: boolean
type: boolean
on: rule_present
- int_kw:
rule: IntKw
@ -273,7 +142,7 @@ Class:
- is_public:
rule: Pub
build:
kind: boolean
type: boolean
on: rule_present
- class_kw:
rule: ClassKw
@ -298,7 +167,7 @@ Function:
- is_public:
rule: Pub
build:
kind: boolean
type: boolean
on: rule_present
- fn_kw:
rule: Fn
@ -316,7 +185,7 @@ OperatorFunction:
- is_public:
rule: Pub
build:
kind: boolean
type: boolean
on: rule_present
- op_kw:
rule: Op
@ -336,7 +205,7 @@ PlatformFunction:
- is_public:
rule: Pub
build:
kind: boolean
type: boolean
on: rule_present
- platform_kw:
rule: Platform
@ -447,12 +316,12 @@ Member:
- is_public:
rule: Pub
build:
kind: boolean
type: boolean
on: rule_present
- is_mut:
rule: Mut
build:
kind: boolean
type: boolean
on: rule_present
- identifier
- type_use
@ -464,7 +333,8 @@ Statement:
- AssignmentStatement
- ExpressionStatement
- UseStatement
- IfStatement
- IfElseStatement
- IfStatementStatement
- WhileStatement
- ForStatement
VariableDeclaration:
@ -475,7 +345,7 @@ VariableDeclaration:
- is_mut:
rule: Mut
build:
kind: boolean
type: boolean
on: rule_present
- identifier
- type_use:
@ -560,7 +430,7 @@ ForStatement:
vec: true
- end_kw:
rule: End
skip: true
vec: true
# Expressions
Expression:
@ -612,7 +482,7 @@ ComparisonExpression:
rule: Expression
optional: true
ComparisonOperator:
leaf_rules:
rules:
- Greater
- Less
- GreaterEqual
@ -628,9 +498,8 @@ ShiftExpression:
optional: true
- right:
rule: Expression
optional: true
ShiftOperator:
leaf_rules:
rules:
- LeftShift
- RightShift
AdditiveExpression:
@ -643,10 +512,6 @@ AdditiveExpression:
- right:
rule: Expression
optional: true
AdditiveOperator:
leaf_rules:
- Add
- Subtract
MultiplicativeExpression:
children:
- left:
@ -656,9 +521,9 @@ MultiplicativeExpression:
optional: true
- right:
rule: Expression
optional: true
MultiplicativeOperator:
leaf_rules:
type: leaf_enum
rules:
- Multiply
- Divide
- Modulo
@ -667,10 +532,9 @@ PrefixExpression:
- operators:
rule: PrefixOperator
vec: true
- right:
rule: SuffixExpression
PrefixOperator:
leaf_rules:
type: leaf_enum
rules:
- Spread
- Not
- Negative
@ -682,14 +546,16 @@ SuffixExpression:
rule: SuffixOperator
vec: true
SuffixOperator:
type: leaf_enum
rules:
- PlusPlus:
child: false
- MinusMinus:
child: false
- ObjectProperty
- ObjectIndex
- Call
- PlusPlus
- MinusMinus
- ObjectProperty:
child: true
- ObjectIndex:
child: true
- Call:
child: true
ObjectProperty:
children:
- identifier
@ -759,48 +625,92 @@ ClosureParameter:
# Literals
Literal:
rules:
- IntLiteral:
kind: int
- LongLiteral:
kind: long
- DoubleLiteral:
kind: double
- SingleQuoteString:
kind: string
- DString
- BacktickString
- BooleanLiteral:
kind: boolean
# Numbers
- NumberLiteral
- StringLiteral
- BooleanLiteral
NumberLiteral:
rules:
- DoubleLiteral
- LongLiteral
- IntLiteral
IntLiteral:
produce:
kind: int
children:
- number_base
- literal:
build:
type: i32
from: parse_number_base
LongLiteral:
produce:
kind: long
children:
- number_base
- literal:
build:
type: i64
from: parse_number_base
DoubleLiteral:
produce:
kind: double
# Strings
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
- BacktickString
SingleQuoteString:
produce:
kind: string
from: string_inner
DString:
children:
- string_inner:
optional: true
DoubleQuoteString:
children:
- inners:
rule: DStringInner
kind: string
vec: true
- expressions:
rule: DStringExpression
vec: true
StringInner:
children:
- literal:
build:
type: string
from: whole_pair
DStringInner:
produce:
kind: string
from: whole_pair
children:
- literal:
build:
type: string
from: whole_pair
DStringExpression:
children:
- expression
@ -808,16 +718,19 @@ BacktickString:
children:
- inners:
rule: BacktickInner
kind: string
vec: true
- expressions:
rule: DStringExpression
vec: true
BacktickInner:
produce:
kind: string
from: whole_pair
children:
- literal:
build:
type: string
from: whole_pair
BooleanLiteral:
produce:
kind: boolean
from: parse_whole_pair
children:
- literal:
build:
type: boolean
from: parse_whole_pair

View File

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

View File

@ -7,6 +7,7 @@ pub struct DeimosParser;
#[cfg(test)]
mod deimos_parser_tests {
use crate::parser::{DeimosParser, Rule};
use indoc::indoc;
use pest::Parser;
macro_rules! fail_rule {
@ -44,7 +45,160 @@ mod deimos_parser_tests {
}
}
mod generated_tests {
include!(concat!(env!("OUT_DIR"), "/src/parser/tests/test_suites.rs"));
#[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}");
}
}

View File

@ -1 +0,0 @@
``

View File

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

View File

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

View File

@ -1 +0,0 @@
0x1234abcd

View File

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

View File

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

View File

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

View File

@ -1 +0,0 @@
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,13 +5,9 @@ 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_level: start_level,
indent_string: indent_string.to_string(),
out,
}
@ -28,7 +24,7 @@ impl IndentWriter {
pub fn write(&mut self, s: &str) -> std::io::Result<()> {
write!(self.out, "{}", s)
}
pub fn writeln(&mut self, s: &str) -> std::io::Result<()> {
self.write(&format!("{}\n", s))
}
@ -39,7 +35,7 @@ impl IndentWriter {
}
write!(self.out, "{}", s)
}
pub fn writeln_indented(&mut self, s: &str) -> std::io::Result<()> {
self.write_indented(&format!("{}\n", s))
}

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

@ -28,7 +28,7 @@ impl DvmFunction {
pub fn instructions(&self) -> &Vec<Instruction> {
&self.instructions
}
pub fn source_code_location(&self) -> &SourceCodeLocation {
&self.source_code_location
}

View File

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

View File

@ -21,7 +21,7 @@ impl DvmMethod {
implements,
}
}
pub fn fqn(&self) -> &str {
self.function.fqn()
}

View File

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

View File

@ -7,9 +7,9 @@ pub enum DvmType {
Double,
Boolean,
USize,
// Other
Object,
Array,
ConstantPointer,
ConstantPointer
}

View File

@ -70,7 +70,7 @@ impl DvmValue {
panic!("Expected DvmValue::Int, but found DvmValue::{:?}", self);
}
}
pub fn expect_int_or_else(&self, f: impl FnOnce(&Self) -> i32) -> i32 {
if let DvmValue::Int(i) = self {
*i