Write main
file
This commit is contained in:
parent
b5de11a7fa
commit
e42936fb4a
|
@ -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
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.
|
//! 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`
|
||||||
|
|
277
axc/src/main.rs
277
axc/src/main.rs
|
@ -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
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