Write main
file
This commit is contained in:
parent
b5de11a7fa
commit
e42936fb4a
|
@ -34,7 +34,7 @@ def fibPrompt
|
|||
= do {
|
||||
print "Enter a number: ";
|
||||
num <- read <$> getLine;
|
||||
sequence_ (print . fibonacci <$> [0 .. num]);
|
||||
sequence_ (print . fibonacci <$> (0 .. num));
|
||||
};
|
||||
|
||||
// Program entry point.
|
||||
|
|
10
axc/src/ast2ir.rs
Normal file
10
axc/src/ast2ir.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
//! Conversion of AST to intermediate representation.
|
||||
|
||||
use crate::ir::IR;
|
||||
use crate::SyntaxTree;
|
||||
|
||||
/// Compiles an abstract syntax tree into intermediate representation; this assumes the code already
|
||||
/// type-checks, and emits unoptimized IR.
|
||||
pub fn ast2ir(_: SyntaxTree) -> IR {
|
||||
todo!()
|
||||
}
|
9
axc/src/backends/c.rs
Normal file
9
axc/src/backends/c.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
//! Backend for direct generation of C code.
|
||||
|
||||
use crate::ir::IR;
|
||||
|
||||
/// Generates C code with the same semantics as the intermediate representation. Returns the C
|
||||
/// source code as a string.
|
||||
pub fn generate_c(_ir: &IR) -> String {
|
||||
todo!()
|
||||
}
|
3
axc/src/backends/mod.rs
Normal file
3
axc/src/backends/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
//! Different back-ends for code generation.
|
||||
|
||||
pub mod c;
|
5
axc/src/ir.rs
Normal file
5
axc/src/ir.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
//! Intermediate code representation.
|
||||
|
||||
/// The IR tree representing the whole program.
|
||||
#[derive(Debug)]
|
||||
pub struct IR;
|
|
@ -2,6 +2,14 @@
|
|||
//!
|
||||
//! AlexScript is a based programming language, for based people.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
pub mod ast2ir;
|
||||
pub mod ir;
|
||||
pub mod parser;
|
||||
pub mod typeck;
|
||||
pub mod backends;
|
||||
|
||||
use num_bigint::BigUint;
|
||||
|
||||
/// A concrete syntax tree. This represents the full content of an AlexScript program, including all
|
||||
|
@ -88,46 +96,70 @@ pub struct TypeConstructor {
|
|||
/// Expressions.
|
||||
pub enum Expr {
|
||||
/// Unary operators, e.g., `-5`.
|
||||
UnaryOp { kind: String, val: Box<Expr> },
|
||||
UnaryOp {
|
||||
/// The text of the operator.
|
||||
kind: String,
|
||||
|
||||
/// The value being operated upon.
|
||||
val: Box<Expr>,
|
||||
},
|
||||
|
||||
/// Binary operators, e.g., `5 + 5`.
|
||||
BinaryOp {
|
||||
/// The text of the operator.
|
||||
kind: String,
|
||||
|
||||
/// The left side of the operator.
|
||||
left: Box<Expr>,
|
||||
|
||||
/// The right side of the operator.
|
||||
right: Box<Expr>,
|
||||
},
|
||||
|
||||
/// Function application, e.g., `sin x`.
|
||||
Application {
|
||||
/// The function being applied. For curried functions with multiple arguments (e.g., `atan2
|
||||
/// y x`), this is another expression of type `Application`.
|
||||
func: Box<Expr>,
|
||||
|
||||
/// The argument to which the function is being applied.
|
||||
argument: Box<Expr>,
|
||||
},
|
||||
|
||||
/// Defining of temporary variables, e.g., `let x = 5 in x + x`.
|
||||
Let { left: Pattern, right: Box<Expr> },
|
||||
Let {
|
||||
/// The pattern being bound.
|
||||
left: Pattern,
|
||||
|
||||
/// The variable the pattern is matching.
|
||||
right: Box<Expr>,
|
||||
|
||||
/// The expression the pattern is being substituted into.
|
||||
into: Box<Expr>,
|
||||
},
|
||||
|
||||
/// Matching of multiple cases, e.g., `match x { 5 => 'a', 6 => 'b' }`.
|
||||
Match {
|
||||
/// The expression being matched upon.
|
||||
matcher: Box<Expr>,
|
||||
|
||||
/// The possible cases of the `match` expression.
|
||||
cases: Vec<(Pattern, Expr)>,
|
||||
},
|
||||
|
||||
/// Syntax sugar for matching on booleans, e.g., `if foo then bar else baz`.
|
||||
If {
|
||||
subject: Box<Expr>,
|
||||
iftrue: Box<Expr>,
|
||||
iffalse: Box<Expr>,
|
||||
},
|
||||
|
||||
/// Struct initialization, e.g., `Vector { pointer: xyz, length: 12 }`.
|
||||
StructInit {
|
||||
name: String,
|
||||
/// Record initialization, e.g., `{ pointer: xyz, length: 12 }`.
|
||||
Record {
|
||||
/// The elements of the record.
|
||||
elements: Vec<(String, Expr)>,
|
||||
},
|
||||
|
||||
/// Anonymous functions, e.g., `fn x -> x + 1`.
|
||||
Lambda {
|
||||
/// Arguments to the lambda; multiple of these are equivalent to stacking lambdas by
|
||||
/// currying.
|
||||
arguments: Vec<Pattern>,
|
||||
|
||||
/// The result of the lambda.
|
||||
result: Box<Expr>,
|
||||
},
|
||||
|
||||
|
@ -135,11 +167,20 @@ pub enum Expr {
|
|||
VariableReference(Vec<String>),
|
||||
|
||||
/// Dot subscripts, e.g., `foo.bar`.
|
||||
DotSubscript { value: Box<Expr>, subscript: String },
|
||||
DotSubscript {
|
||||
/// The left side of the subscript.
|
||||
value: Box<Expr>,
|
||||
|
||||
/// The right side of the subscript; this is only allowed to be a single word.
|
||||
subscript: String,
|
||||
},
|
||||
|
||||
/// Bracket subscripts, e.g., `foo[bar]`.
|
||||
BracketSubscript {
|
||||
/// The left side of the subscript.
|
||||
value: Box<Expr>,
|
||||
|
||||
/// The right side of the subscript.
|
||||
subscript: Box<Expr>,
|
||||
},
|
||||
|
||||
|
@ -154,8 +195,11 @@ pub enum Type {
|
|||
|
||||
/// `List Int`
|
||||
Application {
|
||||
// TODO: is this right?
|
||||
function: Box<Expr>,
|
||||
/// The function being applied. This must be a generic type.
|
||||
function: Box<Type>,
|
||||
|
||||
/// The expression given as an argument to the type. This can be any expression, to allow
|
||||
/// const generics; in most cases, though, it should be just a normal type.
|
||||
expression: Box<Expr>,
|
||||
},
|
||||
|
||||
|
@ -174,9 +218,11 @@ pub enum Pattern {
|
|||
|
||||
/// `a: String`
|
||||
TypeAnnotated {
|
||||
/// The pattern being annotated.
|
||||
pat: Box<Pattern>,
|
||||
// Note that types are expressions, to simplify parsing.
|
||||
typ: Box<Expr>,
|
||||
|
||||
/// The type that `pat` is being asserted to have.
|
||||
typ: Box<Type>,
|
||||
},
|
||||
|
||||
/// `Foo`
|
||||
|
|
277
axc/src/main.rs
277
axc/src/main.rs
|
@ -1,3 +1,276 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
//! AlexScript CLI.
|
||||
|
||||
use std::{error::Error, fmt::Display, fs::File, io::Write, process::exit, str::FromStr};
|
||||
|
||||
use axc::{
|
||||
ast2ir::ast2ir,
|
||||
backends,
|
||||
parser::{parser, ParserError},
|
||||
typeck::typeck,
|
||||
};
|
||||
use clap::Parser;
|
||||
|
||||
/// Optimization levels.
|
||||
#[derive(Debug)]
|
||||
enum Optimization {
|
||||
Numeric(usize),
|
||||
Size,
|
||||
Debugging,
|
||||
Speed,
|
||||
}
|
||||
|
||||
impl Display for Optimization {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Optimization::Numeric(n) => format!("{}", n),
|
||||
Optimization::Size => "z".to_string(),
|
||||
Optimization::Debugging => "g".to_string(),
|
||||
Optimization::Speed => "fast".to_string(),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Optimization {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
use Optimization::*;
|
||||
|
||||
let levels = [
|
||||
(
|
||||
"0",
|
||||
"Disable all optimizations, for best results when debugging.",
|
||||
Numeric(0),
|
||||
),
|
||||
(
|
||||
"1",
|
||||
"Enable basic optimizations, without significantly affecting build times.",
|
||||
Numeric(1),
|
||||
),
|
||||
(
|
||||
"2",
|
||||
"Enable standard optimizations for release builds.",
|
||||
Numeric(2),
|
||||
),
|
||||
(
|
||||
"3",
|
||||
"Optimize for speed at the expense of size.",
|
||||
Numeric(3),
|
||||
),
|
||||
(
|
||||
"z",
|
||||
"Aggresively optimize for size rather than speed.",
|
||||
Size,
|
||||
),
|
||||
(
|
||||
"g",
|
||||
"Enable some optimizations that do not interfere with debugging.",
|
||||
Debugging,
|
||||
),
|
||||
(
|
||||
"fast",
|
||||
"Optimize for speed at the expense of strict standards compliance.",
|
||||
Speed,
|
||||
),
|
||||
];
|
||||
|
||||
if s == "list" {
|
||||
eprintln!(
|
||||
"{}",
|
||||
levels
|
||||
.iter()
|
||||
.map(|(name, description, _)| format!("\n -O{name} : {description}"))
|
||||
.fold("optimization levels:".to_string(), |s, line| s + &line)
|
||||
);
|
||||
exit(1)
|
||||
} else {
|
||||
levels
|
||||
.into_iter()
|
||||
.find(|(name, _, _)| name == &s)
|
||||
.map(|(_, _, opt)| opt)
|
||||
.ok_or_else(|| "use -Olist to list".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Targets for compiling to.
|
||||
#[derive(Debug)]
|
||||
enum Target {
|
||||
// -------------------- C generation --------------------
|
||||
|
||||
// Highest priority codegen, since it allows us to compile to the vast majority of possible
|
||||
// targets.
|
||||
/// Directly generate C source code.
|
||||
CSource,
|
||||
|
||||
/// Pass generated C code into the system C compiler, and emit an assembly code file.
|
||||
Assembly,
|
||||
|
||||
/// Pass Assembly code into the system assembler, and emit an object file.
|
||||
ObjectFile,
|
||||
|
||||
/// Link generated object files using the system linker, and generate an executable file.
|
||||
Executable,
|
||||
|
||||
/// Link generated object files using the system linker, and generate a shared object file.
|
||||
SharedObject,
|
||||
|
||||
// -------------------- GPU generation --------------------
|
||||
|
||||
// Medium-priority codegen, since GPUs typically can't be portably programmed in C and GPU
|
||||
// programming is a minor goal of the language.
|
||||
/// Directly generate SPIR-V code.
|
||||
Spirv,
|
||||
|
||||
// TODO: add more GPU targets derived from spirv.
|
||||
|
||||
// -------------------- WebAssembly generation --------------------
|
||||
|
||||
// Very low-priority codegen, since efficient WebAssembly can be generated from C.
|
||||
/// Pass generated C code into the WebAssembly compiler, and generate WebAssembly text format
|
||||
/// code. At some point this might get replaced with direct WAT generation.
|
||||
Wat,
|
||||
|
||||
/// Pass generated WAT code into a WebAssembly compiler, and generate WebAssembly binary format
|
||||
/// code.
|
||||
Wasm,
|
||||
|
||||
// -------------------- Other language generation --------------------
|
||||
/// Directly generate Lua code.
|
||||
// Medium-priority codegen, since Lua is the only type of code that runs in e.g. plugins and
|
||||
// scripts in some software, and it is difficult to generate efficient Lua without direct
|
||||
// support from the compiler.
|
||||
Lua,
|
||||
|
||||
/// Directly generate Python code.
|
||||
// Low-priority codegen; the same situation as Lua, but situations that require Python code with
|
||||
// no alternatives are much less common than situations that require Lua code with no
|
||||
// alternatives.
|
||||
Python,
|
||||
|
||||
/// Directly generate Go code.
|
||||
// Extremely low-priority codegen; almost no valid use cases.
|
||||
Go,
|
||||
|
||||
/// Directly generate Ada code.
|
||||
// Currently zero-priority codegen; no valid use cases whatsoever as far as I (Alex) can
|
||||
// determine.
|
||||
Ada,
|
||||
}
|
||||
|
||||
impl Display for Target {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Target::CSource => "c",
|
||||
Target::Assembly => "asm",
|
||||
Target::ObjectFile => "obj",
|
||||
Target::Executable => "exe",
|
||||
Target::SharedObject => "so",
|
||||
Target::Spirv => "spirv",
|
||||
Target::Wat => "wat",
|
||||
Target::Wasm => "wasm",
|
||||
Target::Lua => "lua",
|
||||
Target::Python => "py",
|
||||
Target::Go => "go",
|
||||
Target::Ada => "ada",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Target {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
use Target::*;
|
||||
|
||||
let targets = [
|
||||
("c", "C source code.", CSource),
|
||||
("asm", "Assembly code.", Assembly),
|
||||
("obj", "Object file.", ObjectFile),
|
||||
("exe", "Executable binary file.", Executable),
|
||||
("so", "Shared object file.", SharedObject),
|
||||
("spirv", "Spir-V source code.", Spirv),
|
||||
("wat", "WebAssembly text format code.", Wat),
|
||||
("wasm", "WebAssembly binary format file.", Wasm),
|
||||
("lua", "Lua source code.", Lua),
|
||||
("python", "Python source code.", Python),
|
||||
("go", "Go source code.", Go),
|
||||
("ada", "Ada source code.", Ada),
|
||||
];
|
||||
|
||||
if s == "list" {
|
||||
eprintln!(
|
||||
"{}",
|
||||
targets
|
||||
.iter()
|
||||
.map(|(name, description, _)| format!("\n -t{name} : {description}"))
|
||||
.fold("targets:".to_string(), |s, line| s + &line)
|
||||
);
|
||||
exit(1)
|
||||
} else {
|
||||
targets
|
||||
.into_iter()
|
||||
.find(|(name, _, _)| name == &s)
|
||||
.map(|(_, _, target)| target)
|
||||
.ok_or_else(|| "use -tlist to list".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The AlexScript compiler.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version = "0.1.0")]
|
||||
struct Args {
|
||||
/// Output file to generate.
|
||||
#[clap(short = 'o')]
|
||||
output: Option<String>,
|
||||
|
||||
/// Optimization level. Use `-Olist` to list possible optimization levels.
|
||||
#[clap(short = 'O', default_value_t = Optimization::Numeric(0))]
|
||||
optimization: Optimization,
|
||||
|
||||
/// Generate debug information in resultant code, if possible.
|
||||
#[clap(short = 'g')]
|
||||
debug_syms: bool,
|
||||
|
||||
/// Target to generate code for. Use `-tlist` to list possible targets.
|
||||
#[clap(short = 't', default_value_t = Target::Executable)]
|
||||
target: Target,
|
||||
|
||||
/// The source file to compile.
|
||||
source_file: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
fn main_e() -> Result<(), Box<dyn Error>> {
|
||||
let args = Args::parse();
|
||||
|
||||
let source = std::fs::read_to_string(&args.source_file)?;
|
||||
let ast = chumsky::Parser::parse(&parser(), source).map_err(ParserError)?;
|
||||
typeck(&ast)?;
|
||||
|
||||
let ir = ast2ir(ast);
|
||||
let c = backends::c::generate_c(&ir);
|
||||
|
||||
let mut out_file = File::create("out.c")?;
|
||||
write!(out_file, "{}", c)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
match main_e() {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
eprintln!("axc fatal error: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
28
axc/src/parser.rs
Normal file
28
axc/src/parser.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
//! AlexScript parser.
|
||||
|
||||
use std::{error::Error, fmt::Display};
|
||||
|
||||
use chumsky::{
|
||||
prelude::{filter, Simple},
|
||||
Parser,
|
||||
};
|
||||
|
||||
/// Adapter to make `chumsky`'s parser errors usable as standard Rust errors.
|
||||
#[derive(Debug)]
|
||||
pub struct ParserError(pub Vec<Simple<char>>);
|
||||
|
||||
impl Display for ParserError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for e in &self.0 {
|
||||
write!(f, "{}", e)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParserError {}
|
||||
|
||||
/// Parser for AlexScript code.
|
||||
pub fn parser() -> impl Parser<char, crate::SyntaxTree, Error = Simple<char>> {
|
||||
filter(|c: &char| c.is_numeric()).map(|_| todo!())
|
||||
}
|
22
axc/src/typeck.rs
Normal file
22
axc/src/typeck.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
//! Type checker.
|
||||
|
||||
use std::{error::Error, fmt::Display};
|
||||
|
||||
use crate::SyntaxTree;
|
||||
|
||||
/// A compile-time type error from the user's source code.
|
||||
#[derive(Debug)]
|
||||
pub struct TypeError;
|
||||
|
||||
impl Display for TypeError {
|
||||
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for TypeError {}
|
||||
|
||||
/// Type-checks the syntax tree.
|
||||
pub fn typeck(_: &SyntaxTree) -> Result<(), TypeError> {
|
||||
todo!()
|
||||
}
|
Loading…
Reference in a new issue