Skip to content

Commit

Permalink
Revamp built-ins generation code
Browse files Browse the repository at this point in the history
  • Loading branch information
ggiraldez committed Oct 4, 2024
1 parent 7e05529 commit 904199a
Show file tree
Hide file tree
Showing 18 changed files with 306 additions and 262 deletions.
42 changes: 42 additions & 0 deletions crates/codegen/language/definition/src/model/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub struct Language {

pub documentation_dir: PathBuf,
pub binding_rules_file: PathBuf,
pub file_extension: Option<String>,

pub root_item: Identifier,

Expand Down Expand Up @@ -107,6 +108,47 @@ impl Language {

res
}

/// Collects all versions that change the language built-ins.
///
/// Includes the first supported version. Returns an empty set if there are
/// no built-ins defined.
pub fn collect_built_ins_versions(&self) -> BTreeSet<Version> {
if self.built_ins.is_empty() {
return BTreeSet::new();
}

let first = self.versions.first().unwrap().clone();
let mut res = BTreeSet::from_iter([first]);

let mut add_spec = |spec: &Option<VersionSpecifier>| {
if let Some(spec) = spec {
res.extend(spec.versions().cloned());
}
};

for item in &self.built_ins {
match item {
BuiltIn::BuiltInFunction { item } => {
add_spec(&item.enabled);
}
BuiltIn::BuiltInType { item } => {
add_spec(&item.enabled);
for field in &item.fields {
add_spec(&field.enabled);
}
for function in &item.functions {
add_spec(&function.enabled);
}
}
BuiltIn::BuiltInVariable { item } => {
add_spec(&item.enabled);
}
}
}

res
}
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
Expand Down
2 changes: 2 additions & 0 deletions crates/codegen/language/tests/src/pass/tiny_language.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ codegen_language_macros::compile!(Language(
name = Tiny,
documentation_dir = "tiny/docs",
binding_rules_file = "tiny/bindings/rules.msgb",
file_extension = ".tiny",
root_item = Foo,
leading_trivia = Sequence([]),
trailing_trivia = Sequence([]),
Expand Down Expand Up @@ -50,6 +51,7 @@ fn definition() {
name: "Tiny".into(),
documentation_dir: Path::repo_path("tiny/docs"),
binding_rules_file: Path::repo_path("tiny/bindings/rules.msgb"),
file_extension: Some(".tiny".into()),
root_item: "Foo".into(),
leading_trivia: TriviaParser::Sequence { parsers: [].into() },
trailing_trivia: TriviaParser::Sequence { parsers: [].into() },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@ use semver::Version;
#[allow(unused_variables)]
pub fn get_contents(version: &Version) -> &'static str {
{%- if not rendering_in_stubs -%}
{%- for version, contents in model.bindings.built_ins_source %}
{%- if loop.last -%}
{
r####"{{ contents }}"####
}
{%- else -%}
{%- for version in model.bindings.built_ins_versions %}
{%- if not loop.first -%}
if *version < Version::new({{ version | split(pat=".") | join(sep=", ") }}) {
r####"{{ contents }}"####
include_str!("./built_ins/{{ prev_version }}{{ model.bindings.file_extension }}")
} else
{% endif %}
{%- endfor -%}
{%- else -%}
unreachable!("Built-ins not defined in stubs")
{%- endif -%}
{% if loop.last %}
{
include_str!("./built_ins/{{ version }}{{ model.bindings.file_extension }}")
}
{% endif %}
{% set_global prev_version = version %}
{%- else -%}
""
{%- endfor -%}
{%- else -%}
unreachable!("Built-ins not defined in stubs")
{%- endif -%}
}
108 changes: 12 additions & 96 deletions crates/codegen/runtime/generator/src/bindings/mod.rs
Original file line number Diff line number Diff line change
@@ -1,117 +1,33 @@
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::rc::Rc;

use anyhow::Result;
use codegen_language_definition::model::{BuiltIn, BuiltInField, BuiltInFunction, BuiltInType};
use codegen_language_definition::model::Language;
use infra_utils::codegen::CodegenFileSystem;
use infra_utils::paths::PathExtensions;
use semver::Version;
use serde::Serialize;

use crate::LanguageModel;

#[derive(Default, Serialize)]
pub struct BindingsModel {
binding_rules_source: String,
built_ins_source: BTreeMap<Version, String>,
built_ins_versions: BTreeSet<Version>,
file_extension: String,
}

impl BindingsModel {
pub fn from_language(language: &LanguageModel) -> Result<Self> {
pub fn from_language(language: &Rc<Language>) -> Result<Self> {
// We use `CodegenFileSystem` here to ensure the rules are rebuilt if the rules file changes
let binding_rules_dir = language.definition.binding_rules_file.unwrap_parent();
let binding_rules_dir = language.binding_rules_file.unwrap_parent();
let mut fs = CodegenFileSystem::new(binding_rules_dir)?;
let binding_rules_source = fs.read_file(&language.definition.binding_rules_file)?;

let mut built_ins_source = BTreeMap::new();
let mut last_contents = None;
for version in &language.definition.versions {
let built_ins = filter_built_ins_for_version(&language.definition.built_ins, version);
let contents = (language.render_built_ins)(&built_ins);
match last_contents {
None => last_contents = Some(contents),
Some(ref last) if last == &contents => (),
_ => {
let last = last_contents.replace(contents);
built_ins_source.insert(version.clone(), last.unwrap());
}
}
}
if let Some(version) = language.definition.versions.last() {
built_ins_source.insert(version.clone(), last_contents.unwrap());
}
let binding_rules_source = fs.read_file(&language.binding_rules_file)?;
let built_ins_versions = language.collect_built_ins_versions();
let file_extension = language.file_extension.clone().unwrap_or_default();

Ok(Self {
binding_rules_source,
built_ins_source,
})
}
}

fn filter_built_ins_for_version(built_ins: &[BuiltIn], version: &Version) -> Vec<BuiltIn> {
built_ins
.iter()
.filter_map(|built_in| match built_in {
BuiltIn::BuiltInFunction { item } => filter_built_in_function(item, version)
.map(|item| BuiltIn::BuiltInFunction { item: item.into() }),
BuiltIn::BuiltInType { item } => filter_built_in_type(item, version)
.map(|item| BuiltIn::BuiltInType { item: item.into() }),
BuiltIn::BuiltInVariable { item } => filter_built_in_field(item, version)
.map(|item| BuiltIn::BuiltInVariable { item: item.into() }),
})
.collect()
}

fn filter_built_in_function(
function: &BuiltInFunction,
version: &Version,
) -> Option<BuiltInFunction> {
if function
.enabled
.as_ref()
.map_or(true, |enabled| enabled.contains(version))
{
Some(function.clone())
} else {
None
}
}

fn filter_built_in_field(field: &BuiltInField, version: &Version) -> Option<BuiltInField> {
if field
.enabled
.as_ref()
.map_or(true, |enabled| enabled.contains(version))
{
Some(field.clone())
} else {
None
}
}

fn filter_built_in_type(typ: &BuiltInType, version: &Version) -> Option<BuiltInType> {
if typ
.enabled
.as_ref()
.map_or(true, |enabled| enabled.contains(version))
{
let fields = typ
.fields
.iter()
.filter_map(|field| filter_built_in_field(field, version))
.collect();
let functions = typ
.functions
.iter()
.filter_map(|function| filter_built_in_function(function, version))
.collect();

Some(BuiltInType {
name: typ.name.clone(),
fields,
functions,
enabled: typ.enabled.clone(),
built_ins_versions,
file_extension,
})
} else {
None
}
}
94 changes: 94 additions & 0 deletions crates/codegen/runtime/generator/src/built_ins/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use std::path::Path;
use std::rc::Rc;

use anyhow::Result;
use codegen_language_definition::model::{
BuiltIn, BuiltInField, BuiltInFunction, BuiltInType, Language,
};
use semver::Version;

pub fn render_built_ins(
language: &Rc<Language>,
output_dir: &Path,
render_fn: impl Fn(&[BuiltIn]) -> String,
) -> Result<()> {
let versions = language.collect_built_ins_versions();
let file_extension = language.file_extension.clone().unwrap_or_default();
for version in &versions {
let built_ins = filter_built_ins_for_version(&language.built_ins, version);
let contents = render_fn(&built_ins);

let output_path = output_dir.join(format!("{version}{file_extension}"));
std::fs::write(output_path, contents)?;
}
Ok(())
}

fn filter_built_ins_for_version(built_ins: &[BuiltIn], version: &Version) -> Vec<BuiltIn> {
built_ins
.iter()
.filter_map(|built_in| match built_in {
BuiltIn::BuiltInFunction { item } => filter_built_in_function(item, version)
.map(|item| BuiltIn::BuiltInFunction { item: item.into() }),
BuiltIn::BuiltInType { item } => filter_built_in_type(item, version)
.map(|item| BuiltIn::BuiltInType { item: item.into() }),
BuiltIn::BuiltInVariable { item } => filter_built_in_field(item, version)
.map(|item| BuiltIn::BuiltInVariable { item: item.into() }),
})
.collect()
}

fn filter_built_in_function(
function: &BuiltInFunction,
version: &Version,
) -> Option<BuiltInFunction> {
if function
.enabled
.as_ref()
.map_or(true, |enabled| enabled.contains(version))
{
Some(function.clone())
} else {
None
}
}

fn filter_built_in_field(field: &BuiltInField, version: &Version) -> Option<BuiltInField> {
if field
.enabled
.as_ref()
.map_or(true, |enabled| enabled.contains(version))
{
Some(field.clone())
} else {
None
}
}

fn filter_built_in_type(typ: &BuiltInType, version: &Version) -> Option<BuiltInType> {
if typ
.enabled
.as_ref()
.map_or(true, |enabled| enabled.contains(version))
{
let fields = typ
.fields
.iter()
.filter_map(|field| filter_built_in_field(field, version))
.collect();
let functions = typ
.functions
.iter()
.filter_map(|function| filter_built_in_function(function, version))
.collect();

Some(BuiltInType {
name: typ.name.clone(),
fields,
functions,
enabled: typ.enabled.clone(),
})
} else {
None
}
}
Loading

0 comments on commit 904199a

Please sign in to comment.