Browse Source

Backend command line option (#23)

* Add -t CLI option

* Run rustfmt

* Default to JS target

* Move Target to generator module

* Pass Target when building

* cmd/run: support target option

* Reformat code

* Get rid of backend_* features

* Remove all feature checks

* ci: remove --all-features

We don't have any features left so there's no reason to enable them

* docs: update backends page

* Add --target to changelog

* Implement Copy for Target

That's a very small enum so it doesn't matter if we duplicate it.

* Make LLVM support optional with llvm feature
pull/24/head
Alexey 3 years ago committed by GitHub
parent
commit
fe3a5c5e2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .builds/ci.yml
  2. 2
      .github/workflows/ci.yml
  3. 8
      CHANGELOG.md
  4. 8
      Cargo.toml
  5. 26
      docs/developers/backends.md
  6. 21
      src/builder/mod.rs
  7. 5
      src/command/build.rs
  8. 77
      src/command/run.rs
  9. 39
      src/generator/mod.rs
  10. 1
      src/generator/tests/mod.rs
  11. 21
      src/main.rs
  12. 26
      src/tests/test_examples.rs
  13. 1
      src/util/mod.rs

2
.builds/ci.yml

@ -9,4 +9,4 @@ tasks:
cd antimony
cargo build
cargo test
cargo clippy --all-targets --all-features -- -D warnings
cargo clippy --all-targets -- -D warnings

2
.github/workflows/ci.yml

@ -81,4 +81,4 @@ jobs:
- uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-targets --all-features -- -D warnings
args: --all-targets -- -D warnings

8
CHANGELOG.md

@ -1,5 +1,13 @@
# Changelog
## Unreleased
- Feature flags for backends (`backend_c`, etc.) have been replaced by `--target` (`-t`) command-line option:
```sh
sb -t c run examples/hello_world.sb
```
## v0.5.1 (2021-02-25)
Sabre is now Antimony!

8
Cargo.toml

@ -15,13 +15,7 @@ name = "sb"
path = "src/main.rs"
[features]
backend_c = []
backend_node = []
backend_llvm = ["inkwell"]
# To change, use the --featues flag:
# cargo run --no-default-features --features backend_llvm
default = ["backend_node"]
llvm = ["inkwell"]
[dependencies]
structopt = "0.3.21"

26
docs/developers/backends.md

@ -1,25 +1,23 @@
# Backends
Antimony currently implements a JavaScript backend, but a C backend is in development. WASM, ARM and x86 are planned.
The backend can be specified in the `Cargo.toml` file in the root of the project:
```toml
[features]
...
Backend can be specified when running on building with `--target` (`-t`) option, default is `js`:
default = ["backend_c"]
```
If you're working on an unstable backend, you can override the backend using the `--features --no-default-features` flag of the cargo CLI:
```
cargo run --no-default-features --features backend_llvm ...
```sh
sb -t c build in.sb --out-file out
```
## Available Backends
| Target Language | Identifier | Stability notice |
| :-------------- | :------------- | :--------------- |
| Node.js | `backend_node` | mostly stable |
| LLVM | `backend_llvm` | unstable |
| C | `backend_c` | unstable |
| Node.js | `js` | mostly stable |
| LLVM | `llvm` | unstable |
| C | `c` | unstable |
LLVM also requires to enable `llvm` feature when building:
```sh
cargo build --features llvm
```

21
src/builder/mod.rs

@ -4,6 +4,7 @@ use crate::lexer;
use crate::parser;
use crate::Lib;
use crate::PathBuf;
use generator::Generator;
use std::env;
/**
* Copyright 2021 Garrit Franke
@ -108,7 +109,11 @@ impl Builder {
Ok(module)
}
pub(crate) fn generate(&mut self, out_file: PathBuf) -> Result<(), String> {
pub(crate) fn generate(
&mut self,
target: generator::Target,
out_file: PathBuf,
) -> Result<(), String> {
let mut mod_iter = self.modules.iter();
// TODO: We shouldn't clone here
@ -116,7 +121,19 @@ impl Builder {
for module in mod_iter {
condensed.merge_with(module.clone());
}
let output = generator::generate(condensed);
let output = match target {
generator::Target::JS => generator::js::JsGenerator::generate(condensed),
generator::Target::C => generator::c::CGenerator::generate(condensed),
generator::Target::LLVM => {
#[cfg(feature = "llvm")]
return generator::llvm::LLVMGenerator::generate(condensed);
#[cfg(not(feature = "llvm"))]
panic!("'llvm' feature should be enabled to use LLVM target");
}
};
let mut file = std::fs::File::create(out_file).expect("create failed");
file.write_all(output.as_bytes()).expect("write failed");
file.flush().map_err(|_| "Could not flush file".into())

5
src/command/build.rs

@ -14,10 +14,11 @@
* limitations under the License.
*/
use crate::builder;
use crate::generator;
use std::path::Path;
pub fn build(in_file: &Path, out_file: &Path) -> Result<(), String> {
pub fn build(target: generator::Target, in_file: &Path, out_file: &Path) -> Result<(), String> {
let mut b = builder::Builder::new(in_file.to_path_buf());
b.build()?;
b.generate(out_file.to_path_buf())
b.generate(target, out_file.to_path_buf())
}

77
src/command/run.rs

@ -14,6 +14,7 @@
* limitations under the License.
*/
use crate::command::build;
use crate::generator::Target;
use std::io::Write;
use std::path::PathBuf;
use std::process;
@ -21,54 +22,58 @@ use std::process::Command;
use std::process::Stdio;
use tempfile::tempdir;
pub fn run(in_file: PathBuf) -> Result<(), String> {
pub fn run(target: Target, in_file: PathBuf) -> Result<(), String> {
let out_dir = tempdir()
.expect("Could not create temporary file")
.into_path();
let intermediate_out_file_path = out_dir.join("intermediate.c");
build::build(&in_file, &intermediate_out_file_path)?;
build::build(target, &in_file, &intermediate_out_file_path)?;
let out_file = out_dir.join("out");
if cfg!(feature = "backend_c") {
Command::new("/usr/bin/cc")
.arg(&intermediate_out_file_path)
.arg("-o")
.arg(&out_file)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("Could not spawn compilation process");
match target {
Target::C => {
Command::new("/usr/bin/cc")
.arg(&intermediate_out_file_path)
.arg("-o")
.arg(&out_file)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("Could not spawn compilation process");
let out = Command::new(out_file)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("Could not spawn run process");
let out = Command::new(out_file)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("Could not spawn run process");
std::io::stdout()
.write_all(&out.stdout)
.expect("Could not write to stdout");
std::io::stdout()
.write_all(&out.stdout)
.expect("Could not write to stdout");
std::io::stderr()
.write_all(&out.stderr)
.expect("Could not write to stderr");
} else if cfg!(feature = "backend_node") {
let out = Command::new("node")
.arg(&intermediate_out_file_path)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("Could not spawn run process");
std::io::stderr()
.write_all(&out.stderr)
.expect("Could not write to stderr");
}
Target::JS => {
let out = Command::new("node")
.arg(&intermediate_out_file_path)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("Could not spawn run process");
std::io::stdout()
.write_all(&out.stdout)
.expect("Could not write to stdout");
std::io::stdout()
.write_all(&out.stdout)
.expect("Could not write to stdout");
std::io::stderr()
.write_all(&out.stderr)
.expect("Could not write to stderr");
std::io::stderr()
.write_all(&out.stderr)
.expect("Could not write to stderr");
process::exit(out.status.code().unwrap())
process::exit(out.status.code().unwrap())
}
_ => todo!(),
}
Ok(())
}

39
src/generator/mod.rs

@ -14,32 +14,39 @@
* limitations under the License.
*/
use crate::ast::*;
use std::str::FromStr;
#[cfg(feature = "backend_c")]
pub mod c;
#[cfg(feature = "backend_node")]
pub mod js;
#[cfg(feature = "backend_llvm")]
#[cfg(feature = "llvm")]
pub mod llvm;
#[cfg(test)]
mod tests;
pub mod x86;
pub trait Generator {
fn generate(prog: Module) -> String;
#[derive(Debug, Clone, Copy)]
pub enum Target {
C,
JS,
LLVM,
}
// Since we're using multiple features,
// "unreachable" statements are okay
#[allow(unreachable_code)]
pub fn generate(prog: Module) -> String {
#[cfg(feature = "backend_llvm")]
return llvm::LLVMGenerator::generate(prog);
#[cfg(feature = "backend_c")]
return c::CGenerator::generate(prog);
impl FromStr for Target {
type Err = String;
#[cfg(feature = "backend_node")]
return js::JsGenerator::generate(prog);
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
panic!("No backend specified");
match s.as_str() {
"c" => Ok(Target::C),
"js" => Ok(Target::JS),
"llvm" => Ok(Target::LLVM),
_ => Err(format!("no target {} found", s)),
}
}
}
pub trait Generator {
fn generate(prog: Module) -> String;
}

1
src/generator/tests/mod.rs

@ -1,2 +1 @@
#[cfg(feature = "backend_c")]
mod c_tests;

21
src/main.rs

@ -19,6 +19,7 @@ extern crate rust_embed;
extern crate structopt;
extern crate tempfile;
use generator::Target;
use std::path::PathBuf;
use structopt::StructOpt;
@ -42,7 +43,7 @@ pub struct Lib;
pub struct Builtins;
#[derive(StructOpt, Debug)]
enum Opt {
enum Command {
#[structopt()]
Build {
in_file: PathBuf,
@ -53,12 +54,24 @@ enum Opt {
Run { in_file: PathBuf },
}
#[derive(StructOpt, Debug)]
struct Opt {
#[structopt(subcommand)]
command: Command,
/// Target language. Options: c, js, llvm
#[structopt(long, short, default_value = "js", parse(try_from_str))]
target: Target,
}
fn main() -> Result<(), String> {
let opts = Opt::from_args();
match opts {
Opt::Build { in_file, out_file } => command::build::build(&in_file, &out_file)?,
Opt::Run { in_file } => command::run::run(in_file)?,
match opts.command {
Command::Build { in_file, out_file } => {
command::build::build(opts.target, &in_file, &out_file)?
}
Command::Run { in_file } => command::run::run(opts.target, in_file)?,
};
Ok(())

26
src/tests/test_examples.rs

@ -25,13 +25,7 @@ fn test_directory(dir_in: &str) -> Result<(), Error> {
let _ = fs::create_dir(&dir_out);
let out_file_suffix = if cfg!(feature = "backend_node") {
".js"
} else if cfg!(feature = "backend_c") {
".c"
} else {
todo!()
};
let out_file_suffix = ".js";
for ex in examples {
let example = ex?;
@ -59,16 +53,14 @@ fn test_directory(dir_in: &str) -> Result<(), Error> {
.success();
assert_eq!(success, true, "{:?}", &in_file);
if cfg!(feature = "backend_node") {
let node_installed = Command::new("node").arg("-v").spawn()?.wait()?.success();
if node_installed {
let execution = Command::new("node")
.arg(out_file)
.spawn()?
.wait()?
.success();
assert_eq!(execution, true, "{:?}", &in_file)
}
let node_installed = Command::new("node").arg("-v").spawn()?.wait()?.success();
if node_installed {
let execution = Command::new("node")
.arg(out_file)
.spawn()?
.wait()?
.success();
assert_eq!(execution, true, "{:?}", &in_file)
}
}
Ok(())

1
src/util/mod.rs

@ -15,7 +15,6 @@
*/
pub mod string_util;
#[cfg(feature = "backend_c")]
/// Datatype that holds one of two types
pub enum Either<L, R> {
Left(L),

Loading…
Cancel
Save