Merge pull request #33 from txpipe/lang

Lang
This commit is contained in:
Lucas
2022-09-26 17:10:30 -04:00
committed by GitHub
15 changed files with 2758 additions and 79 deletions

View File

@@ -7,6 +7,15 @@ use clap::{Parser, Subcommand};
#[clap(version, about, long_about = None)]
#[clap(propagate_version = true)]
pub enum Args {
/// Build an aiken project
Build,
/// Start a development server
Dev,
/// Create a new aiken project
New {
/// Project name
name: PathBuf,
},
/// A subcommand for working with transactions
#[clap(subcommand)]
Tx(TxCommand),
@@ -50,39 +59,56 @@ pub enum TxCommand {
/// Commands for working with Untyped Plutus Core
#[derive(Subcommand)]
pub enum UplcCommand {
/// Evaluate an Untyped Plutus Core program
Eval {
script: PathBuf,
#[clap(short, long)]
flat: bool,
/// Arguments to pass to the uplc program
args: Vec<String>,
},
/// Encode textual Untyped Plutus Core to flat bytes
Flat {
/// Textual Untyped Plutus Core file
input: PathBuf,
#[clap(short, long)]
print: bool,
/// Output file name
#[clap(short, long)]
out: Option<String>,
#[clap(short, long)]
cbor_hex: bool,
},
/// Decode flat bytes to textual Untyped Plutus Core
Unflat {
input: PathBuf,
/// Print output instead of saving to file
#[clap(short, long)]
print: bool,
#[clap(short, long)]
out: Option<String>,
#[clap(short, long)]
cbor_hex: bool,
},
/// Format an Untyped Plutus Core program
Fmt {
/// Textual Untyped Plutus Core file
input: PathBuf,
/// Print output instead of saving to file
#[clap(short, long)]
print: bool,
},
/// Evaluate an Untyped Plutus Core program
Eval {
script: PathBuf,
/// Decode flat bytes to textual Untyped Plutus Core
Unflat {
/// Flat encoded Untyped Plutus Core file
input: PathBuf,
/// Output file name
#[clap(short, long)]
flat: bool,
/// Arguments to pass to the uplc program
args: Vec<String>,
out: Option<String>,
/// Print output instead of saving to file
#[clap(short, long)]
print: bool,
#[clap(short, long)]
cbor_hex: bool,
},
}

View File

@@ -23,6 +23,31 @@ fn main() -> anyhow::Result<()> {
let args = Args::default();
match args {
Args::Build => {
// 1. load and parse modules
// * lib - contains modules, types, and functions
// * contracts - contains validators
// * scripts - contains native scripts dsl
// 2. type check everything
// 3. generate uplc and policy/address if relevant
todo!()
}
Args::Dev => {
// launch a development server
// this should allow people to test
// their contracts over http
todo!()
}
Args::New { name } => {
if !name.exists() {
fs::create_dir_all(name.join("lib"))?;
fs::create_dir_all(name.join("policies"))?;
fs::create_dir_all(name.join("scripts"))?;
}
}
Args::Tx(tx_cmd) => match tx_cmd {
TxCommand::Simulate {
input,
@@ -161,6 +186,7 @@ fn main() -> anyhow::Result<()> {
}
}
}
UplcCommand::Fmt { input, print } => {
let code = std::fs::read_to_string(&input)?;

15
crates/lang/Cargo.toml Normal file
View File

@@ -0,0 +1,15 @@
[package]
name = "aiken-lang"
version = "0.0.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chumsky = "0.8.0"
internment = "0.7.0"
miette = "5.2.0"
vec1 = "1.8.0"
[dev-dependencies]
pretty_assertions = "1.3.0"

547
crates/lang/src/ast.rs Normal file
View File

@@ -0,0 +1,547 @@
use std::{collections::HashMap, fmt, ops::Range, sync::Arc};
use internment::Intern;
use crate::{
expr::{TypedExpr, UntypedExpr},
tipo::{self, PatternConstructor, Type, ValueConstructor},
};
pub type TypedModule = Module<tipo::Module, TypedDefinition>;
pub type UntypedModule = Module<(), UntypedDefinition>;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ModuleKind {
Contract,
Lib,
Script,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Module<Info, Definitions> {
pub name: Vec<String>,
pub docs: Vec<String>,
pub type_info: Info,
pub definitions: Vec<Definitions>,
pub kind: ModuleKind,
}
pub type TypedDefinition = Definition<Arc<Type>, TypedExpr, String, String>;
pub type UntypedDefinition = Definition<(), UntypedExpr, (), ()>;
#[derive(Debug, Clone, PartialEq)]
pub enum Definition<T, Expr, ConstantRecordTag, PackageName> {
Fn {
arguments: Vec<Arg<T>>,
body: Expr,
doc: Option<String>,
location: Span,
name: String,
public: bool,
return_annotation: Option<Annotation>,
return_type: T,
},
TypeAlias {
alias: String,
annotation: Annotation,
doc: Option<String>,
location: Span,
parameters: Vec<String>,
public: bool,
tipo: T,
},
DataType {
constructors: Vec<RecordConstructor<T>>,
doc: Option<String>,
location: Span,
name: String,
opaque: bool,
parameters: Vec<String>,
public: bool,
typed_parameters: Vec<T>,
},
Use {
as_name: Option<String>,
location: Span,
module: Vec<String>,
package: PackageName,
unqualified: Vec<UnqualifiedImport>,
},
ModuleConstant {
doc: Option<String>,
location: Span,
public: bool,
name: String,
annotation: Option<Annotation>,
value: Box<Constant<T, ConstantRecordTag>>,
tipo: T,
},
}
pub type TypedConstant = Constant<Arc<Type>, String>;
pub type UntypedConstant = Constant<(), ()>;
#[derive(Debug, Clone, PartialEq)]
pub enum Constant<T, RecordTag> {
Int {
location: Span,
value: String,
},
String {
location: Span,
value: String,
},
Pair {
location: Span,
elements: Vec<Self>,
},
List {
location: Span,
elements: Vec<Self>,
tipo: T,
},
Record {
location: Span,
module: Option<String>,
name: String,
args: Vec<CallArg<Self>>,
tag: RecordTag,
tipo: T,
field_map: Option<FieldMap>,
},
ByteString {
location: Span,
// segments: Vec<BitStringSegment<Self, T>>,
},
Var {
location: Span,
module: Option<String>,
name: String,
constructor: Option<Box<ValueConstructor>>,
tipo: T,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CallArg<A> {
pub label: Option<String>,
pub location: Span,
pub value: A,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FieldMap {
pub arity: usize,
pub fields: HashMap<String, usize>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RecordConstructor<T> {
pub location: Span,
pub name: String,
pub arguments: Vec<RecordConstructorArg<T>>,
pub documentation: Option<String>,
pub sugar: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RecordConstructorArg<T> {
pub label: Option<String>,
// ast
pub annotation: Annotation,
pub location: Span,
pub tipo: T,
pub doc: Option<String>,
}
pub type UntypedArg = Arg<()>;
#[derive(Debug, Clone, PartialEq)]
pub struct Arg<T> {
pub arg_name: ArgName,
pub location: Span,
pub annotation: Option<Annotation>,
pub tipo: T,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ArgName {
Discard {
name: String,
location: Span,
},
LabeledDiscard {
label: String,
name: String,
location: Span,
},
Named {
name: String,
location: Span,
},
NamedLabeled {
name: String,
label: String,
location: Span,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnqualifiedImport {
pub location: Span,
pub name: String,
pub as_name: Option<String>,
pub layer: Layer,
}
// TypeAst
#[derive(Debug, Clone, PartialEq)]
pub enum Annotation {
Constructor {
location: Span,
module: Option<String>,
name: String,
arguments: Vec<Self>,
},
Fn {
location: Span,
arguments: Vec<Self>,
ret: Box<Self>,
},
Var {
location: Span,
name: String,
},
Tuple {
location: Span,
elems: Vec<Self>,
},
Hole {
location: Span,
name: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Layer {
Value,
Type,
}
impl Default for Layer {
fn default() -> Self {
Layer::Value
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BinOp {
// Boolean logic
And,
Or,
// Equality
Eq,
NotEq,
// Order comparison
LtInt,
LtEqInt,
GtEqInt,
GtInt,
// Maths
AddInt,
SubInt,
MultInt,
DivInt,
ModInt,
}
pub type UntypedPattern = Pattern<(), ()>;
pub type TypedPattern = Pattern<PatternConstructor, Arc<Type>>;
#[derive(Debug, Clone, PartialEq)]
pub enum Pattern<Constructor, Type> {
Int {
location: Span,
value: String,
},
Float {
location: Span,
value: String,
},
String {
location: Span,
value: String,
},
/// The creation of a variable.
/// e.g. `assert [this_is_a_var, .._] = x`
Var {
location: Span,
name: String,
},
/// A reference to a variable in a bit string. This is always a variable
/// being used rather than a new variable being assigned.
VarUsage {
location: Span,
name: String,
tipo: Type,
},
/// A name given to a sub-pattern using the `as` keyword.
/// e.g. `assert #(1, [_, _] as the_list) = x`
Assign {
name: String,
location: Span,
pattern: Box<Self>,
},
/// A pattern that binds to any value but does not assign a variable.
/// Always starts with an underscore.
Discard {
name: String,
location: Span,
},
List {
location: Span,
elements: Vec<Self>,
tail: Option<Box<Self>>,
},
/// The constructor for a custom type. Starts with an uppercase letter.
Constructor {
location: Span,
name: String,
arguments: Vec<CallArg<Self>>,
module: Option<String>,
constructor: Constructor,
with_spread: bool,
tipo: Type,
},
Tuple {
location: Span,
elems: Vec<Self>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AssignmentKind {
Let,
Assert,
}
pub type MultiPattern<PatternConstructor, Type> = Vec<Pattern<PatternConstructor, Type>>;
pub type UntypedMultiPattern = MultiPattern<(), ()>;
pub type TypedMultiPattern = MultiPattern<PatternConstructor, Arc<Type>>;
pub type TypedClause = Clause<TypedExpr, PatternConstructor, Arc<Type>, String>;
pub type UntypedClause = Clause<UntypedExpr, (), (), ()>;
#[derive(Debug, Clone, PartialEq)]
pub struct Clause<Expr, PatternConstructor, Type, RecordTag> {
pub location: Span,
pub pattern: MultiPattern<PatternConstructor, Type>,
pub alternative_patterns: Vec<MultiPattern<PatternConstructor, Type>>,
pub guard: Option<ClauseGuard<Type, RecordTag>>,
pub then: Expr,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ClauseGuard<Type, RecordTag> {
Equals {
location: Span,
left: Box<Self>,
right: Box<Self>,
},
NotEquals {
location: Span,
left: Box<Self>,
right: Box<Self>,
},
GtInt {
location: Span,
left: Box<Self>,
right: Box<Self>,
},
GtEqInt {
location: Span,
left: Box<Self>,
right: Box<Self>,
},
LtInt {
location: Span,
left: Box<Self>,
right: Box<Self>,
},
LtEqInt {
location: Span,
left: Box<Self>,
right: Box<Self>,
},
Or {
location: Span,
left: Box<Self>,
right: Box<Self>,
},
And {
location: Span,
left: Box<Self>,
right: Box<Self>,
},
Var {
location: Span,
tipo: Type,
name: String,
},
TupleIndex {
location: Span,
index: u64,
tipo: Type,
tuple: Box<Self>,
},
Constant(Constant<Type, RecordTag>),
}
pub struct TypedRecordUpdateArg {
pub label: String,
pub location: Span,
pub value: TypedExpr,
pub index: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub struct UntypedRecordUpdateArg {
pub label: String,
// pub location: SrcSpan,
pub value: UntypedExpr,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RecordUpdateSpread {
pub base: Box<UntypedExpr>,
pub location: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TodoKind {
Keyword,
EmptyFunction,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct SrcId(Intern<Vec<String>>);
impl SrcId {
#[cfg(test)]
pub fn empty() -> Self {
SrcId(Intern::new(Vec::new()))
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Span {
pub src: SrcId,
pub start: usize,
pub end: usize,
}
impl Span {
#[cfg(test)]
pub fn empty() -> Self {
use chumsky::Span;
Self::new(SrcId::empty(), 0..0)
}
pub fn src(&self) -> SrcId {
self.src
}
pub fn range(&self) -> Range<usize> {
use chumsky::Span;
self.start()..self.end()
}
pub fn union(self, other: Self) -> Self {
use chumsky::Span;
assert_eq!(
self.src, other.src,
"attempted to union spans with different sources"
);
Self {
start: self.start().min(other.start()),
end: self.end().max(other.end()),
..self
}
}
}
impl fmt::Debug for Span {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}:{:?}", self.src, self.range())
}
}
impl chumsky::Span for Span {
type Context = SrcId;
type Offset = usize;
fn new(context: Self::Context, range: Range<Self::Offset>) -> Self {
assert!(range.start <= range.end);
Self {
src: context,
start: range.start,
end: range.end,
}
}
fn context(&self) -> Self::Context {
self.src
}
fn start(&self) -> Self::Offset {
self.start
}
fn end(&self) -> Self::Offset {
self.end
}
}

4
crates/lang/src/build.rs Normal file
View File

@@ -0,0 +1,4 @@
pub enum Origin {
Src,
Test,
}

110
crates/lang/src/error.rs Normal file
View File

@@ -0,0 +1,110 @@
use std::{collections::HashSet, fmt};
use crate::{ast::Span, token::Token};
#[derive(Debug)]
pub struct ParseError {
kind: ErrorKind,
span: Span,
#[allow(dead_code)]
while_parsing: Option<(Span, &'static str)>,
expected: HashSet<Pattern>,
label: Option<&'static str>,
}
impl ParseError {
pub fn merge(mut self, other: Self) -> Self {
// TODO: Use HashSet
for expected in other.expected.into_iter() {
self.expected.insert(expected);
}
self
}
}
impl PartialEq for ParseError {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind && self.span == other.span && self.label == other.label
}
}
impl<T: Into<Pattern>> chumsky::Error<T> for ParseError {
type Span = Span;
type Label = &'static str;
fn expected_input_found<Iter: IntoIterator<Item = Option<T>>>(
span: Self::Span,
expected: Iter,
found: Option<T>,
) -> Self {
Self {
kind: found
.map(Into::into)
.map(ErrorKind::Unexpected)
.unwrap_or(ErrorKind::UnexpectedEnd),
span,
while_parsing: None,
expected: expected
.into_iter()
.map(|x| x.map(Into::into).unwrap_or(Pattern::End))
.collect(),
label: None,
}
}
fn with_label(mut self, label: Self::Label) -> Self {
self.label.get_or_insert(label);
self
}
fn merge(self, other: Self) -> Self {
ParseError::merge(self, other)
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum ErrorKind {
UnexpectedEnd,
Unexpected(Pattern),
Unclosed {
start: Pattern,
before_span: Span,
before: Option<Pattern>,
},
NoEndBranch,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum Pattern {
Char(char),
Token(Token),
Literal,
TypeIdent,
TermIdent,
End,
}
impl From<char> for Pattern {
fn from(c: char) -> Self {
Self::Char(c)
}
}
impl From<Token> for Pattern {
fn from(tok: Token) -> Self {
Self::Token(tok)
}
}
impl fmt::Display for Pattern {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Pattern::Token(token) => write!(f, "{}", token),
Pattern::Char(c) => write!(f, "{:?}", c),
Pattern::Literal => write!(f, "literal"),
Pattern::TypeIdent => write!(f, "type name"),
Pattern::TermIdent => write!(f, "identifier"),
Pattern::End => write!(f, "end of input"),
}
}
}

324
crates/lang/src/expr.rs Normal file
View File

@@ -0,0 +1,324 @@
use std::sync::Arc;
use vec1::Vec1;
use crate::{
ast::{
Annotation, Arg, AssignmentKind, BinOp, CallArg, Clause, Pattern, RecordUpdateSpread, Span,
TodoKind, TypedRecordUpdateArg, UntypedRecordUpdateArg,
},
tipo::{ModuleValueConstructor, PatternConstructor, Type, ValueConstructor},
};
pub enum TypedExpr {
Int {
location: Span,
tipo: Arc<Type>,
value: String,
},
Float {
location: Span,
tipo: Arc<Type>,
value: String,
},
String {
location: Span,
tipo: Arc<Type>,
value: String,
},
Sequence {
location: Span,
expressions: Vec<Self>,
},
/// A chain of pipe expressions.
/// By this point the type checker has expanded it into a series of
/// assignments and function calls, but we still have a Pipeline AST node as
/// even though it is identical to `Sequence` we want to use different
/// locations when showing it in error messages, etc.
Pipeline {
location: Span,
expressions: Vec<Self>,
},
Var {
location: Span,
constructor: ValueConstructor,
name: String,
},
Fn {
location: Span,
tipo: Arc<Type>,
is_capture: bool,
args: Vec<Arg<Arc<Type>>>,
body: Box<Self>,
return_annotation: Option<Annotation>,
},
List {
location: Span,
tipo: Arc<Type>,
elements: Vec<Self>,
tail: Option<Box<Self>>,
},
Call {
location: Span,
tipo: Arc<Type>,
fun: Box<Self>,
args: Vec<CallArg<Self>>,
},
BinOp {
location: Span,
tipo: Arc<Type>,
name: BinOp,
left: Box<Self>,
right: Box<Self>,
},
Assignment {
location: Span,
tipo: Arc<Type>,
value: Box<Self>,
pattern: Pattern<PatternConstructor, Arc<Type>>,
kind: AssignmentKind,
},
Try {
location: Span,
tipo: Arc<Type>,
value: Box<Self>,
then: Box<Self>,
pattern: Pattern<PatternConstructor, Arc<Type>>,
},
When {
location: Span,
tipo: Arc<Type>,
subjects: Vec<Self>,
clauses: Vec<Clause<Self, PatternConstructor, Arc<Type>, String>>,
},
RecordAccess {
location: Span,
tipo: Arc<Type>,
label: String,
index: u64,
record: Box<Self>,
},
ModuleSelect {
location: Span,
tipo: Arc<Type>,
label: String,
module_name: String,
module_alias: String,
constructor: ModuleValueConstructor,
},
Tuple {
location: Span,
tipo: Arc<Type>,
elems: Vec<Self>,
},
TupleIndex {
location: Span,
tipo: Arc<Type>,
index: u64,
tuple: Box<Self>,
},
Todo {
location: Span,
label: Option<String>,
tipo: Arc<Type>,
},
RecordUpdate {
location: Span,
tipo: Arc<Type>,
spread: Box<Self>,
args: Vec<TypedRecordUpdateArg>,
},
Negate {
location: Span,
value: Box<Self>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum UntypedExpr {
Int {
location: Span,
value: String,
},
Float {
location: Span,
value: String,
},
String {
location: Span,
value: String,
},
Sequence {
location: Span,
expressions: Vec<Self>,
},
Var {
location: Span,
name: String,
},
Fn {
location: Span,
is_capture: bool,
arguments: Vec<Arg<()>>,
body: Box<Self>,
return_annotation: Option<Annotation>,
},
List {
location: Span,
elements: Vec<Self>,
tail: Option<Box<Self>>,
},
Call {
location: Span,
fun: Box<Self>,
arguments: Vec<CallArg<Self>>,
},
BinOp {
location: Span,
name: BinOp,
left: Box<Self>,
right: Box<Self>,
},
PipeLine {
expressions: Vec1<Self>,
},
Assignment {
location: Span,
value: Box<Self>,
pattern: Pattern<(), ()>,
kind: AssignmentKind,
annotation: Option<Annotation>,
},
Try {
location: Span,
value: Box<Self>,
pattern: Pattern<(), ()>,
then: Box<Self>,
annotation: Option<Annotation>,
},
Case {
location: Span,
subjects: Vec<Self>,
clauses: Vec<Clause<Self, (), (), ()>>,
},
FieldAccess {
location: Span,
label: String,
container: Box<Self>,
},
Tuple {
location: Span,
elems: Vec<Self>,
},
TupleIndex {
location: Span,
index: u64,
tuple: Box<Self>,
},
Todo {
kind: TodoKind,
location: Span,
label: Option<String>,
},
RecordUpdate {
location: Span,
constructor: Box<Self>,
spread: RecordUpdateSpread,
arguments: Vec<UntypedRecordUpdateArg>,
},
Negate {
location: Span,
value: Box<Self>,
},
}
impl UntypedExpr {
pub fn append_in_sequence(self, next: Self) -> Self {
let location = Span {
start: self.location().start,
end: next.location().end,
..self.location()
};
match self {
Self::Sequence {
mut expressions, ..
} => {
expressions.push(next);
Self::Sequence {
location,
expressions,
}
}
_ => Self::Sequence {
location,
expressions: vec![self, next],
},
}
}
pub fn location(&self) -> Span {
match self {
Self::Try { then, .. } => then.location(),
Self::PipeLine { expressions, .. } => expressions.last().location(),
Self::Fn { location, .. }
| Self::Var { location, .. }
| Self::Int { location, .. }
| Self::Todo { location, .. }
| Self::Case { location, .. }
| Self::Call { location, .. }
| Self::List { location, .. }
| Self::Float { location, .. }
| Self::BinOp { location, .. }
| Self::Tuple { location, .. }
| Self::String { location, .. }
| Self::Assignment { location, .. }
| Self::TupleIndex { location, .. }
| Self::FieldAccess { location, .. }
| Self::RecordUpdate { location, .. }
| Self::Negate { location, .. } => *location,
Self::Sequence {
location,
expressions,
..
} => expressions.last().map(Self::location).unwrap_or(*location),
}
}
}

164
crates/lang/src/lexer.rs Normal file
View File

@@ -0,0 +1,164 @@
use chumsky::prelude::*;
use crate::{ast::Span, error::ParseError, token::Token};
pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = ParseError> {
let int = text::int(10).map(|value| Token::Int { value });
let op = choice((
just("==").to(Token::EqualEqual),
just('=').to(Token::Equal),
just("..").to(Token::Dot),
just('.').to(Token::Dot),
just("!=").to(Token::NotEqual),
just('!').to(Token::Bang),
just("<=").to(Token::LessEqual),
just('<').to(Token::Less),
just(">=").to(Token::GreaterEqual),
just('>').to(Token::Greater),
just('+').to(Token::Plus),
just("->").to(Token::RArrow),
just('-').to(Token::Minus),
just('*').to(Token::Star),
just('/').to(Token::Slash),
just('%').to(Token::Percent),
just("|>").to(Token::Pipe),
just(',').to(Token::Comma),
just(':').to(Token::Colon),
));
let grouping = choice((
just('(').to(Token::LeftParen),
just(')').to(Token::RightParen),
just('[').to(Token::LeftSquare),
just(']').to(Token::RightSquare),
just('{').to(Token::LeftBrace),
just('}').to(Token::RightBrace),
));
let escape = just('\\').ignore_then(
just('\\')
.or(just('/'))
.or(just('"'))
.or(just('b').to('\x08'))
.or(just('f').to('\x0C'))
.or(just('n').to('\n'))
.or(just('r').to('\r'))
.or(just('t').to('\t')),
);
let string = just('"')
.ignore_then(filter(|c| *c != '\\' && *c != '"').or(escape).repeated())
.then_ignore(just('"'))
.collect::<String>()
.map(|value| Token::String { value })
.labelled("string");
let keyword = text::ident().map(|s: String| match s.as_str() {
"as" => Token::As,
"assert" => Token::Assert,
"const" => Token::Const,
"fn" => Token::Fn,
"if" => Token::If,
"is" => Token::Is,
"let" => Token::Let,
"opaque" => Token::Opaque,
"pub" => Token::Pub,
"use" => Token::Use,
"todo" => Token::Todo,
"try" => Token::Try,
"type" => Token::Type,
"when" => Token::When,
_ => {
if s.chars().next().map_or(false, |c| c.is_uppercase()) {
Token::UpName {
// TODO: do not allow _ in upname
name: s,
}
} else if s.starts_with('_') {
Token::DiscardName {
// TODO: do not allow uppercase letters in discard name
name: s,
}
} else {
Token::Name {
// TODO: do not allow uppercase letters in name
name: s,
}
}
}
});
let token = choice((keyword, int, op, grouping, string))
.or(any().map(Token::Error).validate(|t, span, emit| {
emit(ParseError::expected_input_found(
span,
None,
Some(t.clone()),
));
t
}))
.map_with_span(move |token, span| (token, span))
.padded()
.recover_with(skip_then_retry_until([]));
let comments = just("//")
.then_ignore(
just('(')
.ignore_then(take_until(just(")#")).ignored())
.or(none_of('\n').ignored().repeated().ignored()),
)
.padded()
.ignored()
.repeated();
token
.padded_by(comments)
.repeated()
.padded()
.then_ignore(end())
}
#[cfg(test)]
mod tests {
use chumsky::prelude::*;
use crate::{
ast::{Span, SrcId},
lexer,
token::Token,
};
#[test]
fn simple() {
let code = "pub type |> >=\n{ Thing _na_thing name";
let len = code.chars().count();
let span = |i| Span::new(SrcId::empty(), i..i + 1);
assert_eq!(
lexer::lexer()
.parse(chumsky::Stream::from_iter(
span(len),
code.chars().enumerate().map(|(i, c)| (c, span(i))),
))
.map(|tokens| tokens.into_iter().map(|(tok, _)| tok).collect::<Vec<_>>()),
Ok(vec![
Token::Pub,
Token::Type,
Token::Pipe,
Token::GreaterEqual,
Token::LeftBrace,
Token::UpName {
name: "Thing".to_string()
},
Token::DiscardName {
name: "_na_thing".to_string()
},
Token::Name {
name: "name".to_string()
}
]),
);
}
}

8
crates/lang/src/lib.rs Normal file
View File

@@ -0,0 +1,8 @@
pub mod ast;
pub mod build;
pub mod error;
pub mod expr;
pub mod lexer;
pub mod parser;
pub mod tipo;
pub mod token;

811
crates/lang/src/parser.rs Normal file
View File

@@ -0,0 +1,811 @@
use chumsky::prelude::*;
use crate::{
ast::{self, BinOp, TodoKind},
error::ParseError,
expr,
token::Token,
};
pub fn module_parser(
kind: ast::ModuleKind,
) -> impl Parser<Token, ast::UntypedModule, Error = ParseError> {
choice((
import_parser(),
data_parser(),
type_alias_parser(),
fn_parser(),
))
.repeated()
.then_ignore(end())
.map(move |definitions| ast::UntypedModule {
kind,
definitions,
docs: vec![],
name: vec![],
type_info: (),
})
}
pub fn import_parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
let unqualified_import = choice((
select! {Token::Name { name } => name}.then(
just(Token::As)
.ignore_then(select! {Token::Name { name } => name})
.or_not(),
),
select! {Token::UpName { name } => name}.then(
just(Token::As)
.ignore_then(select! {Token::UpName { name } => name})
.or_not(),
),
))
.map_with_span(|(name, as_name), span| ast::UnqualifiedImport {
name,
location: span,
as_name,
layer: Default::default(),
});
let unqualified_imports = just(Token::Dot)
.ignore_then(
unqualified_import
.separated_by(just(Token::Comma))
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace)),
)
.or_not();
let as_name = just(Token::As)
.ignore_then(select! {Token::Name { name } => name})
.or_not();
let module_path = select! {Token::Name { name } => name}
.separated_by(just(Token::Slash))
.then(unqualified_imports)
.then(as_name);
just(Token::Use).ignore_then(module_path).map_with_span(
|((module, unqualified), as_name), span| ast::UntypedDefinition::Use {
module,
as_name,
unqualified: unqualified.unwrap_or_default(),
package: (),
location: span,
},
)
}
pub fn data_parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
let unlabeled_constructor_type_args = type_parser()
.map_with_span(|annotation, span| ast::RecordConstructorArg {
label: None,
annotation,
tipo: (),
doc: None,
location: span,
})
.separated_by(just(Token::Comma))
.delimited_by(just(Token::LeftParen), just(Token::RightParen));
let constructors = select! {Token::UpName { name } => name}
.then(
choice((
labeled_constructor_type_args(),
unlabeled_constructor_type_args,
))
.or_not(),
)
.map_with_span(|(name, arguments), span| ast::RecordConstructor {
location: span,
arguments: arguments.unwrap_or_default(),
name,
documentation: None,
sugar: false,
})
.repeated()
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace));
let record_sugar = labeled_constructor_type_args().map_with_span(|arguments, span| {
vec![ast::RecordConstructor {
location: span,
arguments,
documentation: None,
name: String::from("_replace"),
sugar: true,
}]
});
pub_parser()
.then(just(Token::Opaque).ignored().or_not())
.or_not()
.then(type_name_with_args())
.then(choice((constructors, record_sugar)))
.map_with_span(|((pub_opaque, (name, parameters)), constructors), span| {
ast::UntypedDefinition::DataType {
location: span,
constructors: constructors
.into_iter()
.map(|mut constructor| {
if constructor.sugar {
constructor.name = name.clone();
}
constructor
})
.collect(),
doc: None,
name,
opaque: pub_opaque
.map(|(_, opt_opaque)| opt_opaque.is_some())
.unwrap_or(false),
parameters: parameters.unwrap_or_default(),
public: pub_opaque.is_some(),
typed_parameters: vec![],
}
})
}
pub fn type_alias_parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
pub_parser()
.or_not()
.then(type_name_with_args())
.then_ignore(just(Token::Equal))
.then(type_parser())
.map_with_span(|((opt_pub, (alias, parameters)), annotation), span| {
ast::UntypedDefinition::TypeAlias {
alias,
annotation,
doc: None,
location: span,
parameters: parameters.unwrap_or_default(),
public: opt_pub.is_some(),
tipo: (),
}
})
}
pub fn fn_parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
pub_parser()
.or_not()
.then_ignore(just(Token::Fn))
.then(select! {Token::Name {name} => name})
.then(
fn_param_parser()
.separated_by(just(Token::Comma))
.delimited_by(just(Token::LeftParen), just(Token::RightParen)),
)
.then(just(Token::RArrow).ignore_then(type_parser()).or_not())
.then_ignore(just(Token::LeftBrace))
.then(expr_seq_parser())
.then_ignore(just(Token::RightBrace))
.map_with_span(
|((((opt_pub, name), arguments), return_annotation), body), span| {
ast::UntypedDefinition::Fn {
arguments,
body,
doc: None,
location: span,
name,
public: opt_pub.is_some(),
return_annotation,
return_type: (),
}
},
)
}
pub fn fn_param_parser() -> impl Parser<Token, ast::UntypedArg, Error = ParseError> {
choice((
select! {Token::Name {name} => name}
.then(select! {Token::DiscardName {name} => name})
.map_with_span(|(label, name), span| ast::ArgName::LabeledDiscard {
label,
name,
location: span,
}),
select! {Token::DiscardName {name} => name}.map_with_span(|name, span| {
ast::ArgName::Discard {
name,
location: span,
}
}),
select! {Token::Name {name} => name}
.then(select! {Token::Name {name} => name})
.map_with_span(|(label, name), span| ast::ArgName::NamedLabeled {
label,
name,
location: span,
}),
select! {Token::Name {name} => name}.map_with_span(|name, span| ast::ArgName::Named {
name,
location: span,
}),
))
.then(just(Token::Colon).ignore_then(type_parser()).or_not())
.map_with_span(|(arg_name, annotation), span| ast::Arg {
location: span,
annotation,
tipo: (),
arg_name,
})
}
pub fn expr_seq_parser() -> impl Parser<Token, expr::UntypedExpr, Error = ParseError> {
recursive(|r| {
choice((
just(Token::Try)
.ignore_then(pattern_parser())
.then(just(Token::Colon).ignore_then(type_parser()).or_not())
.then_ignore(just(Token::Equal))
.then(expr_parser())
.then(r.clone())
.map_with_span(|(((pattern, annotation), value), then_), span| {
expr::UntypedExpr::Try {
location: span,
value: Box::new(value),
pattern,
then: Box::new(then_),
annotation,
}
}),
expr_parser()
.then(r.repeated())
.map_with_span(|(expr, exprs), _span| {
exprs
.into_iter()
.fold(expr, |acc, elem| acc.append_in_sequence(elem))
}),
))
})
}
pub fn expr_parser() -> impl Parser<Token, expr::UntypedExpr, Error = ParseError> {
recursive(|_r| {
let op = choice((
just(Token::Star).to(BinOp::MultInt),
just(Token::Slash).to(BinOp::DivInt),
just(Token::Percent).to(BinOp::ModInt),
));
let product = expr_unit_parser()
.then(op.then(expr_unit_parser()).repeated())
.foldl(|a, (op, b)| expr::UntypedExpr::BinOp {
location: a.location().union(b.location()),
name: op,
left: Box::new(a),
right: Box::new(b),
})
.boxed();
let op = choice((
just(Token::Plus).to(BinOp::AddInt),
just(Token::Minus).to(BinOp::SubInt),
));
product
.clone()
.then(op.then(product).repeated())
.foldl(|a, (op, b)| expr::UntypedExpr::BinOp {
location: a.location().union(b.location()),
name: op,
left: Box::new(a),
right: Box::new(b),
})
})
}
pub fn expr_unit_parser() -> impl Parser<Token, expr::UntypedExpr, Error = ParseError> {
choice((
select! {Token::String {value} => value}.map_with_span(|value, span| {
expr::UntypedExpr::String {
location: span,
value,
}
}),
select! { Token::Int {value} => value}.map_with_span(|value, span| {
expr::UntypedExpr::Int {
location: span,
value,
}
}),
select! {
Token::Name { name } => name,
Token::UpName { name } => name,
}
.map_with_span(|name, span| expr::UntypedExpr::Var {
location: span,
name,
}),
just(Token::Todo)
.ignore_then(
select! {Token::String {value} => value}
.delimited_by(just(Token::LeftParen), just(Token::RightParen))
.or_not(),
)
.map_with_span(|label, span| expr::UntypedExpr::Todo {
kind: TodoKind::Keyword,
location: span,
label,
}),
))
}
pub fn type_parser() -> impl Parser<Token, ast::Annotation, Error = ParseError> {
recursive(|r| {
choice((
select! {Token::DiscardName { name } => name}.map_with_span(|name, span| {
ast::Annotation::Hole {
location: span,
name,
}
}),
just(Token::Fn)
.ignore_then(
r.clone()
.separated_by(just(Token::Comma))
.delimited_by(just(Token::LeftParen), just(Token::RightParen)),
)
.then_ignore(just(Token::RArrow))
.then(r.clone())
.map_with_span(|(arguments, ret), span| ast::Annotation::Fn {
location: span,
arguments,
ret: Box::new(ret),
}),
select! {Token::UpName { name } => name}
.then(
r.clone()
.separated_by(just(Token::Comma))
.delimited_by(just(Token::LeftParen), just(Token::RightParen))
.or_not(),
)
.map_with_span(|(name, arguments), span| ast::Annotation::Constructor {
location: span,
module: None,
name,
arguments: arguments.unwrap_or_default(),
}),
select! {Token::Name { name } => name}
.then(
just(Token::Dot)
.ignore_then(select! {Token::UpName {name} => name})
.then(
r.separated_by(just(Token::Comma))
.delimited_by(just(Token::LeftParen), just(Token::RightParen))
.or_not(),
)
.or_not(),
)
.map_with_span(|(mod_name, opt_dot), span| {
if let Some((name, arguments)) = opt_dot {
ast::Annotation::Constructor {
location: span,
module: Some(mod_name),
name,
arguments: arguments.unwrap_or_default(),
}
} else {
ast::Annotation::Var {
location: span,
name: mod_name,
}
}
}),
))
})
}
pub fn labeled_constructor_type_args(
) -> impl Parser<Token, Vec<ast::RecordConstructorArg<()>>, Error = ParseError> {
select! {Token::Name {name} => name}
.then_ignore(just(Token::Colon))
.then(type_parser())
.map_with_span(|(name, annotation), span| ast::RecordConstructorArg {
label: Some(name),
annotation,
tipo: (),
doc: None,
location: span,
})
.separated_by(just(Token::Comma))
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace))
}
pub fn type_name_with_args() -> impl Parser<Token, (String, Option<Vec<String>>), Error = ParseError>
{
just(Token::Type).ignore_then(
select! {Token::UpName { name } => name}.then(
select! {Token::Name { name } => name}
.separated_by(just(Token::Comma))
.delimited_by(just(Token::LeftParen), just(Token::RightParen))
.or_not(),
),
)
}
pub fn pub_parser() -> impl Parser<Token, (), Error = ParseError> {
just(Token::Pub).ignored()
}
pub fn pattern_parser() -> impl Parser<Token, ast::UntypedPattern, Error = ParseError> {
recursive(|r| {
let constructor_pattern_arg_parser = choice((
select! {Token::Name {name} => name}
.then_ignore(just(Token::Colon))
.then(r.clone())
.map_with_span(|(name, pattern), span| ast::CallArg {
location: span,
label: Some(name),
value: pattern,
}),
r.map_with_span(|pattern, span| ast::CallArg {
location: span,
value: pattern,
label: None,
}),
));
let constructor_pattern_args_parser = constructor_pattern_arg_parser
.separated_by(just(Token::Comma))
.allow_trailing()
.then(
just(Token::DotDot)
.then_ignore(just(Token::Comma).or_not())
.ignored()
.or_not(),
)
.delimited_by(just(Token::LeftParen), just(Token::RightParen))
.or_not()
.map(|opt_args| {
opt_args
.map(|(a, b)| (a, b.is_some()))
.unwrap_or_else(|| (vec![], false))
});
let constructor_pattern_parser =
select! {Token::UpName { name } => name}.then(constructor_pattern_args_parser);
choice((
select! { Token::Name {name} => name }
.then(
just(Token::Dot)
.ignore_then(constructor_pattern_parser.clone())
.or_not(),
)
.map_with_span(|(name, opt_pattern), span| {
if let Some((c_name, (arguments, with_spread))) = opt_pattern {
ast::UntypedPattern::Constructor {
location: span,
name: c_name,
arguments,
module: Some(name),
constructor: (),
with_spread,
tipo: (),
}
} else {
ast::UntypedPattern::Var {
location: span,
name,
}
}
}),
constructor_pattern_parser.map_with_span(|(name, (arguments, with_spread)), span| {
ast::UntypedPattern::Constructor {
location: span,
name,
arguments,
module: None,
constructor: (),
with_spread,
tipo: (),
}
}),
select! {Token::DiscardName {name} => name}.map_with_span(|name, span| {
ast::UntypedPattern::Discard {
name,
location: span,
}
}),
select! {Token::String {value} => value}.map_with_span(|value, span| {
ast::UntypedPattern::String {
location: span,
value,
}
}),
select! {Token::Int {value} => value}.map_with_span(|value, span| {
ast::UntypedPattern::Int {
location: span,
value,
}
}),
))
.then(
just(Token::As)
.ignore_then(select! { Token::Name {name} => name})
.or_not(),
)
.map_with_span(|(pattern, opt_as), span| {
if let Some(name) = opt_as {
ast::UntypedPattern::Assign {
name,
location: span,
pattern: Box::new(pattern),
}
} else {
pattern
}
})
})
}
#[cfg(test)]
mod tests {
use chumsky::prelude::*;
use pretty_assertions::assert_eq;
use crate::{
ast::{self, Span, SrcId},
expr, lexer,
parser::module_parser,
};
#[test]
fn simple() {
let code = r#"
use std/list
use std/address.{Address as A, thing as w}
use std/tx as t
type Option(a) {
Some(a, Int)
None
Wow { name: Int, age: Int }
}
pub opaque type User {
name: _w
}
type Thing = Option(Int)
pub type Me = Option(String)
pub fn add_one(a) {
a + 1
}
"#;
let len = code.chars().count();
let span = |i| Span::new(SrcId::empty(), i..i + 1);
let tokens = lexer::lexer()
.parse(chumsky::Stream::from_iter(
span(len),
code.chars().enumerate().map(|(i, c)| (c, span(i))),
))
.unwrap();
let res = module_parser(ast::ModuleKind::Script)
.parse(chumsky::Stream::from_iter(span(len), tokens.into_iter()))
.unwrap();
assert_eq!(
res,
ast::UntypedModule {
docs: vec![],
kind: ast::ModuleKind::Script,
name: vec![],
type_info: (),
definitions: vec![
ast::UntypedDefinition::Use {
location: Span::new(SrcId::empty(), 13..25),
module: vec!["std".to_string(), "list".to_string()],
as_name: None,
unqualified: vec![],
package: (),
},
ast::UntypedDefinition::Use {
location: Span::new(SrcId::empty(), 38..80),
module: vec!["std".to_string(), "address".to_string()],
as_name: None,
unqualified: vec![
ast::UnqualifiedImport {
as_name: Some("A".to_string()),
location: Span::new(SrcId::empty(), 55..67),
layer: Default::default(),
name: "Address".to_string()
},
ast::UnqualifiedImport {
as_name: Some("w".to_string()),
location: Span::new(SrcId::empty(), 69..79),
layer: Default::default(),
name: "thing".to_string()
}
],
package: (),
},
ast::UntypedDefinition::Use {
location: Span::new(SrcId::empty(), 93..108),
module: vec!["std".to_string(), "tx".to_string()],
as_name: Some("t".to_string()),
unqualified: vec![],
package: (),
},
ast::UntypedDefinition::DataType {
location: Span::new(SrcId::empty(), 122..240),
constructors: vec![
ast::RecordConstructor {
location: Span::new(SrcId::empty(), 153..165),
name: "Some".to_string(),
arguments: vec![
ast::RecordConstructorArg {
label: None,
annotation: ast::Annotation::Var {
location: Span::new(SrcId::empty(), 158..159),
name: "a".to_string(),
},
location: Span::new(SrcId::empty(), 158..159),
tipo: (),
doc: None,
},
ast::RecordConstructorArg {
label: None,
annotation: ast::Annotation::Constructor {
location: Span::new(SrcId::empty(), 161..164),
module: None,
name: "Int".to_string(),
arguments: vec![],
},
location: Span::new(SrcId::empty(), 161..164),
tipo: (),
doc: None,
},
],
documentation: None,
sugar: false,
},
ast::RecordConstructor {
location: Span::new(SrcId::empty(), 180..184),
name: "None".to_string(),
arguments: vec![],
documentation: None,
sugar: false,
},
ast::RecordConstructor {
location: Span::new(SrcId::empty(), 199..226),
name: "Wow".to_string(),
arguments: vec![
ast::RecordConstructorArg {
label: Some("name".to_string(),),
annotation: ast::Annotation::Constructor {
location: Span::new(SrcId::empty(), 211..214),
module: None,
name: "Int".to_string(),
arguments: vec![],
},
location: Span::new(SrcId::empty(), 205..214),
tipo: (),
doc: None,
},
ast::RecordConstructorArg {
label: Some("age".to_string(),),
annotation: ast::Annotation::Constructor {
location: Span::new(SrcId::empty(), 221..224),
module: None,
name: "Int".to_string(),
arguments: vec![],
},
location: Span::new(SrcId::empty(), 216..224),
tipo: (),
doc: None,
},
],
documentation: None,
sugar: false,
},
],
doc: None,
name: "Option".to_string(),
opaque: false,
parameters: vec!["a".to_string(),],
public: false,
typed_parameters: vec![],
},
ast::UntypedDefinition::DataType {
location: Span::new(SrcId::empty(), 254..313),
constructors: vec![ast::RecordConstructor {
location: Span::new(SrcId::empty(), 275..313),
name: "User".to_string(),
arguments: vec![ast::RecordConstructorArg {
label: Some("name".to_string()),
annotation: ast::Annotation::Hole {
location: Span::new(SrcId::empty(), 297..299),
name: "_w".to_string(),
},
location: Span::new(SrcId::empty(), 291..299),
tipo: (),
doc: None,
},],
documentation: None,
sugar: true,
},],
doc: None,
name: "User".to_string(),
opaque: true,
parameters: vec![],
public: true,
typed_parameters: vec![],
},
ast::UntypedDefinition::TypeAlias {
alias: "Thing".to_string(),
annotation: ast::Annotation::Constructor {
location: Span::new(SrcId::empty(), 340..351),
module: None,
name: "Option".to_string(),
arguments: vec![ast::Annotation::Constructor {
location: Span::new(SrcId::empty(), 347..350),
module: None,
name: "Int".to_string(),
arguments: vec![],
},],
},
doc: None,
location: Span::new(SrcId::empty(), 327..351),
parameters: vec![],
public: false,
tipo: (),
},
ast::UntypedDefinition::TypeAlias {
alias: "Me".to_string(),
annotation: ast::Annotation::Constructor {
location: Span::new(SrcId::empty(), 379..393),
module: None,
name: "Option".to_string(),
arguments: vec![ast::Annotation::Constructor {
location: Span::new(SrcId::empty(), 386..392),
module: None,
name: "String".to_string(),
arguments: vec![],
},],
},
doc: None,
location: Span::new(SrcId::empty(), 365..393),
parameters: vec![],
public: true,
tipo: (),
},
ast::UntypedDefinition::Fn {
arguments: vec![ast::Arg {
arg_name: ast::ArgName::Named {
name: "a".to_string(),
location: Span::new(SrcId::empty(), 422..423),
},
location: Span::new(SrcId::empty(), 422..423),
annotation: None,
tipo: (),
},],
body: expr::UntypedExpr::BinOp {
location: Span::new(SrcId::empty(), 441..446),
name: ast::BinOp::AddInt,
left: Box::new(expr::UntypedExpr::Var {
location: Span::new(SrcId::empty(), 441..442),
name: "a".to_string(),
}),
right: Box::new(expr::UntypedExpr::Int {
location: Span::new(SrcId::empty(), 445..446),
value: "1".to_string(),
}),
},
doc: None,
location: Span::new(SrcId::empty(), 407..460),
name: "add_one".to_string(),
public: true,
return_annotation: None,
return_type: (),
},
]
},
);
}
}

163
crates/lang/src/tipo.rs Normal file
View File

@@ -0,0 +1,163 @@
use std::{cell::RefCell, collections::HashMap, sync::Arc};
use crate::{
ast::{Constant, FieldMap, Span, TypedConstant},
build::Origin,
};
#[derive(Debug, Clone, PartialEq)]
pub enum Type {
/// A nominal (named) type such as `Int`, `Float`, or a programmer defined
/// custom type such as `Person`. The type can take other types as
/// arguments (aka "generics" or "parametric polymorphism").
///
/// If the type is defined in the Gleam prelude the `module` field will be
/// empty, otherwise it will contain the name of the module that
/// defines the type.
///
App {
public: bool,
module: Vec<String>,
name: String,
args: Vec<Arc<Type>>,
},
/// The type of a function. It takes arguments and returns a value.
///
Fn {
args: Vec<Arc<Type>>,
retrn: Arc<Type>,
},
/// A type variable. See the contained `TypeVar` enum for more information.
///
Var { tipo: Arc<RefCell<TypeVar>> },
/// A tuple is an ordered collection of 0 or more values, each of which
/// can have a different type, so the `tuple` type is the sum of all the
/// contained types.
///
Tuple { elems: Vec<Arc<Type>> },
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypeVar {
/// Unbound is an unbound variable. It is one specific type but we don't
/// know what yet in the inference process. It has a unique id which can be used to
/// identify if two unbound variable Rust values are the same Gleam type variable
/// instance or not.
///
Unbound { id: u64 },
/// Link is type variable where it was an unbound variable but we worked out
/// that it is some other type and now we point to that one.
///
Link { tipo: Arc<Type> },
/// A Generic variable stands in for any possible type and cannot be
/// specialised to any one type
///
/// # Example
///
/// ```gleam
/// type Cat(a) {
/// Cat(name: a)
/// }
/// // a is TypeVar::Generic
/// ```
///
Generic { id: u64 },
}
#[derive(Debug, Clone, PartialEq)]
pub struct ValueConstructor {
pub public: bool,
pub variant: ValueConstructorVariant,
pub tipo: Arc<Type>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ValueConstructorVariant {
/// A locally defined variable or function parameter
LocalVariable { location: Span },
/// A module constant
ModuleConstant {
location: Span,
module: String,
literal: Constant<Arc<Type>, String>,
},
/// A function belonging to the module
ModuleFn {
name: String,
field_map: Option<FieldMap>,
module: Vec<String>,
arity: usize,
location: Span,
},
/// A constructor for a custom type
Record {
name: String,
arity: usize,
field_map: Option<FieldMap>,
location: Span,
module: String,
},
}
pub struct Module {
pub name: Vec<String>,
pub origin: Origin,
pub package: String,
pub types: HashMap<String, TypeConstructor>,
pub types_constructors: HashMap<String, Vec<String>>,
pub values: HashMap<String, ValueConstructor>,
pub accessors: HashMap<String, AccessorsMap>,
}
pub struct TypeConstructor {
pub public: bool,
pub origin: Span,
pub module: Vec<String>,
pub parameters: Vec<Arc<Type>>,
pub typ: Arc<Type>,
}
pub struct AccessorsMap {
pub public: bool,
pub tipo: Arc<Type>,
pub accessors: HashMap<String, RecordAccessor>,
}
pub struct RecordAccessor {
// TODO: smaller int. Doesn't need to be this big
pub index: u64,
pub label: String,
pub tipo: Arc<Type>,
}
pub enum PatternConstructor {
Record {
name: String,
field_map: Option<FieldMap>,
},
}
pub enum ModuleValueConstructor {
Record {
name: String,
arity: usize,
type_: Arc<Type>,
field_map: Option<FieldMap>,
location: Span,
},
Fn {
location: Span,
},
Constant {
literal: TypedConstant,
location: Span,
},
}

147
crates/lang/src/token.rs Normal file
View File

@@ -0,0 +1,147 @@
use std::fmt;
#[derive(Clone, Debug, PartialEq, Hash, Eq)]
pub enum Token {
Error(char),
Name { name: String },
UpName { name: String },
DiscardName { name: String },
Int { value: String },
String { value: String },
// Groupings
LeftParen, // (
RightParen, // )
LeftSquare, // [
RightSquare, // }
LeftBrace, // {
RightBrace, // }
// Int Operators
Plus,
Minus,
Star,
Slash,
Less,
Greater,
LessEqual,
GreaterEqual,
Percent,
// ByteString Operators
PlusDot, // '+.'
MinusDot, // '-.'
StarDot, // '*.'
SlashDot, // '/.'
LessDot, // '<.'
GreaterDot, // '>.'
LessEqualDot, // '<=.'
GreaterEqualDot, // '>=.'
// Other Punctuation
Colon,
Comma,
Hash, // '#'
Bang, // '!'
Equal,
EqualEqual, // '=='
NotEqual, // '!='
Vbar, // '|'
VbarVbar, // '||'
AmperAmper, // '&&'
Pipe, // '|>'
Dot, // '.'
RArrow, // '->'
DotDot, // '..'
EndOfFile,
// Extra
CommentNormal,
CommentDoc,
CommentModule,
EmptyLine,
// Keywords (alphabetically):
As,
Assert,
Const,
Fn,
If,
Is,
Let,
Opaque,
Pub,
Use,
Todo,
Try,
Type,
When,
}
impl fmt::Display for Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Token::Error(c) => {
write!(f, "\"{}\"", c)?;
return Ok(());
}
Token::Name { name } => name,
Token::UpName { name } => name,
Token::DiscardName { name } => name,
Token::Int { value } => value,
Token::String { value } => value,
Token::LeftParen => "(",
Token::RightParen => ")",
Token::LeftSquare => "[",
Token::RightSquare => "]",
Token::LeftBrace => "{",
Token::RightBrace => "}",
Token::Plus => "+",
Token::Minus => "-",
Token::Star => "*",
Token::Slash => "/",
Token::Less => "<",
Token::Greater => ">",
Token::LessEqual => "<=",
Token::GreaterEqual => ">=",
Token::Percent => "%",
Token::PlusDot => "+.",
Token::MinusDot => "-.",
Token::StarDot => "*.",
Token::SlashDot => "/.",
Token::LessDot => "<.",
Token::GreaterDot => ">.",
Token::LessEqualDot => "<=.",
Token::GreaterEqualDot => ">=.",
Token::Colon => ":",
Token::Comma => ",",
Token::Hash => "#",
Token::Bang => "!",
Token::Equal => "=",
Token::EqualEqual => "==",
Token::NotEqual => "!=",
Token::Vbar => "|",
Token::VbarVbar => "||",
Token::AmperAmper => "&&",
Token::Pipe => "|>",
Token::Dot => ".",
Token::RArrow => "->",
Token::DotDot => "..",
Token::EndOfFile => "EOF",
Token::CommentNormal => "//",
Token::CommentDoc => "///",
Token::CommentModule => "////",
Token::EmptyLine => "EMPTYLINE",
Token::As => "as",
Token::Assert => "assert",
Token::When => "when",
Token::Is => "is",
Token::Const => "const",
Token::Fn => "fn",
Token::If => "if",
Token::Use => "import",
Token::Let => "let",
Token::Opaque => "opaque",
Token::Pub => "pub",
Token::Todo => "todo",
Token::Try => "try",
Token::Type => "type",
};
write!(f, "\"{}\"", s)
}
}