Skip to content

Commit

Permalink
Standard library chaining -- Closes #14
Browse files Browse the repository at this point in the history
  • Loading branch information
Kampfkarren committed Nov 6, 2019
1 parent 97a6db1 commit 294c4d5
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 40 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- Added standard library chaining. This means you can combine two standard libraries by setting `std` in selene.toml to `std1+std2`. You can chain as many as you want.

## [0.1.0] - 2019-11-06
- Initial release
10 changes: 10 additions & 0 deletions docs/src/cli/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,13 @@ std = "lua52"
```toml
std = "special"
```

### Chaining the standard library

We can chain together multiple standard libraries by simply using a plus sign (`+`) in between the names.

For example, if we had `game.toml` and `engine.toml` standard libraries, we could chain them together like so:

```toml
std = "game+engine"
```
148 changes: 129 additions & 19 deletions selene-lib/src/standard_library.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{
collections::{BTreeMap, HashMap},
fmt,
fmt, fs, io,
path::Path,
};

use serde::{
Expand Down Expand Up @@ -37,6 +38,40 @@ pub struct StandardLibraryMeta {
pub structs: Option<BTreeMap<String, BTreeMap<String, Field>>>,
}

#[derive(Debug)]
pub enum StandardLibraryError {
DeserializeError(toml::de::Error),
IoError(io::Error),
}

impl fmt::Display for StandardLibraryError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
match self {
StandardLibraryError::DeserializeError(error) => {
write!(formatter, "deserialize error: {}", error)
}
StandardLibraryError::IoError(error) => write!(formatter, "io error: {}", error),
}
}
}

impl std::error::Error for StandardLibraryError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use StandardLibraryError::*;

match self {
DeserializeError(error) => Some(error),
IoError(error) => Some(error),
}
}
}

impl From<io::Error> for StandardLibraryError {
fn from(error: io::Error) -> Self {
StandardLibraryError::IoError(error)
}
}

impl StandardLibrary {
pub fn from_name(name: &str) -> Option<StandardLibrary> {
macro_rules! names {
Expand All @@ -54,6 +89,16 @@ impl StandardLibrary {
)
});

if let Some(meta) = &std.meta {
if let Some(base_name) = &meta.base {
let base = StandardLibrary::from_name(base_name);

std.extend(
base.expect("built-in library based off of non-existent built-in"),
);
}
}

std.inflate();

Some(std)
Expand All @@ -71,6 +116,65 @@ impl StandardLibrary {
}
}

pub fn from_config_name(
name: &str,
directory: Option<&Path>,
) -> Result<Option<StandardLibrary>, StandardLibraryError> {
let mut library: Option<StandardLibrary> = None;

for segment in name.split('+') {
let segment_library = match StandardLibrary::from_name(segment) {
Some(default) => default,

None => {
let mut path = directory
.map(Path::to_path_buf)
.unwrap_or_else(||
panic!(
"from_config_name used with no directory, but segment `{}` is not a built-in library",
segment
)
);

path.push(format!("{}.toml", segment));
match StandardLibrary::from_file(&path)? {
Some(library) => library,
None => return Ok(None),
}
}
};

match library {
Some(ref mut base) => base.extend(segment_library),
None => library = Some(segment_library),
};
}

if let Some(ref mut library) = library {
library.inflate();
}

Ok(library)
}

pub fn from_file(filename: &Path) -> Result<Option<StandardLibrary>, StandardLibraryError> {
let content = fs::read_to_string(filename)?;
let mut library: StandardLibrary =
toml::from_str(&content).map_err(StandardLibraryError::DeserializeError)?;

if let Some(meta) = &library.meta {
if let Some(base_name) = &meta.base {
if let Some(base) =
StandardLibrary::from_config_name(&base_name, filename.parent())?
{
library.extend(base);
}
}
}

Ok(Some(library))
}

pub fn find_global(&self, names: &[String]) -> Option<&Field> {
assert!(!names.is_empty());
let mut current = &self.globals;
Expand Down Expand Up @@ -114,12 +218,8 @@ impl StandardLibrary {
}
}

pub fn inflate(&mut self) {
fn merge(
structs: Option<&BTreeMap<String, BTreeMap<String, Field>>>,
into: &mut BTreeMap<String, Field>,
other: &mut BTreeMap<String, Field>,
) {
pub fn extend(&mut self, mut other: StandardLibrary) {
fn merge(into: &mut BTreeMap<String, Field>, other: &mut BTreeMap<String, Field>) {
for (k, v) in other {
let (k, mut v) = (k.to_owned(), v.to_owned());

Expand All @@ -131,7 +231,7 @@ impl StandardLibrary {
if let Some(conflict) = into.get_mut(&k) {
if let Field::Table(ref mut from_children) = v {
if let Field::Table(into_children) = conflict {
merge(structs, into_children, from_children);
merge(into_children, from_children);
continue;
}
}
Expand All @@ -141,24 +241,34 @@ impl StandardLibrary {
}
}

let mut globals =
if let Some(base) = &self.meta.as_ref().and_then(|meta| meta.base.as_ref()) {
let base = StandardLibrary::from_name(base).unwrap_or_else(|| {
panic!("standard library based on '{}', which does not exist", base)
});
if let Some(other_meta) = &mut other.meta {
if let Some(other_structs) = &mut other_meta.structs {
if self.meta.is_none() {
self.meta = Some(StandardLibraryMeta::default());
}

base.globals.clone()
} else {
BTreeMap::new()
};
let meta = self.meta.as_mut().unwrap();

if let Some(structs) = meta.structs.as_mut() {
structs.extend(other_structs.iter().map(|(k, v)| (k.clone(), v.clone())));
} else {
meta.structs = Some(other_structs.clone());
}
}
}

let mut globals = BTreeMap::new();
merge(&mut globals, &mut other.globals);
merge(&mut globals, &mut self.globals);
self.globals = globals;
}

pub fn inflate(&mut self) {
let structs = self
.meta
.as_ref()
.and_then(|meta| meta.structs.as_ref())
.cloned();
merge(structs.as_ref(), &mut globals, &mut self.globals);
self.globals = globals;

for (name, children) in structs.unwrap_or_default() {
self.structs
Expand Down
34 changes: 13 additions & 21 deletions selene/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,28 +264,20 @@ fn start(matches: opts::Options) {
},
};

let standard_library = match fs::read_to_string(format!("{}.toml", &config.std)) {
Ok(contents) => match toml::from_str::<StandardLibrary>(&contents) {
Ok(mut standard_library) => {
standard_library.inflate();
standard_library
}
Err(error) => {
error!(
"Custom standard library wasn't formatted properly: {}",
error
);
return;
}
},
let current_dir = std::env::current_dir().unwrap();
let standard_library = match StandardLibrary::from_config_name(&config.std, Some(&current_dir))
{
Ok(Some(library)) => library,

Err(_) => match StandardLibrary::from_name(&config.std) {
Some(std) => std,
None => {
error!("Unknown standard library '{}'", config.std);
return;
}
},
Ok(None) => {
error!("Standard library was empty.");
return;
}

Err(error) => {
error!("Could not retrieve standard library: {}", error);
return;
}
};

let checker = Arc::new(match Checker::new(config, standard_library) {
Expand Down

0 comments on commit 294c4d5

Please sign in to comment.