Write main file

This commit is contained in:
Alex Bethel 2022-08-04 21:31:35 -05:00
parent b5de11a7fa
commit e42936fb4a
9 changed files with 416 additions and 20 deletions

View file

@ -34,7 +34,7 @@ def fibPrompt
= do { = do {
print "Enter a number: "; print "Enter a number: ";
num <- read <$> getLine; num <- read <$> getLine;
sequence_ (print . fibonacci <$> [0 .. num]); sequence_ (print . fibonacci <$> (0 .. num));
}; };
// Program entry point. // Program entry point.

10
axc/src/ast2ir.rs Normal file
View 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
View 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
View file

@ -0,0 +1,3 @@
//! Different back-ends for code generation.
pub mod c;

5
axc/src/ir.rs Normal file
View file

@ -0,0 +1,5 @@
//! Intermediate code representation.
/// The IR tree representing the whole program.
#[derive(Debug)]
pub struct IR;

View file

@ -2,6 +2,14 @@
//! //!
//! AlexScript is a based programming language, for based people. //! 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; use num_bigint::BigUint;
/// A concrete syntax tree. This represents the full content of an AlexScript program, including all /// A concrete syntax tree. This represents the full content of an AlexScript program, including all
@ -88,46 +96,70 @@ pub struct TypeConstructor {
/// Expressions. /// Expressions.
pub enum Expr { pub enum Expr {
/// Unary operators, e.g., `-5`. /// 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`. /// Binary operators, e.g., `5 + 5`.
BinaryOp { BinaryOp {
/// The text of the operator.
kind: String, kind: String,
/// The left side of the operator.
left: Box<Expr>, left: Box<Expr>,
/// The right side of the operator.
right: Box<Expr>, right: Box<Expr>,
}, },
/// Function application, e.g., `sin x`. /// Function application, e.g., `sin x`.
Application { 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>, func: Box<Expr>,
/// The argument to which the function is being applied.
argument: Box<Expr>, argument: Box<Expr>,
}, },
/// Defining of temporary variables, e.g., `let x = 5 in x + x`. /// 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' }`. /// Matching of multiple cases, e.g., `match x { 5 => 'a', 6 => 'b' }`.
Match { Match {
/// The expression being matched upon.
matcher: Box<Expr>, matcher: Box<Expr>,
/// The possible cases of the `match` expression.
cases: Vec<(Pattern, Expr)>, cases: Vec<(Pattern, Expr)>,
}, },
/// Syntax sugar for matching on booleans, e.g., `if foo then bar else baz`. /// Record initialization, e.g., `{ pointer: xyz, length: 12 }`.
If { Record {
subject: Box<Expr>, /// The elements of the record.
iftrue: Box<Expr>,
iffalse: Box<Expr>,
},
/// Struct initialization, e.g., `Vector { pointer: xyz, length: 12 }`.
StructInit {
name: String,
elements: Vec<(String, Expr)>, elements: Vec<(String, Expr)>,
}, },
/// Anonymous functions, e.g., `fn x -> x + 1`. /// Anonymous functions, e.g., `fn x -> x + 1`.
Lambda { Lambda {
/// Arguments to the lambda; multiple of these are equivalent to stacking lambdas by
/// currying.
arguments: Vec<Pattern>, arguments: Vec<Pattern>,
/// The result of the lambda.
result: Box<Expr>, result: Box<Expr>,
}, },
@ -135,11 +167,20 @@ pub enum Expr {
VariableReference(Vec<String>), VariableReference(Vec<String>),
/// Dot subscripts, e.g., `foo.bar`. /// 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]`. /// Bracket subscripts, e.g., `foo[bar]`.
BracketSubscript { BracketSubscript {
/// The left side of the subscript.
value: Box<Expr>, value: Box<Expr>,
/// The right side of the subscript.
subscript: Box<Expr>, subscript: Box<Expr>,
}, },
@ -154,8 +195,11 @@ pub enum Type {
/// `List Int` /// `List Int`
Application { Application {
// TODO: is this right? /// The function being applied. This must be a generic type.
function: Box<Expr>, 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>, expression: Box<Expr>,
}, },
@ -174,9 +218,11 @@ pub enum Pattern {
/// `a: String` /// `a: String`
TypeAnnotated { TypeAnnotated {
/// The pattern being annotated.
pat: Box<Pattern>, 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` /// `Foo`

View file

@ -1,3 +1,276 @@
fn main() { //! AlexScript CLI.
println!("Hello, world!");
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
View 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
View 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!()
}