mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 23:31:12 +00:00
Record raw strings during parse step
This commit is contained in:
parent
fa9e074488
commit
d54cf81f7b
40 changed files with 4111 additions and 7400 deletions
41
Cargo.lock
generated
41
Cargo.lock
generated
|
@ -16,11 +16,6 @@ dependencies = [
|
||||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ascii"
|
|
||||||
version = "0.9.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
|
@ -54,18 +49,6 @@ dependencies = [
|
||||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "combine"
|
|
||||||
version = "3.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"ascii 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "difference"
|
name = "difference"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
|
@ -76,11 +59,6 @@ name = "dogged"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "either"
|
|
||||||
version = "1.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
|
@ -440,7 +418,6 @@ name = "roc"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"combine 3.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"dogged 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"dogged 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"fraction 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"fraction 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -517,19 +494,6 @@ name = "unindent"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unreachable"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "void"
|
|
||||||
version = "1.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
|
@ -552,17 +516,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
[metadata]
|
[metadata]
|
||||||
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
|
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
|
||||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||||
"checksum ascii 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "91e320562a8fa3286a481b7189f89578ace6b20df99e123c87f2f509c957c5d6"
|
|
||||||
"checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf"
|
"checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf"
|
||||||
"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd"
|
"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd"
|
||||||
"checksum bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708"
|
"checksum bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708"
|
||||||
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
|
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
|
||||||
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
|
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
|
||||||
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
||||||
"checksum combine 3.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680"
|
|
||||||
"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
||||||
"checksum dogged 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2638df109789fe360f0d9998c5438dd19a36678aaf845e46f285b688b1a1657a"
|
"checksum dogged 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2638df109789fe360f0d9998c5438dd19a36678aaf845e46f285b688b1a1657a"
|
||||||
"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b"
|
|
||||||
"checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3"
|
"checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3"
|
||||||
"checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33"
|
"checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33"
|
||||||
"checksum fraction 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1055159ac82fb210c813303f716b6c8db57ace9d5ec2dbbc2e1d7a864c1dd74e"
|
"checksum fraction 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1055159ac82fb210c813303f716b6c8db57ace9d5ec2dbbc2e1d7a864c1dd74e"
|
||||||
|
@ -613,8 +574,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169"
|
"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169"
|
||||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||||
"checksum unindent 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "834b4441326c660336850c5c0926cc20548e848967a5f57bc20c2b741c8d41f4"
|
"checksum unindent 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "834b4441326c660336850c5c0926cc20548e848967a5f57bc20c2b741c8d41f4"
|
||||||
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
|
|
||||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
|
||||||
"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
|
"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
|
||||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
|
@ -7,7 +7,6 @@ authors = ["Richard Feldman <oss@rtfeldman.com>"]
|
||||||
dogged = { version = "0.2.0", optional = true }
|
dogged = { version = "0.2.0", optional = true }
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
petgraph = { version = "0.4.5", optional = true }
|
petgraph = { version = "0.4.5", optional = true }
|
||||||
combine = "3.8.1"
|
|
||||||
im-rc = "13.0.0"
|
im-rc = "13.0.0"
|
||||||
fraction = "0.6.2"
|
fraction = "0.6.2"
|
||||||
num = "0.2.0"
|
num = "0.2.0"
|
||||||
|
|
66
src/can/env.rs
Normal file
66
src/can/env.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use can::expr::Expr;
|
||||||
|
use can::pattern::Pattern;
|
||||||
|
use can::problem::Problem;
|
||||||
|
use can::procedure::{Procedure, References};
|
||||||
|
use can::symbol::Symbol;
|
||||||
|
use collections::{ImMap, MutMap};
|
||||||
|
use parse::ast;
|
||||||
|
use region::{Located, Region};
|
||||||
|
|
||||||
|
/// The canonicalization environment for a particular module.
|
||||||
|
pub struct Env {
|
||||||
|
/// The module's path. Unqualified references to identifiers and variant names are assumed
|
||||||
|
/// to be relative to this path.
|
||||||
|
pub home: String,
|
||||||
|
|
||||||
|
/// Problems we've encountered along the way, which will be reported to the user at the end.
|
||||||
|
pub problems: Vec<Problem>,
|
||||||
|
|
||||||
|
/// Variants either declared in this module, or imported.
|
||||||
|
pub variants: ImMap<Symbol, Located<Box<ast::VariantName>>>,
|
||||||
|
|
||||||
|
/// Former closures converted to top-level procedures.
|
||||||
|
pub procedures: MutMap<Symbol, Procedure>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Env {
|
||||||
|
pub fn new(
|
||||||
|
home: String,
|
||||||
|
declared_variants: ImMap<Symbol, Located<Box<ast::VariantName>>>,
|
||||||
|
) -> Env {
|
||||||
|
Env {
|
||||||
|
home,
|
||||||
|
variants: declared_variants,
|
||||||
|
problems: Vec::new(),
|
||||||
|
procedures: MutMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn problem(&mut self, problem: Problem) -> () {
|
||||||
|
self.problems.push(problem)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_closure(
|
||||||
|
&mut self,
|
||||||
|
symbol: Symbol,
|
||||||
|
args: Vec<Located<Pattern>>,
|
||||||
|
body: Located<Expr>,
|
||||||
|
definition: Region,
|
||||||
|
references: References,
|
||||||
|
) -> () {
|
||||||
|
// We can't if the closure is self tail recursive yet, because it doesn't know its final name yet.
|
||||||
|
// (Assign sets that.) Assume this is false, and let Assign change it to true after it sets final name.
|
||||||
|
let is_self_tail_recursive = false;
|
||||||
|
let name = None; // The Assign logic is also responsible for setting names after the fact.
|
||||||
|
let procedure = Procedure {
|
||||||
|
args,
|
||||||
|
name,
|
||||||
|
body,
|
||||||
|
is_self_tail_recursive,
|
||||||
|
definition,
|
||||||
|
references,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.procedures.insert(symbol, procedure);
|
||||||
|
}
|
||||||
|
}
|
46
src/can/expr.rs
Normal file
46
src/can/expr.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use can::pattern::Pattern;
|
||||||
|
use can::problem::RuntimeError;
|
||||||
|
use can::symbol::Symbol;
|
||||||
|
use operator::Operator;
|
||||||
|
use region::Located;
|
||||||
|
use std::i64;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Expr {
|
||||||
|
// Literals
|
||||||
|
Int(i64),
|
||||||
|
Float(f64),
|
||||||
|
EmptyStr,
|
||||||
|
Str(Box<str>),
|
||||||
|
Char(char), // OBSOLETE
|
||||||
|
List(Vec<Located<Expr>>),
|
||||||
|
EmptyList,
|
||||||
|
|
||||||
|
// Lookups
|
||||||
|
Var(Symbol),
|
||||||
|
/// Works the same as Var, but has an important marking purpose.
|
||||||
|
/// See 13623e3f5f65ea2d703cf155f16650c1e8246502 for the bug this fixed.
|
||||||
|
FunctionPointer(Symbol),
|
||||||
|
/// We have a separate variant for this so that we can report errors
|
||||||
|
/// (including type errors later) in the context of the sugar rather than
|
||||||
|
/// confusingly talking about the desugared version the user can't see.
|
||||||
|
InterpolatedStr(Vec<(Box<str>, Located<Expr>)>, Box<str>),
|
||||||
|
|
||||||
|
// Pattern Matching
|
||||||
|
Case(Box<Located<Expr>>, Vec<(Located<Pattern>, Located<Expr>)>),
|
||||||
|
Assign(Vec<(Located<Pattern>, Located<Expr>)>, Box<Located<Expr>>),
|
||||||
|
|
||||||
|
// Application
|
||||||
|
Call(Box<Located<Expr>>, Vec<Located<Expr>>),
|
||||||
|
ApplyVariant(Symbol, Option<Vec<Located<Expr>>>),
|
||||||
|
|
||||||
|
// Product Types
|
||||||
|
EmptyRecord,
|
||||||
|
|
||||||
|
// Sugar
|
||||||
|
If(Box<Located<Expr>>, Box<Located<Expr>>, Box<Located<Expr>>),
|
||||||
|
Operator(Box<Located<Expr>>, Located<Operator>, Box<Located<Expr>>),
|
||||||
|
|
||||||
|
// Compiles, but will crash if reached
|
||||||
|
RuntimeError(RuntimeError),
|
||||||
|
}
|
1334
src/can/mod.rs
Normal file
1334
src/can/mod.rs
Normal file
File diff suppressed because it is too large
Load diff
203
src/can/pattern.rs
Normal file
203
src/can/pattern.rs
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
use can::env::Env;
|
||||||
|
use can::problem::Problem;
|
||||||
|
use can::scope::Scope;
|
||||||
|
use can::symbol::Symbol;
|
||||||
|
use collections::ImMap;
|
||||||
|
use ident::{Ident, VariantName};
|
||||||
|
use parse::ast;
|
||||||
|
use region::{Located, Region};
|
||||||
|
|
||||||
|
/// A pattern, including possible problems (e.g. shadowing) so that
|
||||||
|
/// codegen can generate a runtime error if this pattern is reached.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Pattern {
|
||||||
|
Identifier(Symbol),
|
||||||
|
Variant(Symbol),
|
||||||
|
AppliedVariant(Symbol, Vec<Located<Pattern>>),
|
||||||
|
IntLiteral(i64),
|
||||||
|
FloatLiteral(f64),
|
||||||
|
ExactString(String),
|
||||||
|
EmptyRecordLiteral,
|
||||||
|
Underscore,
|
||||||
|
|
||||||
|
// Runtime Exceptions
|
||||||
|
Shadowed(Located<Ident>),
|
||||||
|
UnrecognizedVariant(Located<VariantName>),
|
||||||
|
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||||
|
UnsupportedPattern(Region),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Different patterns are supported in different circumstances.
|
||||||
|
/// For example, case branches can pattern match on number literals, but
|
||||||
|
/// assignments and function args can't. Underscore is supported in function
|
||||||
|
/// arg patterns and in case branch patterns, but not in assignments.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum PatternType {
|
||||||
|
Assignment,
|
||||||
|
FunctionArg,
|
||||||
|
CaseBranch,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn canonicalize_pattern(
|
||||||
|
_env: &mut Env,
|
||||||
|
_scope: &mut Scope,
|
||||||
|
_pattern_type: &PatternType,
|
||||||
|
_loc_pattern: &Located<ast::Pattern>,
|
||||||
|
_shadowable_idents: &mut ImMap<Ident, (Symbol, Region)>,
|
||||||
|
) -> Located<Pattern> {
|
||||||
|
panic!("TODO restore can_pattern");
|
||||||
|
//use can::ast::Pattern::*;
|
||||||
|
|
||||||
|
//let region = loc_pattern.region.clone();
|
||||||
|
//let pattern = match &loc_pattern.value {
|
||||||
|
// &Identifier(ref name) => {
|
||||||
|
// let unqualified_ident = Ident::Unqualified(name.to_string());
|
||||||
|
|
||||||
|
// // We use shadowable_idents for this, and not scope, because for assignments
|
||||||
|
// // they are different. When canonicalizing a particular assignment, that new
|
||||||
|
// // ident is in scope (for recursion) but not shadowable.
|
||||||
|
// //
|
||||||
|
// // For example, when canonicalizing (fibonacci = ...), `fibonacci` should be in scope
|
||||||
|
// // so that it can refer to itself without getting a naming problem, but it should not
|
||||||
|
// // be in the collection of shadowable idents because you can't shadow yourself!
|
||||||
|
// match shadowable_idents.get(&unqualified_ident) {
|
||||||
|
// Some((_, region)) => {
|
||||||
|
// let loc_shadowed_ident = Located {
|
||||||
|
// region: region.clone(),
|
||||||
|
// value: unqualified_ident,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// // This is already in scope, meaning it's about to be shadowed.
|
||||||
|
// // Shadowing is not allowed!
|
||||||
|
// env.problem(Problem::Shadowing(loc_shadowed_ident.clone()));
|
||||||
|
|
||||||
|
// // Change this Pattern to a Shadowed variant, so that
|
||||||
|
// // codegen knows to generate a runtime exception here.
|
||||||
|
// Pattern::Shadowed(loc_shadowed_ident)
|
||||||
|
// }
|
||||||
|
// None => {
|
||||||
|
// // Make sure we aren't shadowing something in the home module's scope.
|
||||||
|
// let qualified_ident =
|
||||||
|
// Ident::Qualified(env.home.clone(), unqualified_ident.name());
|
||||||
|
|
||||||
|
// match scope.idents.get(&qualified_ident) {
|
||||||
|
// Some((_, region)) => {
|
||||||
|
// let loc_shadowed_ident = Located {
|
||||||
|
// region: region.clone(),
|
||||||
|
// value: qualified_ident,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// // This is already in scope, meaning it's about to be shadowed.
|
||||||
|
// // Shadowing is not allowed!
|
||||||
|
// env.problem(Problem::Shadowing(loc_shadowed_ident.clone()));
|
||||||
|
|
||||||
|
// // Change this Pattern to a Shadowed variant, so that
|
||||||
|
// // codegen knows to generate a runtime exception here.
|
||||||
|
// Pattern::Shadowed(loc_shadowed_ident)
|
||||||
|
// }
|
||||||
|
// None => {
|
||||||
|
// let new_ident = qualified_ident.clone();
|
||||||
|
// let new_name = qualified_ident.name();
|
||||||
|
// let symbol = scope.symbol(&new_name);
|
||||||
|
|
||||||
|
// // This is a fresh identifier that wasn't already in scope.
|
||||||
|
// // Add it to scope!
|
||||||
|
// let symbol_and_region = (symbol.clone(), region.clone());
|
||||||
|
|
||||||
|
// // Add this to both scope.idents *and* shadowable_idents.
|
||||||
|
// // The latter is relevant when recursively canonicalizing Variant patterns,
|
||||||
|
// // which can bring multiple new idents into scope. For example, it's important
|
||||||
|
// // that we catch (Blah foo foo) as being an example of shadowing.
|
||||||
|
// scope
|
||||||
|
// .idents
|
||||||
|
// .insert(new_ident.clone(), symbol_and_region.clone());
|
||||||
|
// shadowable_idents.insert(new_ident, symbol_and_region);
|
||||||
|
|
||||||
|
// Pattern::Identifier(symbol)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &AppliedVariant((ref loc_name, ref loc_args)) => {
|
||||||
|
// // Canonicalize the variant's arguments.
|
||||||
|
// let mut can_args: Vec<Located<Pattern>> = Vec::new();
|
||||||
|
|
||||||
|
// for loc_arg in loc_args {
|
||||||
|
// let loc_can_arg =
|
||||||
|
// canonicalize_pattern(env, scope, pattern_type, &loc_arg, shadowable_idents);
|
||||||
|
|
||||||
|
// can_args.push(loc_can_arg);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Canonicalize the variant's name.
|
||||||
|
// let symbol = Symbol::from_variant(&loc_name.value, &env.home);
|
||||||
|
|
||||||
|
// if env.variants.contains_key(&symbol) {
|
||||||
|
// // No problems; the qualified variant name was in scope!
|
||||||
|
// Pattern::AppliedVariant(symbol, can_args)
|
||||||
|
// } else {
|
||||||
|
// // We couldn't find the variant name in scope. NAMING PROBLEM!
|
||||||
|
// env.problem(Problem::UnrecognizedVariant(loc_name.clone()));
|
||||||
|
|
||||||
|
// Pattern::UnrecognizedVariant(loc_name.clone())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &Variant(ref loc_name) => {
|
||||||
|
// // Canonicalize the variant's name.
|
||||||
|
// let symbol = Symbol::from_variant(&loc_name.value, &env.home);
|
||||||
|
|
||||||
|
// if env.variants.contains_key(&symbol) {
|
||||||
|
// // No problems; the qualified variant name was in scope! Pattern::Variant(symbol)
|
||||||
|
// } else {
|
||||||
|
// // We couldn't find the variant name in scope. NAMING PROBLEM!
|
||||||
|
// env.problem(Problem::UnrecognizedVariant(loc_name.clone()));
|
||||||
|
|
||||||
|
// Pattern::UnrecognizedVariant(loc_name.clone())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &IntLiteral(ref num) => match pattern_type {
|
||||||
|
// CaseBranch => Pattern::IntLiteral(*num),
|
||||||
|
// ptype @ Assignment | ptype @ FunctionArg => {
|
||||||
|
// unsupported_pattern(env, *ptype, region)
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
|
||||||
|
// &FloatLiteral(ref num) => match pattern_type {
|
||||||
|
// CaseBranch => Pattern::FloatLiteral(*num),
|
||||||
|
// ptype @ Assignment | ptype @ FunctionArg => {
|
||||||
|
// unsupported_pattern(env, *ptype, region)
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
|
||||||
|
// &ExactString(ref string) => match pattern_type {
|
||||||
|
// CaseBranch => Pattern::ExactString(string.clone()),
|
||||||
|
// ptype @ Assignment | ptype @ FunctionArg => {
|
||||||
|
// unsupported_pattern(env, *ptype, region)
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
|
||||||
|
// &Underscore => match pattern_type {
|
||||||
|
// CaseBranch | FunctionArg => Pattern::Underscore,
|
||||||
|
// Assignment => unsupported_pattern(env, Assignment, region),
|
||||||
|
// },
|
||||||
|
|
||||||
|
// &EmptyRecordLiteral => Pattern::EmptyRecordLiteral,
|
||||||
|
//};
|
||||||
|
|
||||||
|
//Located {
|
||||||
|
// region,
|
||||||
|
// value: pattern,
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't
|
||||||
|
/// assign to Int patterns), report it to Env and return an UnsupportedPattern runtime error pattern.
|
||||||
|
fn _unsupported_pattern<'a>(env: &mut Env, pattern_type: PatternType, region: Region) -> Pattern {
|
||||||
|
env.problem(Problem::UnsupportedPattern(pattern_type, region.clone()));
|
||||||
|
|
||||||
|
Pattern::UnsupportedPattern(region)
|
||||||
|
}
|
44
src/can/problem.rs
Normal file
44
src/can/problem.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
use can::expr::Expr;
|
||||||
|
use can::pattern::{Pattern, PatternType};
|
||||||
|
use ident::{Ident, VariantName};
|
||||||
|
use operator::Operator;
|
||||||
|
use region::{Located, Region};
|
||||||
|
|
||||||
|
/// Problems that can occur in the course of canonicalization.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Problem {
|
||||||
|
Shadowing(Located<Ident>),
|
||||||
|
UnrecognizedFunctionName(Located<Ident>),
|
||||||
|
UnrecognizedConstant(Located<Ident>),
|
||||||
|
UnrecognizedVariant(Located<VariantName>),
|
||||||
|
UnusedAssignment(Located<Ident>),
|
||||||
|
UnusedArgument(Located<Ident>),
|
||||||
|
PrecedenceProblem(PrecedenceProblem),
|
||||||
|
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||||
|
UnsupportedPattern(PatternType, Region),
|
||||||
|
CircularAssignment(Vec<Located<Ident>>),
|
||||||
|
RuntimeError(RuntimeError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum PrecedenceProblem {
|
||||||
|
BothNonAssociative(Located<Operator>, Located<Operator>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum RuntimeError {
|
||||||
|
InvalidPrecedence(PrecedenceProblem, Box<Located<Expr>>),
|
||||||
|
UnrecognizedFunctionName(Located<Ident>),
|
||||||
|
UnrecognizedConstant(Located<Ident>),
|
||||||
|
UnrecognizedVariant(Located<VariantName>),
|
||||||
|
FloatOutsideRange(Box<str>),
|
||||||
|
IntOutsideRange(Box<str>),
|
||||||
|
InvalidHex(std::num::ParseIntError, Box<str>),
|
||||||
|
InvalidOctal(std::num::ParseIntError, Box<str>),
|
||||||
|
InvalidBinary(std::num::ParseIntError, Box<str>),
|
||||||
|
CircularAssignment(
|
||||||
|
Vec<Located<Ident>>,
|
||||||
|
Vec<(Located<Pattern>, Located<Expr>)>,
|
||||||
|
Box<Located<Expr>>,
|
||||||
|
),
|
||||||
|
}
|
72
src/can/procedure.rs
Normal file
72
src/can/procedure.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
use can::expr::Expr;
|
||||||
|
use can::pattern::Pattern;
|
||||||
|
use can::symbol::Symbol;
|
||||||
|
use collections::ImSet;
|
||||||
|
use region::{Located, Region};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Procedure {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub is_self_tail_recursive: bool,
|
||||||
|
pub definition: Region,
|
||||||
|
pub args: Vec<Located<Pattern>>,
|
||||||
|
pub body: Located<Expr>,
|
||||||
|
pub references: References,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Procedure {
|
||||||
|
pub fn new(
|
||||||
|
definition: Region,
|
||||||
|
args: Vec<Located<Pattern>>,
|
||||||
|
body: Located<Expr>,
|
||||||
|
references: References,
|
||||||
|
) -> Procedure {
|
||||||
|
Procedure {
|
||||||
|
name: None,
|
||||||
|
is_self_tail_recursive: false,
|
||||||
|
definition,
|
||||||
|
args,
|
||||||
|
body,
|
||||||
|
references,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// These are all ordered sets because they end up getting traversed in a graph search
|
||||||
|
/// to determine how assignments shuold be ordered. We want builds to be reproducible,
|
||||||
|
/// so it's important that building the same code gives the same order every time!
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct References {
|
||||||
|
pub locals: ImSet<Symbol>,
|
||||||
|
pub globals: ImSet<Symbol>,
|
||||||
|
pub variants: ImSet<Symbol>,
|
||||||
|
pub calls: ImSet<Symbol>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl References {
|
||||||
|
pub fn new() -> References {
|
||||||
|
References {
|
||||||
|
locals: ImSet::default(),
|
||||||
|
globals: ImSet::default(),
|
||||||
|
variants: ImSet::default(),
|
||||||
|
calls: ImSet::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn union(mut self, other: References) -> Self {
|
||||||
|
self.locals = self.locals.union(other.locals);
|
||||||
|
self.globals = self.globals.union(other.globals);
|
||||||
|
self.variants = self.variants.union(other.variants);
|
||||||
|
self.calls = self.calls.union(other.calls);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_local(&self, symbol: &Symbol) -> bool {
|
||||||
|
self.locals.contains(symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_variant(&self, symbol: &Symbol) -> bool {
|
||||||
|
self.variants.contains(symbol)
|
||||||
|
}
|
||||||
|
}
|
35
src/can/scope.rs
Normal file
35
src/can/scope.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use can::symbol::Symbol;
|
||||||
|
use collections::ImMap;
|
||||||
|
use ident::Ident;
|
||||||
|
use region::Region;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Scope {
|
||||||
|
pub idents: ImMap<Ident, (Symbol, Region)>,
|
||||||
|
symbol_prefix: String,
|
||||||
|
next_unique_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scope {
|
||||||
|
pub fn new(symbol_prefix: String, declared_idents: ImMap<Ident, (Symbol, Region)>) -> Scope {
|
||||||
|
Scope {
|
||||||
|
symbol_prefix,
|
||||||
|
|
||||||
|
// This is used to generate unique names for anonymous closures.
|
||||||
|
// It always begins at 0.
|
||||||
|
next_unique_id: 0,
|
||||||
|
|
||||||
|
idents: declared_idents,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn symbol(&self, name: &str) -> Symbol {
|
||||||
|
Symbol::new(&self.symbol_prefix, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gen_unique_symbol(&mut self) -> Symbol {
|
||||||
|
self.next_unique_id = self.next_unique_id + 1;
|
||||||
|
|
||||||
|
Symbol::new(&self.symbol_prefix, &self.next_unique_id.to_string())
|
||||||
|
}
|
||||||
|
}
|
445
src/can/string.rs
Normal file
445
src/can/string.rs
Normal file
|
@ -0,0 +1,445 @@
|
||||||
|
// use bumpalo::collections::string::String;
|
||||||
|
// use bumpalo::collections::vec::Vec;
|
||||||
|
use bumpalo::Bump;
|
||||||
|
use parse::ast::Expr;
|
||||||
|
// use parse::ast::{Attempting, Expr};
|
||||||
|
// use parse::ident;
|
||||||
|
// use parse::parser::{unexpected, unexpected_eof, Fail, Parser, State};
|
||||||
|
// use parse::problems::{Problem, Problems};
|
||||||
|
// use region::{Loc, Region};
|
||||||
|
use region::Region;
|
||||||
|
// use std::char;
|
||||||
|
// use std::iter::Peekable;
|
||||||
|
|
||||||
|
pub fn canonical_string_literal<'a>(_arena: &Bump, _raw: &'a str, _region: Region) -> Expr<'a> {
|
||||||
|
panic!("TODO restore canonicalization");
|
||||||
|
}
|
||||||
|
// let mut problems = std::vec::Vec::new();
|
||||||
|
|
||||||
|
// // Stores the accumulated string characters
|
||||||
|
// let mut buf = String::new_in(arena);
|
||||||
|
|
||||||
|
// // This caches the total string length of interpolated_pairs. Every
|
||||||
|
// // time we add a new pair to interpolated_pairs, we increment this
|
||||||
|
// // by the sum of whatever we parsed in order to obtain that pair.
|
||||||
|
// let mut buf_col_offset: usize = 0;
|
||||||
|
|
||||||
|
// // Stores interpolated identifiers, if any.
|
||||||
|
// let mut interpolated_pairs = Vec::new_in(arena);
|
||||||
|
|
||||||
|
// let mut chars = raw.chars();
|
||||||
|
|
||||||
|
// while let Some(ch) = chars.next() {
|
||||||
|
// match ch {
|
||||||
|
// // If it's a backslash, escape things.
|
||||||
|
// '\\' => match chars.next() {
|
||||||
|
// Some(next_ch) => {
|
||||||
|
// if let Some(ident) = handle_escaped_char(
|
||||||
|
// arena,
|
||||||
|
// &state,
|
||||||
|
// next_ch,
|
||||||
|
// &mut chars,
|
||||||
|
// &mut buf,
|
||||||
|
// &mut problems,
|
||||||
|
// )? {
|
||||||
|
// let expr = Expr::Var(ident);
|
||||||
|
|
||||||
|
// // +2 for `\(` and then another +1 for `)` at the end
|
||||||
|
// let parsed_length = buf.len() + 2 + ident.len() + 1;
|
||||||
|
|
||||||
|
// // Casting should always succeed in this section, because
|
||||||
|
// // if this string literal overflowed our maximum
|
||||||
|
// // line length, that would have already happened back
|
||||||
|
// // in the parsing step, and we never would have reached
|
||||||
|
// // this code. Still, debug_assert that they won't!
|
||||||
|
// debug_assert!(buf_col_offset <= std::u16::MAX as usize);
|
||||||
|
// debug_assert!(ident.len() <= std::u16::MAX as usize);
|
||||||
|
// debug_assert!((parsed_length - ident.len() - 1) <= std::u16::MAX as usize);
|
||||||
|
|
||||||
|
// let start_line = state.line;
|
||||||
|
|
||||||
|
// // Subtract ident length and another 1 for the `)`
|
||||||
|
// let start_col = state.column
|
||||||
|
// + buf_col_offset as u16
|
||||||
|
// + (parsed_length - ident.len() - 1) as u16;
|
||||||
|
// let ident_region = Region {
|
||||||
|
// start_line,
|
||||||
|
// start_col,
|
||||||
|
// end_line: start_line,
|
||||||
|
// end_col: start_col + ident.len() as u16 - 1,
|
||||||
|
// };
|
||||||
|
// let loc_expr = Loc {
|
||||||
|
// region: ident_region,
|
||||||
|
// value: expr,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// // Push the accumulated string into the pairs list,
|
||||||
|
// // along with the ident that came after it.
|
||||||
|
// interpolated_pairs.push((buf.into_bump_str(), loc_expr));
|
||||||
|
|
||||||
|
// // Reset the buffer so we start working on a new string.
|
||||||
|
// buf = String::new_in(arena);
|
||||||
|
|
||||||
|
// // Advance the cached offset of how many chars we've parsed,
|
||||||
|
// // so the next time we see an interpolated ident, we can
|
||||||
|
// // correctly calculate its region.
|
||||||
|
// buf_col_offset += parsed_length;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// None => {
|
||||||
|
// problems.push(loc_char(Problem::TrailingBackslash, &state, buf.len()));
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// '\t' => {
|
||||||
|
// // Tabs are syntax errors.
|
||||||
|
// problems.push(loc_char(Problem::Tab, &state, buf.len()));
|
||||||
|
// }
|
||||||
|
// '\r' => {
|
||||||
|
// // Carriage returns aren't allowed in string literals.
|
||||||
|
// problems.push(loc_char(Problem::CarriageReturn, &state, buf.len()));
|
||||||
|
// }
|
||||||
|
// normal_char => buf.push(normal_char),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // We ran out of characters; this is the end of the string!
|
||||||
|
// if problems.is_empty() {
|
||||||
|
// let final_str = buf.into_bump_str();
|
||||||
|
|
||||||
|
// if interpolated_pairs.is_empty() {
|
||||||
|
// Expr::Str(final_str)
|
||||||
|
// } else {
|
||||||
|
// let tuple_ref = arena.alloc((interpolated_pairs.into_bump_slice(), final_str));
|
||||||
|
|
||||||
|
// Expr::InterpolatedStr(tuple_ref)
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// Expr::MalformedStr(problems.into_boxed_slice())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn loc_char<'a, V>(value: V, state: &State<'a>, buf_len: usize) -> Loc<V> {
|
||||||
|
// let start_line = state.line;
|
||||||
|
// let start_col = state.column + buf_len as u16;
|
||||||
|
// let end_line = start_line;
|
||||||
|
// // All invalid chars should have a length of 1
|
||||||
|
// let end_col = state.column + 1;
|
||||||
|
|
||||||
|
// let region = Region {
|
||||||
|
// start_line,
|
||||||
|
// start_col,
|
||||||
|
// end_line,
|
||||||
|
// end_col,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// Loc { region, value }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn loc_escaped_char<'a, V>(value: V, state: &State<'a>, buf_len: usize) -> Loc<V> {
|
||||||
|
// let start_line = state.line;
|
||||||
|
// let start_col = state.column + buf_len as u16;
|
||||||
|
// let end_line = start_line;
|
||||||
|
// // escapes should all be 2 chars long
|
||||||
|
// let end_col = state.column + 1;
|
||||||
|
|
||||||
|
// let region = Region {
|
||||||
|
// start_line,
|
||||||
|
// start_col,
|
||||||
|
// end_line,
|
||||||
|
// end_col,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// Loc { region, value }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn loc_escaped_unicode<'a, V>(
|
||||||
|
// value: V,
|
||||||
|
// state: &State<'a>,
|
||||||
|
// buf_len: usize,
|
||||||
|
// hex_str_len: usize,
|
||||||
|
// ) -> Loc<V> {
|
||||||
|
// let start_line = state.line;
|
||||||
|
// // +1 due to the `"` which precedes buf.
|
||||||
|
// let start_col = state.column + buf_len as u16 + 1;
|
||||||
|
// let end_line = start_line;
|
||||||
|
// // +3 due to the `\u{` and another + 1 due to the `}`
|
||||||
|
// // -1 to prevent overshooting because end col is inclusive.
|
||||||
|
// let end_col = start_col + 3 + hex_str_len as u16 + 1 - 1;
|
||||||
|
|
||||||
|
// let region = Region {
|
||||||
|
// start_line,
|
||||||
|
// start_col,
|
||||||
|
// end_line,
|
||||||
|
// end_col,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// Loc { region, value }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[inline(always)]
|
||||||
|
// fn handle_escaped_char<'a, I>(
|
||||||
|
// arena: &'a Bump,
|
||||||
|
// state: &State<'a>,
|
||||||
|
// ch: char,
|
||||||
|
// chars: &mut Peekable<I>,
|
||||||
|
// buf: &mut String<'a>,
|
||||||
|
// problems: &mut Problems,
|
||||||
|
// ) -> Result<Option<&'a str>, (Fail, State<'a>)>
|
||||||
|
// where
|
||||||
|
// I: Iterator<Item = char>,
|
||||||
|
// {
|
||||||
|
// match ch {
|
||||||
|
// '\\' => buf.push('\\'),
|
||||||
|
// '"' => buf.push('"'),
|
||||||
|
// 't' => buf.push('\t'),
|
||||||
|
// 'n' => buf.push('\n'),
|
||||||
|
// 'r' => buf.push('\r'),
|
||||||
|
// '0' => buf.push('\0'), // We explicitly support null characters, as we
|
||||||
|
// // can't be sure we won't receive them from Rust.
|
||||||
|
// 'u' => handle_escaped_unicode(arena, &state, chars, buf, problems)?,
|
||||||
|
// '(' => {
|
||||||
|
// let ident = parse_interpolated_ident(arena, state, chars)?;
|
||||||
|
|
||||||
|
// return Ok(Some(ident));
|
||||||
|
// }
|
||||||
|
// '\t' => {
|
||||||
|
// // Report and continue.
|
||||||
|
// // Tabs are syntax errors, but maybe the rest of the string is fine!
|
||||||
|
// problems.push(loc_escaped_char(Problem::Tab, &state, buf.len()));
|
||||||
|
// }
|
||||||
|
// '\r' => {
|
||||||
|
// // Report and continue.
|
||||||
|
// // Carriage returns aren't allowed in string literals,
|
||||||
|
// // but maybe the rest of the string is fine!
|
||||||
|
// problems.push(loc_escaped_char(Problem::CarriageReturn, &state, buf.len()));
|
||||||
|
// }
|
||||||
|
// '\n' => {
|
||||||
|
// // Report and bail out.
|
||||||
|
// // We can't safely assume where the string was supposed to end.
|
||||||
|
// problems.push(loc_escaped_char(
|
||||||
|
// Problem::NewlineInLiteral,
|
||||||
|
// &state,
|
||||||
|
// buf.len(),
|
||||||
|
// ));
|
||||||
|
|
||||||
|
// return Err(unexpected_eof(
|
||||||
|
// buf.len(),
|
||||||
|
// Attempting::UnicodeEscape,
|
||||||
|
// state.clone(),
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
// _ => {
|
||||||
|
// // Report and continue.
|
||||||
|
// // An unsupported escaped char (e.g. \q) shouldn't halt parsing.
|
||||||
|
// problems.push(loc_escaped_char(
|
||||||
|
// Problem::UnsupportedEscapedChar,
|
||||||
|
// &state,
|
||||||
|
// buf.len(),
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Ok(None)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[inline(always)]
|
||||||
|
// fn handle_escaped_unicode<'a, I>(
|
||||||
|
// arena: &'a Bump,
|
||||||
|
// state: &State<'a>,
|
||||||
|
// chars: &mut Peekable<I>,
|
||||||
|
// buf: &mut String<'a>,
|
||||||
|
// problems: &mut Problems,
|
||||||
|
// ) -> Result<(), (Fail, State<'a>)>
|
||||||
|
// where
|
||||||
|
// I: Iterator<Item = char>,
|
||||||
|
// {
|
||||||
|
// // \u{00A0} is how you specify a Unicode code point,
|
||||||
|
// // so we should always see a '{' next.
|
||||||
|
// if chars.next() != Some('{') {
|
||||||
|
// let start_line = state.line;
|
||||||
|
// // +1 due to the `"` which precedes buf
|
||||||
|
// let start_col = state.column + 1 + buf.len() as u16;
|
||||||
|
// let end_line = start_line;
|
||||||
|
|
||||||
|
// // All we parsed was `\u`, so end on the column after `\`'s column.
|
||||||
|
// let end_col = start_col + 1;
|
||||||
|
|
||||||
|
// let region = Region {
|
||||||
|
// start_line,
|
||||||
|
// start_col,
|
||||||
|
// end_line,
|
||||||
|
// end_col,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// problems.push(Loc {
|
||||||
|
// region,
|
||||||
|
// value: Problem::NoUnicodeDigits,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // The rest of the string literal might be fine. Keep parsing!
|
||||||
|
// return Ok(());
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Record the point in the string literal where we started parsing `\u`
|
||||||
|
// let start_of_unicode = buf.len();
|
||||||
|
|
||||||
|
// // Stores the accumulated unicode digits
|
||||||
|
// let mut hex_str = String::new_in(arena);
|
||||||
|
|
||||||
|
// while let Some(hex_char) = chars.next() {
|
||||||
|
// match hex_char {
|
||||||
|
// '}' => {
|
||||||
|
// // Done! Validate and add it to the buffer.
|
||||||
|
// match u32::from_str_radix(&hex_str, 16) {
|
||||||
|
// Ok(code_pt) => {
|
||||||
|
// if code_pt > 0x10FFFF {
|
||||||
|
// let start_line = state.line;
|
||||||
|
// // +1 due to the `"` which precedes buf
|
||||||
|
// // +3 due to the `\u{` which precedes the hex digits
|
||||||
|
// let start_col = state.column + 1 + buf.len() as u16 + 3;
|
||||||
|
// let end_line = start_line;
|
||||||
|
|
||||||
|
// // We want to underline only the number. That's the error!
|
||||||
|
// // -1 because we want to end on the last digit, not
|
||||||
|
// // overshoot it.
|
||||||
|
// let end_col = start_col + hex_str.len() as u16 - 1;
|
||||||
|
|
||||||
|
// let region = Region {
|
||||||
|
// start_line,
|
||||||
|
// start_col,
|
||||||
|
// end_line,
|
||||||
|
// end_col,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// problems.push(Loc {
|
||||||
|
// region,
|
||||||
|
// value: Problem::UnicodeCodePointTooLarge,
|
||||||
|
// });
|
||||||
|
// } else {
|
||||||
|
// // If it all checked out, add it to
|
||||||
|
// // the main buffer.
|
||||||
|
// match char::from_u32(code_pt) {
|
||||||
|
// Some(ch) => buf.push(ch),
|
||||||
|
// None => {
|
||||||
|
// problems.push(loc_escaped_unicode(
|
||||||
|
// Problem::InvalidUnicodeCodePoint,
|
||||||
|
// &state,
|
||||||
|
// start_of_unicode,
|
||||||
|
// hex_str.len(),
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// Err(_) => {
|
||||||
|
// let problem = if hex_str.is_empty() {
|
||||||
|
// Problem::NoUnicodeDigits
|
||||||
|
// } else {
|
||||||
|
// Problem::NonHexCharsInUnicodeCodePoint
|
||||||
|
// };
|
||||||
|
|
||||||
|
// problems.push(loc_escaped_unicode(
|
||||||
|
// problem,
|
||||||
|
// &state,
|
||||||
|
// start_of_unicode,
|
||||||
|
// hex_str.len(),
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // We are now done processing the unicode portion of the string,
|
||||||
|
// // so exit the loop without further advancing the iterator.
|
||||||
|
// return Ok(());
|
||||||
|
// }
|
||||||
|
// '\t' => {
|
||||||
|
// // Report and continue.
|
||||||
|
// // Tabs are syntax errors, but maybe the rest of the string is fine!
|
||||||
|
// problems.push(loc_escaped_unicode(
|
||||||
|
// Problem::Tab,
|
||||||
|
// &state,
|
||||||
|
// start_of_unicode,
|
||||||
|
// hex_str.len(),
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
// '\r' => {
|
||||||
|
// // Report and continue.
|
||||||
|
// // Carriage returns aren't allowed in string literals,
|
||||||
|
// // but maybe the rest of the string is fine!
|
||||||
|
// problems.push(loc_escaped_unicode(
|
||||||
|
// Problem::CarriageReturn,
|
||||||
|
// &state,
|
||||||
|
// start_of_unicode,
|
||||||
|
// hex_str.len(),
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
// '\n' => {
|
||||||
|
// // Report and bail out.
|
||||||
|
// // We can't safely assume where the string was supposed to end.
|
||||||
|
// problems.push(loc_escaped_unicode(
|
||||||
|
// Problem::NewlineInLiteral,
|
||||||
|
// &state,
|
||||||
|
// start_of_unicode,
|
||||||
|
// hex_str.len(),
|
||||||
|
// ));
|
||||||
|
|
||||||
|
// return Err(unexpected_eof(
|
||||||
|
// buf.len(),
|
||||||
|
// Attempting::UnicodeEscape,
|
||||||
|
// state.clone(),
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
// normal_char => hex_str.push(normal_char),
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // If we're about to hit the end of the string, and we didn't already
|
||||||
|
// // complete parsing a valid unicode escape sequence, this is a malformed
|
||||||
|
// // escape sequence - it wasn't terminated!
|
||||||
|
// if chars.peek() == Some(&'"') {
|
||||||
|
// // Record a problem and exit the loop early, so the string literal
|
||||||
|
// // parsing logic can consume the quote and do its job as normal.
|
||||||
|
// let start_line = state.line;
|
||||||
|
// // +1 due to the `"` which precedes buf.
|
||||||
|
// let start_col = state.column + buf.len() as u16 + 1;
|
||||||
|
// let end_line = start_line;
|
||||||
|
// // +3 due to the `\u{`
|
||||||
|
// // -1 to prevent overshooting because end col is inclusive.
|
||||||
|
// let end_col = start_col + 3 + hex_str.len() as u16 - 1;
|
||||||
|
|
||||||
|
// let region = Region {
|
||||||
|
// start_line,
|
||||||
|
// start_col,
|
||||||
|
// end_line,
|
||||||
|
// end_col,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// problems.push(Loc {
|
||||||
|
// region,
|
||||||
|
// value: Problem::MalformedEscapedUnicode,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return Ok(());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[inline(always)]
|
||||||
|
// fn parse_interpolated_ident<'a, I>(
|
||||||
|
// arena: &'a Bump,
|
||||||
|
// state: &State<'a>,
|
||||||
|
// chars: &mut Peekable<I>,
|
||||||
|
// ) -> Result<&'a str, (Fail, State<'a>)>
|
||||||
|
// where
|
||||||
|
// I: Iterator<Item = char>,
|
||||||
|
// {
|
||||||
|
// // This will return Err on invalid identifiers like "if"
|
||||||
|
// let ((string, next_char), state) = ident::parse_into(arena, chars, state.clone())?;
|
||||||
|
|
||||||
|
// // Make sure we got a closing ) to end the interpolation.
|
||||||
|
// match next_char {
|
||||||
|
// Some(')') => Ok(string),
|
||||||
|
// Some(ch) => Err(unexpected(ch, 0, state, Attempting::InterpolatedString)),
|
||||||
|
// None => Err(unexpected_eof(0, Attempting::InterpolatedString, state)),
|
||||||
|
// }
|
||||||
|
// }
|
28
src/can/symbol.rs
Normal file
28
src/can/symbol.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
use ident::VariantName;
|
||||||
|
|
||||||
|
/// A globally unique identifier, used for both vars and variants.
|
||||||
|
/// It will be used directly in code gen.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
pub struct Symbol(String);
|
||||||
|
|
||||||
|
impl Symbol {
|
||||||
|
pub fn new(prefix: &str, name: &str) -> Symbol {
|
||||||
|
Symbol(format!("{}{}", prefix, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_variant(variant_name: &VariantName, home: &str) -> Symbol {
|
||||||
|
match &variant_name {
|
||||||
|
&VariantName::Unqualified(ref name) => Symbol::new(home, name),
|
||||||
|
|
||||||
|
&VariantName::Qualified(ref path, ref name) => Symbol::new(path, name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<String> for Symbol {
|
||||||
|
fn into(self) -> String {
|
||||||
|
let Symbol(string) = self;
|
||||||
|
|
||||||
|
string
|
||||||
|
}
|
||||||
|
}
|
1710
src/canonicalize.rs
1710
src/canonicalize.rs
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,7 @@
|
||||||
use canonicalize::Expr::{self, *};
|
use can::expr::Expr::{self, *};
|
||||||
use canonicalize::{Pattern, Procedure, Symbol};
|
use can::pattern::Pattern;
|
||||||
|
use can::procedure::Procedure;
|
||||||
|
use can::symbol::Symbol;
|
||||||
use collections::ImMap;
|
use collections::ImMap;
|
||||||
use operator::{ArgSide, Operator};
|
use operator::{ArgSide, Operator};
|
||||||
use region::{Located, Region};
|
use region::{Located, Region};
|
||||||
|
@ -93,13 +95,14 @@ fn constrain_op(
|
||||||
) -> Constraint {
|
) -> Constraint {
|
||||||
let op = loc_op.value;
|
let op = loc_op.value;
|
||||||
let op_types = Type::for_operator(op);
|
let op_types = Type::for_operator(op);
|
||||||
let fn_var = subs.mk_flex_var();
|
// TODO use fn_var
|
||||||
|
let _fn_var = subs.mk_flex_var();
|
||||||
let ret_var = subs.mk_flex_var();
|
let ret_var = subs.mk_flex_var();
|
||||||
let ret_type = Variable(ret_var);
|
let ret_type = Variable(ret_var);
|
||||||
let ret_reason = Reason::OperatorRet(op);
|
let ret_reason = Reason::OperatorRet(op);
|
||||||
let expected_ret_type = ForReason(ret_reason, op_types.ret, region.clone());
|
let expected_ret_type = ForReason(ret_reason, op_types.ret, region.clone());
|
||||||
|
|
||||||
let (l_var, l_con) = constrain_op_arg(
|
let (_l_var, l_con) = constrain_op_arg(
|
||||||
ArgSide::Left,
|
ArgSide::Left,
|
||||||
bound_vars,
|
bound_vars,
|
||||||
subs,
|
subs,
|
||||||
|
@ -107,7 +110,7 @@ fn constrain_op(
|
||||||
op_types.left,
|
op_types.left,
|
||||||
l_loc_expr,
|
l_loc_expr,
|
||||||
);
|
);
|
||||||
let (r_var, r_con) = constrain_op_arg(
|
let (_r_var, r_con) = constrain_op_arg(
|
||||||
ArgSide::Right,
|
ArgSide::Right,
|
||||||
bound_vars,
|
bound_vars,
|
||||||
subs,
|
subs,
|
||||||
|
@ -116,8 +119,8 @@ fn constrain_op(
|
||||||
r_loc_expr,
|
r_loc_expr,
|
||||||
);
|
);
|
||||||
|
|
||||||
let vars = vec![fn_var, ret_var, l_var, r_var];
|
|
||||||
// TODO occurs check!
|
// TODO occurs check!
|
||||||
|
// let vars = vec![fn_var, ret_var, l_var, r_var];
|
||||||
// return $ exists (funcVar:resultVar:argVars) $ CAnd ...
|
// return $ exists (funcVar:resultVar:argVars) $ CAnd ...
|
||||||
|
|
||||||
And(vec![
|
And(vec![
|
||||||
|
@ -226,7 +229,7 @@ pub fn constrain_defs(
|
||||||
subs: &mut Subs,
|
subs: &mut Subs,
|
||||||
ret_con: Constraint,
|
ret_con: Constraint,
|
||||||
) -> Constraint {
|
) -> Constraint {
|
||||||
let mut rigid_info = Info::with_capacity(assignments.len());
|
let rigid_info = Info::with_capacity(assignments.len());
|
||||||
let mut flex_info = Info::with_capacity(assignments.len());
|
let mut flex_info = Info::with_capacity(assignments.len());
|
||||||
|
|
||||||
for (loc_pattern, loc_expr) in assignments {
|
for (loc_pattern, loc_expr) in assignments {
|
||||||
|
@ -320,7 +323,7 @@ fn string() -> Type {
|
||||||
builtin_type("String", "String", Vec::new())
|
builtin_type("String", "String", Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn num(var: Variable) -> Type {
|
fn _num(var: Variable) -> Type {
|
||||||
builtin_type("Num", "Num", vec![Type::Variable(var)])
|
builtin_type("Num", "Num", vec![Type::Variable(var)])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,9 +517,9 @@ pub fn constrain_procedure(
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Args {
|
struct Args {
|
||||||
vars: Vec<Variable>,
|
pub vars: Vec<Variable>,
|
||||||
typ: Type,
|
pub typ: Type,
|
||||||
ret_type: Type,
|
pub ret_type: Type,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn constrain_args<I>(args: I, subs: &mut Subs, state: &mut PatternState) -> Args
|
fn constrain_args<I>(args: I, subs: &mut Subs, state: &mut PatternState) -> Args
|
||||||
|
@ -571,7 +574,7 @@ struct PatternState {
|
||||||
|
|
||||||
impl PatternState {
|
impl PatternState {
|
||||||
pub fn add_pattern(&mut self, loc_pattern: Located<Pattern>, expected: Expected<Type>) {
|
pub fn add_pattern(&mut self, loc_pattern: Located<Pattern>, expected: Expected<Type>) {
|
||||||
use canonicalize::Pattern::*;
|
use can::pattern::Pattern::*;
|
||||||
|
|
||||||
let region = loc_pattern.region;
|
let region = loc_pattern.region;
|
||||||
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod parse;
|
|
||||||
pub mod parse_state;
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,100 +0,0 @@
|
||||||
use combine::lib::fmt;
|
|
||||||
|
|
||||||
use combine::stream::state::{Positioner, RangePositioner};
|
|
||||||
use combine::stream::Resetable;
|
|
||||||
|
|
||||||
// Plan:
|
|
||||||
//
|
|
||||||
// 1. Let space parsers check indentation. They should expect indentation to only ever increase (right?) when
|
|
||||||
// doing a many_whitespaces or many1_whitespaces. Multline strings can have separate whitespace parsers.
|
|
||||||
// 2. For any expression that has subexpressions (e.g. ifs, parens, operators) record their indentation levels
|
|
||||||
// by doing .and(position()) followed by .and_then() which says "I can have a declaration inside me as
|
|
||||||
// long as the entire decl is indented more than me."
|
|
||||||
// 3. Make an alternative to RangeStreamOnce where uncons_while barfs on \t (or maybe just do this in whitespaces?)
|
|
||||||
|
|
||||||
/// Struct which represents a position in a source file.
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
|
||||||
pub struct IndentablePosition {
|
|
||||||
/// Current line of the input
|
|
||||||
pub line: u32,
|
|
||||||
/// Current column of the input
|
|
||||||
pub column: u16,
|
|
||||||
|
|
||||||
/// Current indentation level, in columns (so no indent is col 1 - this saves an arithmetic operation.)
|
|
||||||
pub indent_col: u16,
|
|
||||||
|
|
||||||
// true at the beginning of each line, then false after encountering the first nonspace char.
|
|
||||||
pub is_indenting: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
clone_resetable! { () IndentablePosition }
|
|
||||||
|
|
||||||
impl Default for IndentablePosition {
|
|
||||||
fn default() -> Self {
|
|
||||||
IndentablePosition {
|
|
||||||
line: 1,
|
|
||||||
column: 1,
|
|
||||||
indent_col: 1,
|
|
||||||
is_indenting: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for IndentablePosition {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"line: {}, column: {}, indent_col: {}, is_indenting: {}",
|
|
||||||
self.line, self.column, self.indent_col, self.is_indenting
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IndentablePosition {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
IndentablePosition::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Positioner<char> for IndentablePosition {
|
|
||||||
type Position = IndentablePosition;
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn position(&self) -> IndentablePosition {
|
|
||||||
self.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn update(&mut self, item: &char) {
|
|
||||||
match *item {
|
|
||||||
'\n' => {
|
|
||||||
self.column = 1;
|
|
||||||
self.line += 1;
|
|
||||||
self.indent_col = 1;
|
|
||||||
self.is_indenting = true;
|
|
||||||
}
|
|
||||||
' ' => {
|
|
||||||
self.column += 1;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
if self.is_indenting {
|
|
||||||
// As soon as we hit a nonspace char, we're done indenting.
|
|
||||||
// It doesn't count as an indent until we hit a nonspace character though!
|
|
||||||
// Until that point it's still a blank line, not an indented one.
|
|
||||||
self.indent_col = self.column;
|
|
||||||
self.is_indenting = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.column += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> RangePositioner<char, &'a str> for IndentablePosition {
|
|
||||||
fn update_range(&mut self, range: &&'a str) {
|
|
||||||
for c in range.chars() {
|
|
||||||
self.update(&c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
721
src/eval.rs
721
src/eval.rs
|
@ -1,721 +0,0 @@
|
||||||
use expr::{Expr, Ident, Pattern, VariantName};
|
|
||||||
use expr::Pattern::*;
|
|
||||||
use expr;
|
|
||||||
use operator::Operator::*;
|
|
||||||
use operator::Operator;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::fmt;
|
|
||||||
use collections::ImMap;
|
|
||||||
use self::Problem::*;
|
|
||||||
use fraction::Fraction;
|
|
||||||
use region::{Located, Region};
|
|
||||||
|
|
||||||
pub fn eval(expr: Located<Expr>) -> Evaluated {
|
|
||||||
scoped_eval(prepare_for_eval(expr), &ImMap::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_for_eval(expr: Located<Expr>) -> Located<Expr> {
|
|
||||||
expr::apply_precedence_and_associativity(expr).unwrap().map(&apply_pizza)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum Evaluated {
|
|
||||||
// Literals
|
|
||||||
Int(i64),
|
|
||||||
Frac(Fraction),
|
|
||||||
Approx(f64),
|
|
||||||
EmptyStr,
|
|
||||||
Str(String),
|
|
||||||
InterpolatedStr(Vec<(String, Ident)>, String),
|
|
||||||
Char(char),
|
|
||||||
Closure(Vec<Pattern>, Box<Located<Expr>>, Scope),
|
|
||||||
|
|
||||||
// Sum Types
|
|
||||||
ApplyVariant(VariantName, Option<Vec<Evaluated>>),
|
|
||||||
|
|
||||||
// Product Types
|
|
||||||
EmptyRecord,
|
|
||||||
|
|
||||||
// Errors
|
|
||||||
EvalError(Region, Problem)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum Problem {
|
|
||||||
UnrecognizedVarName(String),
|
|
||||||
TypeMismatch(String),
|
|
||||||
ReassignedVarName(String),
|
|
||||||
WrongArity(u32 /* Expected */, u32 /* Provided */),
|
|
||||||
NotEqual, // Used when (for example) a string literal pattern match fails
|
|
||||||
NoBranchesMatched,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Scope = ImMap<String, Rc<Evaluated>>;
|
|
||||||
|
|
||||||
pub fn scoped_eval(expr: Located<Expr>, vars: &Scope) -> Evaluated {
|
|
||||||
use self::Evaluated::*;
|
|
||||||
|
|
||||||
let region = expr.region;
|
|
||||||
|
|
||||||
match expr.value {
|
|
||||||
Expr::Int(num) => Int(num),
|
|
||||||
Expr::EmptyStr => EmptyStr,
|
|
||||||
Expr::Str(string) => Str(string),
|
|
||||||
Expr::Frac(numerator, denominator) => Frac(fraction_from_i64s(numerator, denominator)),
|
|
||||||
Expr::Approx(num) => Approx(num),
|
|
||||||
Expr::Char(ch) => Char(ch),
|
|
||||||
Expr::Closure(args, body) => Closure(args.into_iter().map(|e| e.value).collect(), body, vars.clone()),
|
|
||||||
Expr::EmptyRecord => EmptyRecord,
|
|
||||||
|
|
||||||
// Resolve variable names
|
|
||||||
Expr::Var(ident) => {
|
|
||||||
let ident_name = ident.name();
|
|
||||||
|
|
||||||
match vars.get(&ident_name) {
|
|
||||||
Some(resolved) => (**resolved).clone(),
|
|
||||||
None => EvalError(region, UnrecognizedVarName(ident_name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Expr::InterpolatedStr(pairs, trailing_str) => {
|
|
||||||
let mut output = String::new();
|
|
||||||
|
|
||||||
for (string, loc_ident) in pairs.into_iter() {
|
|
||||||
let ident_name = loc_ident.value.name();
|
|
||||||
|
|
||||||
match vars.get(&ident_name) {
|
|
||||||
Some(resolved) => {
|
|
||||||
match **resolved {
|
|
||||||
Str(ref var_string) => {
|
|
||||||
output.push_str(string.as_str());
|
|
||||||
output.push_str(var_string.as_str());
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
return EvalError(region, TypeMismatch(ident_name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => { return EvalError(region, UnrecognizedVarName(ident_name)); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push_str(trailing_str.as_str());
|
|
||||||
|
|
||||||
Str(output)
|
|
||||||
},
|
|
||||||
|
|
||||||
Expr::Assign(located_pattern, assigned_expr, returned_expr) => {
|
|
||||||
eval_assign(located_pattern, *assigned_expr, *returned_expr, vars)
|
|
||||||
}
|
|
||||||
|
|
||||||
Expr::CallByName(ident, args) => {
|
|
||||||
let ident_name = ident.name();
|
|
||||||
|
|
||||||
let func_expr = match vars.get(&ident_name) {
|
|
||||||
Some(resolved) => (**resolved).clone(),
|
|
||||||
None => EvalError(region, UnrecognizedVarName(ident_name))
|
|
||||||
};
|
|
||||||
|
|
||||||
eval_apply(region, func_expr, args, vars)
|
|
||||||
},
|
|
||||||
|
|
||||||
Expr::ApplyVariant(name, None) => ApplyVariant(name, None),
|
|
||||||
|
|
||||||
Expr::ApplyVariant(name, Some(exprs)) => {
|
|
||||||
ApplyVariant(
|
|
||||||
name,
|
|
||||||
Some(exprs.into_iter().map(|arg| scoped_eval(arg, vars)).collect())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Expr::Apply(func_expr, args) => {
|
|
||||||
eval_apply(region, scoped_eval(*func_expr, vars), args, vars)
|
|
||||||
},
|
|
||||||
|
|
||||||
Expr::Case(condition, branches) => {
|
|
||||||
eval_case(region, scoped_eval(*condition, vars), branches, vars)
|
|
||||||
},
|
|
||||||
|
|
||||||
Expr::Operator(left_arg, op, right_arg) => {
|
|
||||||
eval_operator(
|
|
||||||
region,
|
|
||||||
&scoped_eval(*left_arg, vars),
|
|
||||||
op.value,
|
|
||||||
&scoped_eval(*right_arg, vars)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
Expr::If(condition, if_true, if_false) => {
|
|
||||||
match scoped_eval(*condition, vars) {
|
|
||||||
ApplyVariant(variant_name, None) => {
|
|
||||||
match variant_name.name().as_str() {
|
|
||||||
"True" => scoped_eval(*if_true, vars),
|
|
||||||
"False" => scoped_eval(*if_false, vars),
|
|
||||||
_ => EvalError(region, TypeMismatch("non-Bool used in `if` condition".to_string()))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => EvalError(region, TypeMismatch("non-Bool used in `if` condition".to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval_assign(pattern: Located<Pattern>, assigned_expr: Located<Expr>, returned_expr: Located<Expr>, vars: &Scope) -> Evaluated {
|
|
||||||
use self::Evaluated::*;
|
|
||||||
|
|
||||||
let pattern_region = pattern.region;
|
|
||||||
|
|
||||||
match pattern.value {
|
|
||||||
Identifier(ident) => {
|
|
||||||
let ident_name = ident.name();
|
|
||||||
|
|
||||||
if vars.contains_key(&ident_name) {
|
|
||||||
EvalError(pattern_region, ReassignedVarName(ident_name))
|
|
||||||
} else {
|
|
||||||
// Create a new scope containing the new declaration.
|
|
||||||
let mut new_vars = vars.clone();
|
|
||||||
let evaluated_defn = scoped_eval(assigned_expr, vars);
|
|
||||||
|
|
||||||
new_vars.insert(ident_name, Rc::new(evaluated_defn));
|
|
||||||
|
|
||||||
// Evaluate in_expr with that new scope's variables.
|
|
||||||
scoped_eval(returned_expr, &new_vars)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
Integer(_) => {
|
|
||||||
panic!("You cannot assign integers to other values!");
|
|
||||||
},
|
|
||||||
|
|
||||||
Fraction(_, _) => {
|
|
||||||
panic!("You cannot assign fractions to other values!");
|
|
||||||
},
|
|
||||||
|
|
||||||
Variant(_name, _patterns) => {
|
|
||||||
panic!("Pattern matching on variants is not yet supported!");
|
|
||||||
},
|
|
||||||
|
|
||||||
Underscore => {
|
|
||||||
panic!("Cannot assign to the _ pattern!");
|
|
||||||
},
|
|
||||||
|
|
||||||
Pattern::EmptyRecordLiteral => {
|
|
||||||
panic!("Cannot assign to the {} pattern!");
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn call(region: Region, evaluated: Evaluated, args: Vec<Located<Expr>>) -> Evaluated {
|
|
||||||
eval_apply(region, evaluated, args, &HashMap::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn eval_apply(region: Region, evaluated: Evaluated, args: Vec<Located<Expr>>, vars: &Scope) -> Evaluated {
|
|
||||||
use self::Evaluated::*;
|
|
||||||
|
|
||||||
match evaluated {
|
|
||||||
Closure(arg_patterns, body, closure_vars) => {
|
|
||||||
let combined_vars = vars.clone().union(closure_vars);
|
|
||||||
let evaluated_args =
|
|
||||||
args.into_iter()
|
|
||||||
.map(|arg| scoped_eval(arg, &combined_vars))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
match eval_closure(evaluated_args, arg_patterns, &combined_vars) {
|
|
||||||
Ok(new_vars) => scoped_eval(*body, &new_vars),
|
|
||||||
Err(prob) => EvalError(region, prob)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
val => {
|
|
||||||
EvalError(region, TypeMismatch(format!("Tried to call a non-function: {}", val)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn eval_closure(args: Vec<Evaluated>, arg_patterns: Vec<Pattern>, vars: &Scope)
|
|
||||||
-> Result<Scope, Problem>
|
|
||||||
{
|
|
||||||
if arg_patterns.len() == args.len() {
|
|
||||||
// Create a new scope for the function to use.
|
|
||||||
let mut new_vars = vars.clone();
|
|
||||||
|
|
||||||
for ( arg, pattern ) in args.into_iter().zip(arg_patterns) {
|
|
||||||
pattern_match(&arg, &pattern, &mut new_vars)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(new_vars)
|
|
||||||
} else {
|
|
||||||
Err(WrongArity(arg_patterns.len() as u32, args.len() as u32))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bool_variant(is_true: bool) -> Evaluated {
|
|
||||||
if is_true {
|
|
||||||
Evaluated::ApplyVariant(VariantName::Unqualified("True".to_string()), None)
|
|
||||||
} else {
|
|
||||||
Evaluated::ApplyVariant(VariantName::Unqualified("False".to_string()), None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eq(region: Region, evaluated1: &Evaluated, evaluated2: &Evaluated) -> Evaluated {
|
|
||||||
use self::Evaluated::*;
|
|
||||||
|
|
||||||
match (evaluated1, evaluated2) {
|
|
||||||
// All functions are defined as equal
|
|
||||||
(Closure(_, _, _), Closure(_, _, _)) => bool_variant(true),
|
|
||||||
|
|
||||||
(ApplyVariant(left, None), ApplyVariant(right, None)) => {
|
|
||||||
bool_variant(left == right)
|
|
||||||
},
|
|
||||||
|
|
||||||
(ApplyVariant(left, Some(left_args)), ApplyVariant(right, Some(right_args))) => {
|
|
||||||
bool_variant(left == right && left_args.len() == right_args.len())
|
|
||||||
},
|
|
||||||
|
|
||||||
(ApplyVariant(_, None), ApplyVariant(_, Some(_))) => {
|
|
||||||
bool_variant(false)
|
|
||||||
},
|
|
||||||
|
|
||||||
(ApplyVariant(_, Some(_)), ApplyVariant(_, None)) => {
|
|
||||||
bool_variant(false)
|
|
||||||
},
|
|
||||||
|
|
||||||
(Int(left), Int(right)) => bool_variant(left == right),
|
|
||||||
(Str(left), Str(right)) => bool_variant(left == right),
|
|
||||||
(Char(left), Char(right)) => bool_variant(left == right),
|
|
||||||
(Frac(left), Frac(right)) => bool_variant(left == right),
|
|
||||||
|
|
||||||
(_, _) => EvalError(region, TypeMismatch("tried to use == on two values with incompatible types".to_string())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bool_from_variant_name(name: &VariantName) -> Option<bool> {
|
|
||||||
match name.clone().name().as_str() {
|
|
||||||
"True" => Some(true),
|
|
||||||
"False" => Some(false),
|
|
||||||
_ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn eval_operator(region: Region, left_expr: &Evaluated, op: Operator, right_expr: &Evaluated) -> Evaluated {
|
|
||||||
use self::Evaluated::*;
|
|
||||||
|
|
||||||
// TODO in the future, replace these with named function calls to stdlib
|
|
||||||
match (left_expr, op, right_expr) {
|
|
||||||
// Equals
|
|
||||||
(_, Equals, _) => eq(region, left_expr, right_expr),
|
|
||||||
|
|
||||||
// And
|
|
||||||
(ApplyVariant(left_name, None), And, ApplyVariant(right_name, None)) => {
|
|
||||||
match (bool_from_variant_name(left_name), bool_from_variant_name(right_name)) {
|
|
||||||
(Some(left_bool), Some(right_bool)) => bool_variant(left_bool && right_bool),
|
|
||||||
_ => EvalError(region, TypeMismatch("tried to use && on non-bools".to_string())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(_, And, _) => EvalError(region, TypeMismatch("tried to use && on non-bools".to_string())),
|
|
||||||
|
|
||||||
// Or
|
|
||||||
(ApplyVariant(left_name, None), Or, ApplyVariant(right_name, None)) => {
|
|
||||||
match (bool_from_variant_name(left_name), bool_from_variant_name(right_name)) {
|
|
||||||
(Some(left_bool), Some(right_bool)) => bool_variant(left_bool || right_bool),
|
|
||||||
_ => EvalError(region, TypeMismatch("tried to use && on non-bools".to_string())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(_, Or, _) => EvalError(region, TypeMismatch("tried to use && on non-bools".to_string())),
|
|
||||||
|
|
||||||
// LessThan
|
|
||||||
(Int(left_num), LessThan, Int(right_num)) => bool_variant(left_num < right_num),
|
|
||||||
(Frac(left_num), LessThan, Frac(right_num)) => bool_variant(left_num < right_num),
|
|
||||||
(Int(_), LessThan, Frac(_)) => EvalError(region, TypeMismatch("tried check Frac < Int. Explicitly convert them to the same type first!".to_string())),
|
|
||||||
(Frac(_), LessThan, Int(_)) => EvalError(region,TypeMismatch("tried check Int < Frac. Explicitly convert them to the same type first!".to_string())),
|
|
||||||
(_, LessThan, _) => EvalError(region, TypeMismatch("tried to check if one non-number < another non-number".to_string())),
|
|
||||||
|
|
||||||
// LessThanOrEq
|
|
||||||
(Int(left_num), LessThanOrEq, Int(right_num)) => bool_variant(left_num <= right_num),
|
|
||||||
(Frac(left_num), LessThanOrEq, Frac(right_num)) => bool_variant(left_num <= right_num),
|
|
||||||
(Int(_), LessThanOrEq, Frac(_)) => EvalError(region, TypeMismatch("tried check Frac <= Int. Explicitly convert them to the same type first!".to_string())),
|
|
||||||
(Frac(_), LessThanOrEq, Int(_)) => EvalError(region, TypeMismatch("tried check Int <= Frac. Explicitly convert them to the same type first!".to_string())),
|
|
||||||
(_, LessThanOrEq, _) => EvalError(region, TypeMismatch("tried to check if one non-number <= another non-number".to_string())),
|
|
||||||
|
|
||||||
// GreaterThan
|
|
||||||
(Int(left_num), GreaterThan, Int(right_num)) => bool_variant(left_num > right_num),
|
|
||||||
(Frac(left_num), GreaterThan, Frac(right_num)) => bool_variant(left_num > right_num),
|
|
||||||
(Int(_), GreaterThan, Frac(_)) => EvalError(region, TypeMismatch("tried check Frac > Int. Explicitly convert them to the same type first!".to_string())),
|
|
||||||
(Frac(_), GreaterThan, Int(_)) => EvalError(region, TypeMismatch("tried check Int > Frac. Explicitly convert them to the same type first!".to_string())),
|
|
||||||
(_, GreaterThan, _) => EvalError(region, TypeMismatch("tried to check if one non-number > another non-number".to_string())),
|
|
||||||
|
|
||||||
// GreaterThanOrEq
|
|
||||||
(Int(left_num), GreaterThanOrEq, Int(right_num)) => bool_variant(left_num >= right_num),
|
|
||||||
(Frac(left_num), GreaterThanOrEq, Frac(right_num)) => bool_variant(left_num >= right_num),
|
|
||||||
(Int(_), GreaterThanOrEq, Frac(_)) => EvalError(region, TypeMismatch("tried check Frac >= Int. Explicitly convert them to the same type first!".to_string())),
|
|
||||||
(Frac(_), GreaterThanOrEq, Int(_)) => EvalError(region, TypeMismatch("tried check Int >= Frac. Explicitly convert them to the same type first!".to_string())),
|
|
||||||
(_, GreaterThanOrEq, _) => EvalError(region, TypeMismatch("tried to check if one non-number >= another non-number".to_string())),
|
|
||||||
|
|
||||||
// Plus
|
|
||||||
(Int(left_num), Plus, Int(right_num)) => Int(left_num.checked_add(*right_num).unwrap_or_else(|| panic!("Integer overflow on +"))),
|
|
||||||
(Frac(left_num), Plus, Frac(right_num)) => Frac(left_num + right_num),
|
|
||||||
|
|
||||||
(Int(_), Plus, Frac(_)) => EvalError(region, TypeMismatch("tried to add Frac to Int. Explicitly convert them to the same type first!".to_string())),
|
|
||||||
|
|
||||||
(Frac(_), Plus, Int(_)) => EvalError(region, TypeMismatch("tried to add Int to Frac. Explicitly convert them to the same type first!".to_string())),
|
|
||||||
|
|
||||||
(_, Plus, _) => EvalError(region, TypeMismatch("tried to add non-numbers".to_string())),
|
|
||||||
|
|
||||||
// Star
|
|
||||||
(Int(left_num), Star, Int(right_num)) => Int(left_num.checked_mul(*right_num).unwrap_or_else(|| panic!("Integer overflow on *"))),
|
|
||||||
(Frac(left_num), Star, Frac(right_num)) => Frac(left_num * right_num),
|
|
||||||
|
|
||||||
(Int(_), Star, Frac(_)) => EvalError(region, TypeMismatch("tried to multiply Int by Frac. Explicitly convert them to the same type first!".to_string())),
|
|
||||||
|
|
||||||
(Frac(_), Star, Int(_)) => EvalError(region, TypeMismatch("tried to multiply Frac by Int. Explicitly convert them to the same type first!".to_string())),
|
|
||||||
|
|
||||||
(_, Star, _) => EvalError(region, TypeMismatch("tried to multiply non-numbers".to_string())),
|
|
||||||
|
|
||||||
// Minus
|
|
||||||
(Int(left_num), Minus, Int(right_num)) => Int(left_num.checked_sub(*right_num).unwrap_or_else(|| panic!("Integer underflow on -"))),
|
|
||||||
(Frac(left_num), Minus, Frac(right_num)) => Frac(left_num - right_num),
|
|
||||||
|
|
||||||
(Int(_), Minus, Frac(_)) => EvalError(region, TypeMismatch("tried to subtract Frac from Int. Explicitly convert them to the same type first!".to_string())),
|
|
||||||
|
|
||||||
(Frac(_), Minus, Int(_)) => EvalError(region, TypeMismatch("tried to subtract Int from Frac. Explicitly convert them to the same type first!".to_string())),
|
|
||||||
|
|
||||||
(_, Minus, _) => EvalError(region, TypeMismatch("tried to subtract non-numbers".to_string())),
|
|
||||||
|
|
||||||
// Caret
|
|
||||||
(Int(left_num), Caret, Int(right_num)) => Int(left_num.checked_pow(*right_num as u32 /* TODO panic if this cast fails */).unwrap_or_else(|| panic!("Integer underflow on ^"))),
|
|
||||||
(Frac(_), Caret, Frac(_)) => EvalError(region, TypeMismatch("tried to use ^ with a Frac, which is not yet supported on either side of the ^ operator.".to_string())),
|
|
||||||
|
|
||||||
(_, Caret, _) => EvalError(region, TypeMismatch("tried to use ^ on non-numbers".to_string())),
|
|
||||||
|
|
||||||
// Slash
|
|
||||||
(Frac(left_num), Slash, Frac(right_num)) => {
|
|
||||||
let answer = left_num / right_num;
|
|
||||||
|
|
||||||
if answer.is_finite() {
|
|
||||||
ok_variant(Frac(answer))
|
|
||||||
} else {
|
|
||||||
err_variant(ApplyVariant(VariantName::Unqualified("DivisionByZero".to_string()), None))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
(Int(_), Slash, Int(_)) => EvalError(region, TypeMismatch("tried to divide two Int values. Explicitly convert them to Frac values, or use Int division (the // operator).".to_string())),
|
|
||||||
(Approx(_), Slash, Approx(_)) => EvalError(region, TypeMismatch("tried to divide two Approx values. Explicitly convert them to Frac values, or use Approx division (the ~/ operator).".to_string())),
|
|
||||||
(Int(_), Slash, Frac(_)) => EvalError(region, TypeMismatch("tried to divide Int by Frac. Explicitly convert them to the same type first!".to_string())),
|
|
||||||
(Frac(_), Slash, Int(_)) => EvalError(region, TypeMismatch("tried to divide Frac by Int. Explicitly convert them to the same type first!".to_string())),
|
|
||||||
|
|
||||||
(_, Slash, _) => EvalError(region, TypeMismatch("tried to divide non-numbers".to_string())),
|
|
||||||
|
|
||||||
// DoubleSlash
|
|
||||||
(Int(left_num), DoubleSlash, Int(right_num)) => Int(left_num / right_num),
|
|
||||||
|
|
||||||
(Approx(_), DoubleSlash, Approx(_)) => EvalError(region, TypeMismatch("tried to do integer division on two Approx values. Explicitly convert them to Int values, or use Approx division (the ~/ operator).".to_string())),
|
|
||||||
(Frac(_), DoubleSlash, Frac(_)) => EvalError(region, TypeMismatch("tried to do integer division on two Frac values. Explicitly conver them to Int values, or use Frac division (the / operator).".to_string())),
|
|
||||||
(Int(_), DoubleSlash, Frac(_)) => EvalError(region,TypeMismatch("tried to integer-divide Int by Frac".to_string())),
|
|
||||||
(Frac(_), DoubleSlash, Int(_)) => EvalError(region, TypeMismatch("tried to integer-divide Frac by Int".to_string())),
|
|
||||||
|
|
||||||
(_, DoubleSlash, _) => EvalError(region, TypeMismatch("tried to do integer division on two non-numbers".to_string())),
|
|
||||||
|
|
||||||
// TildeSlash
|
|
||||||
(Approx(left_num), TildeSlash, Approx(right_num)) => {
|
|
||||||
let answer = left_num / right_num;
|
|
||||||
|
|
||||||
if answer.is_finite() {
|
|
||||||
ok_variant(Approx(answer))
|
|
||||||
} else {
|
|
||||||
err_variant(ApplyVariant(VariantName::Unqualified("DivisionByZero".to_string()), None))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
(Int(_), TildeSlash, Int(_)) => EvalError(region, TypeMismatch("tried to do Approx division on two Int values. Explicitly convert them to Approx values, or use Int division (the // operator).".to_string())),
|
|
||||||
(Frac(_), TildeSlash, Frac(_)) => EvalError(region, TypeMismatch("tried to do Approx division on two Frac values. Explicitly conver them to Approx values, or use Frac division (the / operator).".to_string())),
|
|
||||||
(Int(_), TildeSlash, Approx(_)) => EvalError(region, TypeMismatch("tried to do Int ~/ Approx. Explicitly convert both to Approx first!".to_string())),
|
|
||||||
(Frac(_), TildeSlash, Approx(_)) => EvalError(region, TypeMismatch("tried to do Frac ~/ Approx. Explicitly convert both to Approx first!".to_string())),
|
|
||||||
|
|
||||||
(Approx(_), TildeSlash, Int(_)) => EvalError(region, TypeMismatch("tried to divide Approx ~/ Int. Explicitly convert both to Approx first!".to_string())),
|
|
||||||
(Approx(_), TildeSlash, Frac(_)) => EvalError(region, TypeMismatch("tried to divide Approx ~/ Frac. Explicitly convert both to Approx first!".to_string())),
|
|
||||||
|
|
||||||
(_, TildeSlash, _) => EvalError(region, TypeMismatch("tried to divide non-numbers".to_string())),
|
|
||||||
|
|
||||||
// Percent
|
|
||||||
(Int(left_num), Percent, Int(right_num)) => Int(left_num % right_num),
|
|
||||||
(Frac(left_num), Percent, Frac(right_num)) => {
|
|
||||||
let answer = left_num % right_num;
|
|
||||||
|
|
||||||
if answer.is_finite() {
|
|
||||||
ok_variant(Frac(answer))
|
|
||||||
} else {
|
|
||||||
err_variant(ApplyVariant(VariantName::Unqualified("DivisionByZero".to_string()), None))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
(Int(_), Percent, Frac(_)) => EvalError(region, TypeMismatch("tried to do Int % Frac. Explicitly convert them to the same type first!".to_string())),
|
|
||||||
|
|
||||||
(Frac(_), Percent, Int(_)) => EvalError(region, TypeMismatch("tried to do Frac % Int. Explicitly convert them to the same type first!".to_string())),
|
|
||||||
|
|
||||||
(_, Percent, _) => EvalError(region, TypeMismatch("tried to use % on non-numbers".to_string())),
|
|
||||||
|
|
||||||
// Pizza
|
|
||||||
(_, Pizza, _) => { panic!("There was a |> operator that hadn't been removed prior to eval time. This should never happen!"); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn eval_case(region: Region, evaluated: Evaluated, branches: Vec<(Located<Pattern>, Located<Expr>)>, vars: &Scope) -> Evaluated {
|
|
||||||
use self::Evaluated::*;
|
|
||||||
|
|
||||||
for (pattern, definition) in branches {
|
|
||||||
let mut branch_vars = vars.clone();
|
|
||||||
|
|
||||||
if pattern_match(&evaluated, &pattern.value, &mut branch_vars).is_ok() {
|
|
||||||
return scoped_eval(definition, &branch_vars);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EvalError(region, NoBranchesMatched)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pattern_match(evaluated: &Evaluated, pattern: &Pattern, vars: &mut Scope) -> Result<(), Problem> {
|
|
||||||
use self::Evaluated::*;
|
|
||||||
|
|
||||||
match pattern {
|
|
||||||
Identifier(name) => {
|
|
||||||
vars.insert(name.clone().name(), Rc::new(evaluated.clone()));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
Underscore => {
|
|
||||||
// Underscore matches anything, and records no new vars.
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
EmptyRecordLiteral => {
|
|
||||||
match evaluated {
|
|
||||||
EmptyRecord => Ok(()),
|
|
||||||
expr => Err(TypeMismatch(
|
|
||||||
format!("Wanted a `{}`, but was given `{}`.", "{}", expr)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
Integer(pattern_num) => {
|
|
||||||
match evaluated {
|
|
||||||
Int(evaluated_num) => {
|
|
||||||
if *pattern_num == *evaluated_num {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(Problem::NotEqual)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
expr => Err(TypeMismatch(
|
|
||||||
format!("Wanted a `{}`, but was given `{}`.", "{}", expr)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
Fraction(numerator, denominator) => {
|
|
||||||
match evaluated {
|
|
||||||
Frac(actual_frac) => {
|
|
||||||
let expected_frac = fraction_from_i64s(*numerator, *denominator);
|
|
||||||
|
|
||||||
if expected_frac == *actual_frac {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(Problem::NotEqual)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
expr => Err(TypeMismatch(
|
|
||||||
format!("Wanted a `{}`, but was given `{}`.", "{}", expr)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Variant(pattern_variant_name, opt_pattern_contents) => {
|
|
||||||
match evaluated {
|
|
||||||
ApplyVariant(applied_variant_name, opt_applied_contents) => {
|
|
||||||
if *pattern_variant_name != *applied_variant_name {
|
|
||||||
return Err(TypeMismatch(
|
|
||||||
format!("Wanted a `{}` variant, but was given a `{}` variant.",
|
|
||||||
pattern_variant_name,
|
|
||||||
applied_variant_name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
match (opt_pattern_contents, opt_applied_contents) {
|
|
||||||
( Some(ref pattern_contents), Some(applied_contents) ) => {
|
|
||||||
if pattern_contents.len() == applied_contents.len() {
|
|
||||||
// Recursively pattern match
|
|
||||||
for ( pattern_val, applied_val ) in pattern_contents.into_iter().zip(applied_contents) {
|
|
||||||
pattern_match(applied_val, &pattern_val.value, vars)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(WrongArity(
|
|
||||||
pattern_contents.len() as u32,
|
|
||||||
applied_contents.len() as u32
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
( None, None ) => {
|
|
||||||
// It's the variant we expected, but it has no values in it,
|
|
||||||
// so we don't insert anything into vars.
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
( None, Some(contents) ) => {
|
|
||||||
// It's the variant we expected, but the arity is wrong.
|
|
||||||
Err(WrongArity(contents.len() as u32, 0))
|
|
||||||
},
|
|
||||||
( Some(patterns), None ) => {
|
|
||||||
// It's the variant we expected, but the arity is wrong.
|
|
||||||
Err(WrongArity(0, patterns.len() as u32))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
Err(TypeMismatch(format!("Wanted to destructure a `{}` variant, but was given a non-variant.", pattern_variant_name)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Evaluated {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
use self::Evaluated::*;
|
|
||||||
|
|
||||||
match self {
|
|
||||||
// PRIMITIVES
|
|
||||||
Int(num) => write!(f, "{}", *num),
|
|
||||||
Frac(fraction) => {
|
|
||||||
let numerator = *fraction.numer().unwrap();
|
|
||||||
let denominator = *fraction.denom().unwrap();
|
|
||||||
|
|
||||||
if denominator == 10 {
|
|
||||||
write!(f, "{}", (numerator as f64 / 10.0))
|
|
||||||
} else {
|
|
||||||
write!(f, "{}/{}", numerator, denominator)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Approx(num) => write!(f, "~{}", *num),
|
|
||||||
Str(string) => {
|
|
||||||
let escaped_str =
|
|
||||||
(*string)
|
|
||||||
.replace("\\", "\\\\")
|
|
||||||
.replace("\"", "\\\"")
|
|
||||||
.replace("\t", "\\t")
|
|
||||||
.replace("\n", "\\n")
|
|
||||||
.replace("\r", "\\r");
|
|
||||||
|
|
||||||
write!(f, "\"{}\"", escaped_str)
|
|
||||||
},
|
|
||||||
Char(ch) => write!(f, "'{}'", *ch),
|
|
||||||
Closure(args, _, _) => write!(f, "<{}-argument function>", args.len()),
|
|
||||||
ApplyVariant(name, opt_exprs) => {
|
|
||||||
match opt_exprs {
|
|
||||||
None => write!(f, "{}", name.clone().name()),
|
|
||||||
Some(exprs) => {
|
|
||||||
let contents =
|
|
||||||
exprs.into_iter()
|
|
||||||
.map(|expr| format!(" {}", expr))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(",");
|
|
||||||
|
|
||||||
write!(f, "{}{}", name.clone().name(), contents)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// ERRORS
|
|
||||||
EvalError(region, problem) => write!(f, "ERROR: {} at {}", format!("{}", problem), format!("line {}, column {}", region.start_line, region.start_col)),
|
|
||||||
|
|
||||||
// UNFORMATTED
|
|
||||||
_ => write!(f, "<partially evaluated expression>")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Problem {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Problem::UnrecognizedVarName(name) => write!(f, "Unrecognized var name `{}`", name),
|
|
||||||
Problem::NoBranchesMatched => write!(f, "No branches matched in this case-expression"),
|
|
||||||
Problem::TypeMismatch(info) => write!(f, "Type Mismatch - {}", info),
|
|
||||||
Problem::ReassignedVarName(name) => write!(f, "Reassigned constant - {}", name),
|
|
||||||
Problem::NotEqual => write!(f, "Pattern match on literal value failed; the branch wasn't equal."),
|
|
||||||
Problem::WrongArity(expected_arity, provided_arity) => {
|
|
||||||
if provided_arity > expected_arity {
|
|
||||||
write!(f, "Too many arguments! Needed {} arguments, but got {}", expected_arity, provided_arity)
|
|
||||||
} else {
|
|
||||||
write!(f, "Missing arguments! Needed {} arguments, but got {}", expected_arity, provided_arity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ok_variant(contents: Evaluated) -> Evaluated{
|
|
||||||
Evaluated::ApplyVariant(VariantName::Unqualified("Ok".to_string()), Some(vec![contents]))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn err_variant(contents: Evaluated) -> Evaluated {
|
|
||||||
Evaluated::ApplyVariant(VariantName::Unqualified("Err".to_string()), Some(vec![contents]))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fraction_from_i64s(numerator: i64, denominator: i64) -> Fraction {
|
|
||||||
if numerator.is_negative() {
|
|
||||||
Fraction::new_neg(numerator as u64, denominator as u64)
|
|
||||||
} else {
|
|
||||||
Fraction::new(numerator as u64, denominator as u64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_pizza(expr: &Expr) -> Expr {
|
|
||||||
use expr::Expr::*;
|
|
||||||
|
|
||||||
expr.walk(&|sub_expr| {
|
|
||||||
// TODO can we avoid cloning here somehow, without resorting to a macro?
|
|
||||||
match sub_expr.clone() {
|
|
||||||
Operator(boxed_loc_left, loc_op, boxed_loc_right) => {
|
|
||||||
let loc_left = *boxed_loc_left;
|
|
||||||
let loc_right = *boxed_loc_right;
|
|
||||||
let left_region = loc_left.region;
|
|
||||||
let right_region = loc_left.region;
|
|
||||||
let op_region = loc_op.region;
|
|
||||||
|
|
||||||
match ( loc_left.value, loc_op.value, loc_right.value ) {
|
|
||||||
(left_arg, Pizza, Expr::Var(name)) => {
|
|
||||||
Expr::CallByName(
|
|
||||||
name,
|
|
||||||
vec![Located { region: left_region, value: left_arg }]
|
|
||||||
)
|
|
||||||
},
|
|
||||||
(left_arg, Pizza, Expr::CallByName(name, mut args)) => {
|
|
||||||
args.push(Located { region: left_region, value: left_arg });
|
|
||||||
|
|
||||||
CallByName(name, args)
|
|
||||||
},
|
|
||||||
(left_arg, Pizza, Expr::Apply(applied_expr, mut args)) => {
|
|
||||||
args.push(Located { region: left_region, value: left_arg });
|
|
||||||
|
|
||||||
Apply(applied_expr, args)
|
|
||||||
},
|
|
||||||
(left, op, right) => {
|
|
||||||
Operator(
|
|
||||||
Box::new(Located { region: left_region, value: left }),
|
|
||||||
Located { region: op_region, value: op },
|
|
||||||
Box::new(Located { region: right_region, value: right }),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
other => other
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
52
src/expr.rs
52
src/expr.rs
|
@ -34,58 +34,6 @@ pub enum Expr {
|
||||||
Operator(Box<Located<Expr>>, Located<Operator>, Box<Located<Expr>>),
|
Operator(Box<Located<Expr>>, Located<Operator>, Box<Located<Expr>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A variant name, possibly fully-qualified with a module name
|
|
||||||
/// e.g. (Result.Ok)
|
|
||||||
/// Parameterized on a phantom marker for whether it has been canonicalized
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub enum VariantName {
|
|
||||||
Unqualified(String),
|
|
||||||
Qualified(String, String),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An identifier, possibly fully-qualified with a module name
|
|
||||||
/// e.g. (Http.Request from http)
|
|
||||||
/// Parameterized on a phantom marker for whether it has been canonicalized
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
||||||
pub enum Ident {
|
|
||||||
Unqualified(String),
|
|
||||||
Qualified(String, String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ident {
|
|
||||||
pub fn is_qualified(&self) -> bool {
|
|
||||||
match &self {
|
|
||||||
&Ident::Unqualified(_) => false,
|
|
||||||
&Ident::Qualified(_, _) => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn name(self) -> String {
|
|
||||||
match self {
|
|
||||||
Ident::Unqualified(name) => name,
|
|
||||||
Ident::Qualified(_, name) => name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Ident {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Ident::Unqualified(name) => write!(f, "{}", name),
|
|
||||||
Ident::Qualified(path, name) => write!(f, "{}.{}", path, name),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for VariantName {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
VariantName::Unqualified(name) => write!(f, "{}", name),
|
|
||||||
VariantName::Qualified(path, name) => write!(f, "{}.{}", path, name),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Pattern {
|
pub enum Pattern {
|
||||||
Identifier(String),
|
Identifier(String),
|
||||||
|
|
53
src/ident.rs
Normal file
53
src/ident.rs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
|
/// A variant name, possibly fully-qualified with a module name
|
||||||
|
/// e.g. (Result.Ok)
|
||||||
|
/// Parameterized on a phantom marker for whether it has been canonicalized
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum VariantName {
|
||||||
|
Unqualified(String),
|
||||||
|
Qualified(String, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An identifier, possibly fully-qualified with a module name
|
||||||
|
/// e.g. (Http.Request from http)
|
||||||
|
/// Parameterized on a phantom marker for whether it has been canonicalized
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
pub enum Ident {
|
||||||
|
Unqualified(String),
|
||||||
|
Qualified(String, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ident {
|
||||||
|
pub fn is_qualified(&self) -> bool {
|
||||||
|
match &self {
|
||||||
|
&Ident::Unqualified(_) => false,
|
||||||
|
&Ident::Qualified(_, _) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(self) -> String {
|
||||||
|
match self {
|
||||||
|
Ident::Unqualified(name) => name,
|
||||||
|
Ident::Qualified(_, name) => name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Ident {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Ident::Unqualified(name) => write!(f, "{}", name),
|
||||||
|
Ident::Qualified(path, name) => write!(f, "{}.{}", path, name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for VariantName {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
VariantName::Unqualified(name) => write!(f, "{}", name),
|
||||||
|
VariantName::Qualified(path, name) => write!(f, "{}.{}", path, name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
use canonicalize::{Expr, Procedure, Symbol};
|
use can::expr::Expr;
|
||||||
|
use can::procedure::Procedure;
|
||||||
|
use can::symbol::Symbol;
|
||||||
use collections::{ImMap, MutMap};
|
use collections::{ImMap, MutMap};
|
||||||
use constrain::{constrain, constrain_procedure};
|
use constrain::{constrain, constrain_procedure};
|
||||||
use region::Located;
|
use region::Located;
|
||||||
|
|
12
src/lib.rs
12
src/lib.rs
|
@ -1,14 +1,12 @@
|
||||||
// pub mod eval;
|
pub mod can;
|
||||||
pub mod canonicalize;
|
|
||||||
pub mod collections;
|
pub mod collections;
|
||||||
pub mod deprecated;
|
|
||||||
pub mod expr;
|
|
||||||
pub mod graph;
|
pub mod graph;
|
||||||
|
pub mod ident;
|
||||||
pub mod operator;
|
pub mod operator;
|
||||||
pub mod parse; // DEPRECATED
|
pub mod parse;
|
||||||
pub mod region;
|
pub mod region;
|
||||||
|
|
||||||
// pub mod string;
|
pub mod string;
|
||||||
|
|
||||||
pub mod constrain;
|
pub mod constrain;
|
||||||
pub mod ena;
|
pub mod ena;
|
||||||
|
@ -25,7 +23,5 @@ extern crate fxhash;
|
||||||
extern crate im_rc;
|
extern crate im_rc;
|
||||||
extern crate num;
|
extern crate num;
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate combine; // OBSOLETE
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
use expr::{Pattern, Ident};
|
|
||||||
|
|
||||||
pub struct Module {
|
|
||||||
name: Ident,
|
|
||||||
exposes: Vec<Ident>,
|
|
||||||
uses: Vec<Ident>,
|
|
||||||
decls: Vec<Decl>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum Decl {
|
|
||||||
Assign(Pattern, Box<Expr>, Box<Expr>),
|
|
||||||
// TODO Alias
|
|
||||||
// TODO SumType
|
|
||||||
}
|
|
|
@ -1,9 +1,8 @@
|
||||||
|
use bumpalo::collections::vec::Vec;
|
||||||
use operator::Operator;
|
use operator::Operator;
|
||||||
use parse::problems::Problem;
|
|
||||||
use region::Loc;
|
use region::Loc;
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
pub type Ident = str;
|
|
||||||
pub type VariantName = str;
|
pub type VariantName = str;
|
||||||
|
|
||||||
/// A parsed expression. This uses lifetimes extensively for two reasons:
|
/// A parsed expression. This uses lifetimes extensively for two reasons:
|
||||||
|
@ -23,50 +22,45 @@ pub type VariantName = str;
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Expr<'a> {
|
pub enum Expr<'a> {
|
||||||
// Number Literals
|
// Number Literals
|
||||||
Int(i64),
|
Float(&'a str),
|
||||||
Float(f64),
|
Int(&'a str),
|
||||||
|
HexInt(&'a str),
|
||||||
|
OctalInt(&'a str),
|
||||||
|
BinaryInt(&'a str),
|
||||||
|
|
||||||
// String Literals
|
// String Literals
|
||||||
EmptyStr,
|
EmptyStr,
|
||||||
Str(&'a str),
|
Str(&'a str),
|
||||||
/// basically InterpolatedStr(Vec<(String, Loc<Expr>)>, String)
|
BlockStr(&'a [&'a str]),
|
||||||
InterpolatedStr(&'a (&'a [(&'a str, Loc<Expr<'a>>)], &'a str)),
|
|
||||||
|
|
||||||
// List literals
|
// List literals
|
||||||
EmptyList,
|
EmptyList,
|
||||||
List(&'a [Loc<Expr<'a>>]),
|
List(Vec<'a, Loc<Expr<'a>>>),
|
||||||
|
// // Lookups
|
||||||
|
// Var(&'a str),
|
||||||
|
|
||||||
// Lookups
|
// // Pattern Matching
|
||||||
Var(&'a Ident),
|
// Case(&'a (Loc<Expr<'a>>, [(Loc<Pattern<'a>>, Loc<Expr<'a>>)])),
|
||||||
|
// Closure(&'a (&'a [Loc<Pattern<'a>>], Loc<Expr<'a>>)),
|
||||||
|
// /// basically Assign(Vec<(Loc<Pattern>, Loc<Expr>)>, Loc<Expr>)
|
||||||
|
// Assign(&'a (&'a [(Loc<Pattern<'a>>, Loc<Expr<'a>>)], Loc<Expr<'a>>)),
|
||||||
|
|
||||||
// Pattern Matching
|
// // Application
|
||||||
Case(&'a (Loc<Expr<'a>>, [(Loc<Pattern<'a>>, Loc<Expr<'a>>)])),
|
// Call(&'a (Loc<Expr<'a>>, [Loc<Expr<'a>>])),
|
||||||
Closure(&'a (&'a [Loc<Pattern<'a>>], Loc<Expr<'a>>)),
|
// ApplyVariant(&'a (&'a VariantName, [Loc<Expr<'a>>])),
|
||||||
/// basically Assign(Vec<(Loc<Pattern>, Loc<Expr>)>, Loc<Expr>)
|
// Variant(&'a VariantName),
|
||||||
Assign(&'a (&'a [(Loc<Pattern<'a>>, Loc<Expr<'a>>)], Loc<Expr<'a>>)),
|
|
||||||
|
|
||||||
// Application
|
|
||||||
Call(&'a (Loc<Expr<'a>>, [Loc<Expr<'a>>])),
|
|
||||||
ApplyVariant(&'a (&'a VariantName, [Loc<Expr<'a>>])),
|
|
||||||
Variant(&'a VariantName),
|
|
||||||
|
|
||||||
// Product Types
|
// Product Types
|
||||||
EmptyRecord,
|
EmptyRecord,
|
||||||
|
// // Sugar
|
||||||
// Sugar
|
// If(&'a (Loc<Expr<'a>>, Loc<Expr<'a>>, Loc<Expr<'a>>)),
|
||||||
If(&'a (Loc<Expr<'a>>, Loc<Expr<'a>>, Loc<Expr<'a>>)),
|
|
||||||
Operator(&'a (Loc<Expr<'a>>, Loc<Operator>, Loc<Expr<'a>>)),
|
Operator(&'a (Loc<Expr<'a>>, Loc<Operator>, Loc<Expr<'a>>)),
|
||||||
|
|
||||||
// Runtime errors
|
|
||||||
MalformedStr(Box<[Loc<Problem>]>),
|
|
||||||
MalformedInt(Problem),
|
|
||||||
MalformedFloat(Problem),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Pattern<'a> {
|
pub enum Pattern<'a> {
|
||||||
// Identifier
|
// Identifier
|
||||||
Identifier(&'a Ident),
|
Identifier(&'a str),
|
||||||
|
|
||||||
// Variant
|
// Variant
|
||||||
Variant(&'a VariantName),
|
Variant(&'a VariantName),
|
||||||
|
@ -82,33 +76,35 @@ pub enum Pattern<'a> {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expr_size() {
|
fn expr_size() {
|
||||||
// The size of the Expr data structure should be exactly 3 machine words.
|
// The size of the Expr data structure should be exactly 5 machine words.
|
||||||
// This test helps avoid regressions wich accidentally increase its size!
|
// This test helps avoid regressions wich accidentally increase its size!
|
||||||
//
|
|
||||||
// Worth noting that going up to 4 machine words is probably not a big deal;
|
|
||||||
// an 8-byte cache line will only fit 2 of these regardless.
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
std::mem::size_of::<Expr>(),
|
std::mem::size_of::<Expr>(),
|
||||||
// TODO [move this comment to an issue] We should be able to get this
|
// TODO [move this comment to an issue] We should be able to get this
|
||||||
// down to 2, which would mean we could fit 4 of these nodes in a single
|
// down to 2, which would mean we could fit 4 of these nodes in a single
|
||||||
// 64-byte cache line instead of only being able to fit 2.
|
// 64-byte cache line instead of only being able to fit 1.
|
||||||
//
|
//
|
||||||
// Doing this would require, among other things:
|
// Doing this would require, among other things:
|
||||||
// 1. Making a str replacement where the length is stored as u32 instead of usize,
|
// 1. Making a str replacement where the length is stored as u32 instead of usize,
|
||||||
// to leave room for the tagged union's u8 tag.
|
// to leave room for the tagged union's u8 tag.
|
||||||
// (Alternatively could store it as (&'a &'a str), but ew.)
|
// (Alternatively could store it as (&'a &'a str), but ew.)
|
||||||
// 2. Figuring out why &'a (Foo, Bar) by default takes up 24 bytes in Rust.
|
// 2. Similarly, making a slice replacement like that str replacement, and
|
||||||
|
// also where it doesn't share the bytes with anything else - so its
|
||||||
|
// elements can be consumed without having to clone them (unlike a slice).
|
||||||
|
// That's the only reason we're using Vec right now instead of slices -
|
||||||
|
// if we used slices, we'd have to clone their elements during canonicalization
|
||||||
|
// just to iterate over them and canonicalize them normally.
|
||||||
|
// 3. Figuring out why (&'a (Foo, Bar)) by default takes up 24 bytes in Rust.
|
||||||
// I assume it's because the struct is being stored inline instead of
|
// I assume it's because the struct is being stored inline instead of
|
||||||
// as a pointer, but in this case we actually do want the pointer!
|
// as a pointer, but in this case we actually do want the pointer!
|
||||||
// We want to have the lifetime and we want to avoid using the unsafe keyword,
|
// We want to have the lifetime and we want to avoid using the unsafe keyword,
|
||||||
// but we also want this to only store 1 pointer in the AST node.
|
// but we also want this to only store 1 pointer in the AST node.
|
||||||
// Hopefully there's a way!
|
// Hopefully there's a way!
|
||||||
//
|
//
|
||||||
// It's also possible that going up to 4 machine words might yield even
|
// It's also possible that 4 machine words might yield better performance
|
||||||
// better performance, due to more data structures being inlinable,
|
// than 2, due to more data structures being inlinable, and therefore
|
||||||
// and therefore having fewer pointers to chase. This seems worth
|
// having fewer pointers to chase. This seems worth investigating as well.
|
||||||
// investigating as well.
|
std::mem::size_of::<usize>() * 5
|
||||||
std::mem::size_of::<usize>() * 3
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,6 +147,7 @@ pub enum Attempting {
|
||||||
List,
|
List,
|
||||||
Keyword,
|
Keyword,
|
||||||
StringLiteral,
|
StringLiteral,
|
||||||
|
RecordLiteral,
|
||||||
InterpolatedString,
|
InterpolatedString,
|
||||||
NumberLiteral,
|
NumberLiteral,
|
||||||
UnicodeEscape,
|
UnicodeEscape,
|
||||||
|
@ -165,7 +162,15 @@ impl<'a> Display for Expr<'a> {
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
EmptyStr => write!(f, "\"\""),
|
EmptyStr => write!(f, "\"\""),
|
||||||
_ => panic!("TODO"),
|
Str(string) => write!(f, "\"{}\"", string),
|
||||||
|
BlockStr(lines) => write!(f, "\"\"\"{}\"\"\"", lines.join("\n")),
|
||||||
|
Int(string) => string.fmt(f),
|
||||||
|
Float(string) => string.fmt(f),
|
||||||
|
HexInt(string) => write!(f, "0x{}", string),
|
||||||
|
BinaryInt(string) => write!(f, "0b{}", string),
|
||||||
|
OctalInt(string) => write!(f, "0o{}", string),
|
||||||
|
EmptyRecord => write!(f, "{}", "{}"),
|
||||||
|
other => panic!("TODO implement Display for AST variant {:?}", other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,76 @@
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
pub mod ident;
|
pub mod ident;
|
||||||
|
pub mod module;
|
||||||
pub mod number_literal;
|
pub mod number_literal;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod problems;
|
pub mod problems;
|
||||||
pub mod string_literal;
|
pub mod string_literal;
|
||||||
|
|
||||||
|
use bumpalo::Bump;
|
||||||
|
use operator::Operator;
|
||||||
use parse::ast::{Attempting, Expr};
|
use parse::ast::{Attempting, Expr};
|
||||||
use parse::number_literal::number_literal;
|
use parse::number_literal::number_literal;
|
||||||
use parse::parser::{attempt, one_of2, Parser};
|
use parse::parser::{
|
||||||
|
and, attempt, lazy, loc, map, map_with_arena, one_of3, optional, string, unexpected,
|
||||||
|
unexpected_eof, val, Parser, State,
|
||||||
|
};
|
||||||
use parse::string_literal::string_literal;
|
use parse::string_literal::string_literal;
|
||||||
|
|
||||||
pub fn expr<'a>() -> impl Parser<'a, Expr<'a>> {
|
pub fn expr<'a>() -> impl Parser<'a, Expr<'a>> {
|
||||||
|
map_with_arena(
|
||||||
|
and(
|
||||||
attempt(
|
attempt(
|
||||||
Attempting::Expression,
|
Attempting::Expression,
|
||||||
one_of2(number_literal(), string_literal()),
|
loc(one_of3(
|
||||||
|
record_literal(),
|
||||||
|
number_literal(),
|
||||||
|
string_literal(),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
optional(and(loc(operator()), loc(val(Expr::Str("blah"))))),
|
||||||
|
),
|
||||||
|
|arena, (loc_expr1, opt_operator)| match opt_operator {
|
||||||
|
Some((loc_op, loc_expr2)) => {
|
||||||
|
let tuple = arena.alloc((loc_expr1, loc_op, loc_expr2));
|
||||||
|
|
||||||
|
Expr::Operator(tuple)
|
||||||
|
}
|
||||||
|
None => loc_expr1.value,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn operator<'a>() -> impl Parser<'a, Operator> {
|
||||||
|
val(Operator::Plus)
|
||||||
|
// one_of3(
|
||||||
|
// map(string("+"), |_| Operator::Plus),
|
||||||
|
// map(string("-"), |_| Operator::Minus),
|
||||||
|
// map(string("*"), |_| Operator::Star),
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn record_literal<'a>() -> impl Parser<'a, Expr<'a>> {
|
||||||
|
move |_arena: &'a Bump, state: State<'a>| {
|
||||||
|
let mut chars = state.input.chars();
|
||||||
|
|
||||||
|
match chars.next() {
|
||||||
|
Some('{') => (),
|
||||||
|
Some(other_char) => {
|
||||||
|
return Err(unexpected(other_char, 0, state, Attempting::RecordLiteral));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(unexpected_eof(0, Attempting::RecordLiteral, state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match chars.next() {
|
||||||
|
Some('}') => {
|
||||||
|
let next_state = state.advance_without_indenting(2)?;
|
||||||
|
|
||||||
|
Ok((Expr::EmptyRecord, next_state))
|
||||||
|
}
|
||||||
|
Some(other_char) => Err(unexpected(other_char, 0, state, Attempting::RecordLiteral)),
|
||||||
|
None => Err(unexpected_eof(0, Attempting::RecordLiteral, state)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
16
src/parse/module.rs
Normal file
16
src/parse/module.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
use ident::Ident;
|
||||||
|
use parse::ast::{Expr, Pattern};
|
||||||
|
|
||||||
|
pub struct Module<'a> {
|
||||||
|
pub name: Ident,
|
||||||
|
pub exposes: Vec<Ident>,
|
||||||
|
pub uses: Vec<Ident>,
|
||||||
|
pub decls: Vec<Decl<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Decl<'a> {
|
||||||
|
Def(Pattern<'a>, Expr<'a>, Expr<'a>),
|
||||||
|
// TODO Alias
|
||||||
|
// TODO SumType
|
||||||
|
}
|
|
@ -1,19 +1,16 @@
|
||||||
use bumpalo::collections::string::String;
|
|
||||||
use bumpalo::Bump;
|
|
||||||
use parse::ast::{Attempting, Expr};
|
use parse::ast::{Attempting, Expr};
|
||||||
use parse::parser::{unexpected, unexpected_eof, ParseResult, Parser, State};
|
use parse::parser::{unexpected, unexpected_eof, ParseResult, Parser, State};
|
||||||
use parse::problems::Problem;
|
|
||||||
use std::char;
|
use std::char;
|
||||||
|
|
||||||
pub fn number_literal<'a>() -> impl Parser<'a, Expr<'a>> {
|
pub fn number_literal<'a>() -> impl Parser<'a, Expr<'a>> {
|
||||||
move |arena: &'a Bump, state: State<'a>| {
|
move |_arena, state: State<'a>| {
|
||||||
let mut chars = state.input.chars();
|
let mut chars = state.input.chars();
|
||||||
|
|
||||||
match chars.next() {
|
match chars.next() {
|
||||||
Some(first_ch) => {
|
Some(first_ch) => {
|
||||||
// Number literals must start with either an '-' or a digit.
|
// Number literals must start with either an '-' or a digit.
|
||||||
if first_ch == '-' || first_ch.is_ascii_digit() {
|
if first_ch == '-' || first_ch.is_ascii_digit() {
|
||||||
parse_number_literal(first_ch, &mut chars, arena, state)
|
parse_number_literal(first_ch, &mut chars, state)
|
||||||
} else {
|
} else {
|
||||||
Err(unexpected(
|
Err(unexpected(
|
||||||
first_ch,
|
first_ch,
|
||||||
|
@ -32,61 +29,69 @@ pub fn number_literal<'a>() -> impl Parser<'a, Expr<'a>> {
|
||||||
fn parse_number_literal<'a, I>(
|
fn parse_number_literal<'a, I>(
|
||||||
first_ch: char,
|
first_ch: char,
|
||||||
chars: &mut I,
|
chars: &mut I,
|
||||||
arena: &'a Bump,
|
|
||||||
state: State<'a>,
|
state: State<'a>,
|
||||||
) -> ParseResult<'a, Expr<'a>>
|
) -> ParseResult<'a, Expr<'a>>
|
||||||
where
|
where
|
||||||
I: Iterator<Item = char>,
|
I: Iterator<Item = char>,
|
||||||
{
|
{
|
||||||
let mut before_decimal = String::with_capacity_in(1, arena);
|
use self::LiteralType::*;
|
||||||
let mut after_decimal = String::new_in(arena);
|
|
||||||
let mut has_decimal_point = false;
|
|
||||||
let mut chars_skipped = 0;
|
|
||||||
|
|
||||||
// Put the first character into the buffer, even if all we've parsed so
|
let mut typ = Int;
|
||||||
// far is a minus sign.
|
|
||||||
//
|
// We already parsed 1 character (which may have been a minus sign).
|
||||||
// We have to let i64::parse handle the minus sign (if it's there), because
|
let mut chars_parsed = 1;
|
||||||
// otherwise if we ask it to parse i64::MIN.to_string() as a positive i64,
|
|
||||||
// it errors because that positive number doesn't fit in an i64!
|
|
||||||
before_decimal.push(first_ch);
|
|
||||||
|
|
||||||
while let Some(next_ch) = chars.next() {
|
while let Some(next_ch) = chars.next() {
|
||||||
match next_ch {
|
chars_parsed += 1;
|
||||||
digit if next_ch.is_ascii_digit() => {
|
|
||||||
if has_decimal_point {
|
|
||||||
after_decimal.push(digit);
|
|
||||||
} else {
|
|
||||||
before_decimal.push(digit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'_' => {
|
|
||||||
// Underscores are allowed, and disregarded.
|
|
||||||
chars_skipped += 1;
|
|
||||||
}
|
|
||||||
'.' => {
|
|
||||||
if has_decimal_point {
|
|
||||||
// You only get one decimal point!
|
|
||||||
let len = before_decimal.len() + after_decimal.len() + chars_skipped;
|
|
||||||
|
|
||||||
return Err(unexpected('.', len, state, Attempting::NumberLiteral));
|
let err_unexpected = || {
|
||||||
} else {
|
Err(unexpected(
|
||||||
chars_skipped += 1;
|
next_ch,
|
||||||
has_decimal_point = true;
|
chars_parsed,
|
||||||
}
|
state.clone(),
|
||||||
}
|
|
||||||
invalid_char => {
|
|
||||||
if before_decimal.is_empty() {
|
|
||||||
// No digits! We likely parsed a minus sign that's actually an operator.
|
|
||||||
let len = before_decimal.len() + after_decimal.len() + chars_skipped;
|
|
||||||
return Err(unexpected(
|
|
||||||
invalid_char,
|
|
||||||
len,
|
|
||||||
state,
|
|
||||||
Attempting::NumberLiteral,
|
Attempting::NumberLiteral,
|
||||||
));
|
))
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// Returns true iff so far we have parsed the given char and no other chars.
|
||||||
|
let so_far_parsed = |ch| chars_parsed == 2 && first_ch == ch;
|
||||||
|
|
||||||
|
// We don't support negative escaped ints (e.g. 0x01 is supported but -0x01 is not).
|
||||||
|
// If you want that, do something like (negate 0x01).
|
||||||
|
//
|
||||||
|
// I'm open to changing this policy (that is, allowing support for
|
||||||
|
// negative escaped ints), but it'll complicate parsing logic and seems
|
||||||
|
// nonessential, so I'm leaving it out for now.
|
||||||
|
if next_ch == '.' {
|
||||||
|
if typ == Float {
|
||||||
|
// You only get one decimal point!
|
||||||
|
return err_unexpected();
|
||||||
|
} else {
|
||||||
|
typ = Float;
|
||||||
|
}
|
||||||
|
} else if next_ch == 'x' {
|
||||||
|
if so_far_parsed('0') {
|
||||||
|
typ = Hex;
|
||||||
|
} else {
|
||||||
|
return err_unexpected();
|
||||||
|
}
|
||||||
|
} else if next_ch == 'b' {
|
||||||
|
if so_far_parsed('0') {
|
||||||
|
typ = Binary;
|
||||||
|
} else {
|
||||||
|
return err_unexpected();
|
||||||
|
}
|
||||||
|
} else if next_ch == 'o' {
|
||||||
|
if so_far_parsed('0') {
|
||||||
|
typ = Octal;
|
||||||
|
} else {
|
||||||
|
return err_unexpected();
|
||||||
|
}
|
||||||
|
} else if !next_ch.is_ascii_digit() && next_ch != '_' {
|
||||||
|
if so_far_parsed('-') {
|
||||||
|
// No digits! We likely parsed a minus sign that's actually an operator.
|
||||||
|
return err_unexpected();
|
||||||
|
} else {
|
||||||
// We hit an invalid number literal character; we're done!
|
// We hit an invalid number literal character; we're done!
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -96,41 +101,25 @@ where
|
||||||
// At this point we have a number, and will definitely succeed.
|
// At this point we have a number, and will definitely succeed.
|
||||||
// If the number is malformed (outside the supported range),
|
// If the number is malformed (outside the supported range),
|
||||||
// we'll succeed with an appropriate Expr which records that.
|
// we'll succeed with an appropriate Expr which records that.
|
||||||
let expr = if has_decimal_point {
|
let expr = match typ {
|
||||||
let mut f64_buf = String::with_capacity_in(
|
Int => Expr::Int(&state.input[0..chars_parsed]),
|
||||||
before_decimal.len()
|
Float => Expr::Float(&state.input[0..chars_parsed]),
|
||||||
// +1 for the decimal point itself
|
// For these we trim off the 0x/0o/0b part
|
||||||
+ 1
|
Hex => Expr::HexInt(&state.input[2..chars_parsed - 1]),
|
||||||
+ after_decimal.len(),
|
Binary => Expr::BinaryInt(&state.input[2..chars_parsed - 1]),
|
||||||
arena,
|
Octal => Expr::OctalInt(&state.input[2..chars_parsed - 1]),
|
||||||
);
|
|
||||||
|
|
||||||
f64_buf.push_str(&before_decimal);
|
|
||||||
f64_buf.push('.');
|
|
||||||
f64_buf.push_str(&after_decimal);
|
|
||||||
|
|
||||||
// TODO [convert this comment to an issue] - we can get better
|
|
||||||
// performance here by inlining string.parse() for the f64 case,
|
|
||||||
// since we've already done the work of validating that each char
|
|
||||||
// is a digit, plus we also already separately parsed the minus
|
|
||||||
// sign and dot.
|
|
||||||
match f64_buf.parse::<f64>() {
|
|
||||||
Ok(float) if float.is_finite() => Expr::Float(float),
|
|
||||||
_ => Expr::MalformedFloat(Problem::OutsideSupportedRange),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TODO [convert this comment to an issue] - we can get better
|
|
||||||
// performance here by inlining string.parse() for the i64 case,
|
|
||||||
// since we've already done the work of validating that each char
|
|
||||||
// is a digit.
|
|
||||||
match before_decimal.parse::<i64>() {
|
|
||||||
Ok(int_val) => Expr::Int(int_val),
|
|
||||||
Err(_) => Expr::MalformedInt(Problem::OutsideSupportedRange),
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let total_chars_parsed = before_decimal.len() + chars_skipped;
|
let next_state = state.advance_without_indenting(chars_parsed)?;
|
||||||
let state = state.advance_without_indenting(total_chars_parsed)?;
|
|
||||||
|
|
||||||
Ok((expr, state))
|
Ok((expr, next_state))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
enum LiteralType {
|
||||||
|
Int,
|
||||||
|
Float,
|
||||||
|
Hex,
|
||||||
|
Octal,
|
||||||
|
Binary,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use bumpalo::collections::vec::Vec;
|
use bumpalo::collections::vec::Vec;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use parse::ast::Attempting;
|
use parse::ast::Attempting;
|
||||||
use region::Region;
|
use region::{Located, Region};
|
||||||
use std::char;
|
use std::char;
|
||||||
|
|
||||||
// Strategy:
|
// Strategy:
|
||||||
|
@ -190,6 +190,21 @@ pub trait Parser<'a, Output> {
|
||||||
fn parse(&self, &'a Bump, State<'a>) -> ParseResult<'a, Output>;
|
fn parse(&self, &'a Bump, State<'a>) -> ParseResult<'a, Output>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct BoxedParser<'a, Output> {
|
||||||
|
parser: &'a (dyn Parser<'a, Output> + 'a),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Output> BoxedParser<'a, Output> {
|
||||||
|
fn new<P>(arena: &'a Bump, parser: P) -> Self
|
||||||
|
where
|
||||||
|
P: Parser<'a, Output> + 'a,
|
||||||
|
{
|
||||||
|
BoxedParser {
|
||||||
|
parser: arena.alloc(parser),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, F, Output> Parser<'a, Output> for F
|
impl<'a, F, Output> Parser<'a, Output> for F
|
||||||
where
|
where
|
||||||
F: Fn(&'a Bump, State<'a>) -> ParseResult<'a, Output>,
|
F: Fn(&'a Bump, State<'a>) -> ParseResult<'a, Output>,
|
||||||
|
@ -199,6 +214,22 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn val<'a, Val>(value: Val) -> impl Parser<'a, Val>
|
||||||
|
where
|
||||||
|
Val: Clone,
|
||||||
|
{
|
||||||
|
move |_, state| Ok((value.clone(), state))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Needed for recursive parsers
|
||||||
|
pub fn lazy<'a, F, P, Val>(get_parser: F) -> impl Parser<'a, Val>
|
||||||
|
where
|
||||||
|
F: Fn() -> P,
|
||||||
|
P: Parser<'a, Val>,
|
||||||
|
{
|
||||||
|
move |arena, state| get_parser().parse(arena, state)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn map<'a, P, F, Before, After>(parser: P, transform: F) -> impl Parser<'a, After>
|
pub fn map<'a, P, F, Before, After>(parser: P, transform: F) -> impl Parser<'a, After>
|
||||||
where
|
where
|
||||||
P: Parser<'a, Before>,
|
P: Parser<'a, Before>,
|
||||||
|
@ -211,6 +242,18 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn map_with_arena<'a, P, F, Before, After>(parser: P, transform: F) -> impl Parser<'a, After>
|
||||||
|
where
|
||||||
|
P: Parser<'a, Before>,
|
||||||
|
F: Fn(&'a Bump, Before) -> After,
|
||||||
|
{
|
||||||
|
move |arena, state| {
|
||||||
|
parser
|
||||||
|
.parse(arena, state)
|
||||||
|
.map(|(output, next_state)| (transform(arena, output), next_state))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn attempt<'a, P, Val>(attempting: Attempting, parser: P) -> impl Parser<'a, Val>
|
pub fn attempt<'a, P, Val>(attempting: Attempting, parser: P) -> impl Parser<'a, Val>
|
||||||
where
|
where
|
||||||
P: Parser<'a, Val>,
|
P: Parser<'a, Val>,
|
||||||
|
@ -226,6 +269,32 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn loc<'a, P, Val>(parser: P) -> impl Parser<'a, Located<Val>>
|
||||||
|
where
|
||||||
|
P: Parser<'a, Val>,
|
||||||
|
{
|
||||||
|
move |arena, state: State<'a>| {
|
||||||
|
let start_col = state.column;
|
||||||
|
let start_line = state.line;
|
||||||
|
|
||||||
|
match parser.parse(arena, state) {
|
||||||
|
Ok((value, state)) => {
|
||||||
|
let end_col = state.column;
|
||||||
|
let end_line = state.line;
|
||||||
|
let region = Region {
|
||||||
|
start_col,
|
||||||
|
start_line,
|
||||||
|
end_col,
|
||||||
|
end_line,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((Located { region, value }, state))
|
||||||
|
}
|
||||||
|
Err((fail, state)) => Err((fail, state)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn one_or_more<'a, P, A>(parser: P) -> impl Parser<'a, Vec<'a, A>>
|
pub fn one_or_more<'a, P, A>(parser: P) -> impl Parser<'a, Vec<'a, A>>
|
||||||
where
|
where
|
||||||
P: Parser<'a, A>,
|
P: Parser<'a, A>,
|
||||||
|
@ -317,6 +386,7 @@ pub fn string<'a>(string: &'static str) -> impl Parser<'a, ()> {
|
||||||
let input = state.input;
|
let input = state.input;
|
||||||
let len = string.len();
|
let len = string.len();
|
||||||
|
|
||||||
|
// TODO do this comparison in one SIMD instruction (on supported systems)
|
||||||
match input.get(0..len) {
|
match input.get(0..len) {
|
||||||
Some(next_str) if next_str == string => Ok(((), state.advance_without_indenting(len)?)),
|
Some(next_str) if next_str == string => Ok(((), state.advance_without_indenting(len)?)),
|
||||||
_ => Err(unexpected_eof(len, Attempting::Keyword, state)),
|
_ => Err(unexpected_eof(len, Attempting::Keyword, state)),
|
||||||
|
@ -378,6 +448,46 @@ where
|
||||||
// satisfies(any, |ch| ch.is_whitespace())
|
// satisfies(any, |ch| ch.is_whitespace())
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
pub fn and<'a, P1, P2, A, B>(p1: P1, p2: P2) -> impl Parser<'a, (A, B)>
|
||||||
|
where
|
||||||
|
P1: Parser<'a, A>,
|
||||||
|
P2: Parser<'a, B>,
|
||||||
|
{
|
||||||
|
move |arena: &'a Bump, state: State<'a>| {
|
||||||
|
let original_attempting = state.attempting;
|
||||||
|
|
||||||
|
match p1.parse(arena, state) {
|
||||||
|
Ok((out1, state)) => match p2.parse(arena, state) {
|
||||||
|
Ok((out2, state)) => Ok(((out1, out2), state)),
|
||||||
|
Err((fail, state)) => Err((
|
||||||
|
Fail {
|
||||||
|
attempting: original_attempting,
|
||||||
|
..fail
|
||||||
|
},
|
||||||
|
state,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Err((fail, state)) => Err((
|
||||||
|
Fail {
|
||||||
|
attempting: original_attempting,
|
||||||
|
..fail
|
||||||
|
},
|
||||||
|
state,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn optional<'a, P, T>(parser: P) -> impl Parser<'a, Option<T>>
|
||||||
|
where
|
||||||
|
P: Parser<'a, T>,
|
||||||
|
{
|
||||||
|
move |arena: &'a Bump, state: State<'a>| match parser.parse(arena, state) {
|
||||||
|
Ok((out1, state)) => Ok((Some(out1), state)),
|
||||||
|
Err((_, state)) => Ok((None, state)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn one_of2<'a, P1, P2, A>(p1: P1, p2: P2) -> impl Parser<'a, A>
|
pub fn one_of2<'a, P1, P2, A>(p1: P1, p2: P2) -> impl Parser<'a, A>
|
||||||
where
|
where
|
||||||
P1: Parser<'a, A>,
|
P1: Parser<'a, A>,
|
||||||
|
|
|
@ -1,18 +1,11 @@
|
||||||
use bumpalo::collections::string::String;
|
|
||||||
use bumpalo::collections::vec::Vec;
|
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use parse::ast::{Attempting, Expr};
|
use parse::ast::{Attempting, Expr};
|
||||||
use parse::ident;
|
use parse::parser::{unexpected, unexpected_eof, ParseResult, Parser, State};
|
||||||
use parse::parser::{unexpected, unexpected_eof, Fail, Parser, State};
|
|
||||||
use parse::problems::{Problem, Problems};
|
|
||||||
use region::{Loc, Region};
|
|
||||||
use std::char;
|
use std::char;
|
||||||
use std::iter::Peekable;
|
|
||||||
|
|
||||||
pub fn string_literal<'a>() -> impl Parser<'a, Expr<'a>> {
|
pub fn string_literal<'a>() -> impl Parser<'a, Expr<'a>> {
|
||||||
move |arena: &'a Bump, state: State<'a>| {
|
move |arena: &'a Bump, state: State<'a>| {
|
||||||
let mut problems = std::vec::Vec::new();
|
let mut chars = state.input.chars();
|
||||||
let mut chars = state.input.chars().peekable();
|
|
||||||
|
|
||||||
// String literals must start with a quote.
|
// String literals must start with a quote.
|
||||||
// If this doesn't, it must not be a string literal!
|
// If this doesn't, it must not be a string literal!
|
||||||
|
@ -26,464 +19,75 @@ pub fn string_literal<'a>() -> impl Parser<'a, Expr<'a>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have precisely an empty string here, don't bother allocating
|
// At the parsing stage we keep the entire raw string, because the formatter
|
||||||
// a buffer; instead, return EmptyStr immediately.
|
// needs the raw string. (For example, so it can "remember" whether you
|
||||||
if chars.peek() == Some(&'"') {
|
// wrote \u{...} or the actual unicode character itself.)
|
||||||
return Ok((
|
//
|
||||||
Expr::EmptyStr,
|
// Later, in canonicalization, we'll do things like resolving
|
||||||
// 2 because `""` has length 2
|
// unicode escapes and string interpolation.
|
||||||
state.advance_without_indenting(2)?,
|
//
|
||||||
));
|
// Since we're keeping the entire raw string, all we need to track is
|
||||||
}
|
// how many characters we've parsed. So far, that's 1 (the opening `"`).
|
||||||
|
let mut parsed_chars = 1;
|
||||||
// Stores the accumulated string characters
|
let mut prev_ch = '"';
|
||||||
let mut buf = String::new_in(arena);
|
|
||||||
|
|
||||||
// This caches the total string length of interpolated_pairs. Every
|
|
||||||
// time we add a new pair to interpolated_pairs, we increment this
|
|
||||||
// by the sum of whatever we parsed in order to obtain that pair.
|
|
||||||
let mut buf_col_offset: usize = 0;
|
|
||||||
|
|
||||||
// Stores interpolated identifiers, if any.
|
|
||||||
let mut interpolated_pairs = Vec::new_in(arena);
|
|
||||||
|
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
match ch {
|
parsed_chars += 1;
|
||||||
// If it's a backslash, escape things.
|
|
||||||
'\\' => match chars.next() {
|
|
||||||
Some(next_ch) => {
|
|
||||||
if let Some(ident) = handle_escaped_char(
|
|
||||||
arena,
|
|
||||||
&state,
|
|
||||||
next_ch,
|
|
||||||
&mut chars,
|
|
||||||
&mut buf,
|
|
||||||
&mut problems,
|
|
||||||
)? {
|
|
||||||
let expr = Expr::Var(ident);
|
|
||||||
|
|
||||||
// +2 for `\(` and then another +1 for `)` at the end
|
// Potentially end the string (unless this is an escaped `"`!)
|
||||||
let parsed_length = buf.len() + 2 + ident.len() + 1;
|
if ch == '"' && prev_ch != '\\' {
|
||||||
|
let expr = if parsed_chars == 2 {
|
||||||
// It's okay if casting fails in this section, because
|
if let Some('"') = chars.next() {
|
||||||
// we're going to check for line length overflow at the
|
// If the first three chars were all `"`, then this
|
||||||
// end anyway. That will render this region useless,
|
// literal begins with `"""` and is a block string.
|
||||||
// but the user wasn't going to see this region
|
return parse_block_string(arena, state, &mut chars);
|
||||||
// anyway if the line length overflowed.
|
|
||||||
let start_line = state.line;
|
|
||||||
|
|
||||||
// Subtract ident length and another 1 for the `)`
|
|
||||||
let start_col = state.column
|
|
||||||
+ buf_col_offset as u16
|
|
||||||
+ (parsed_length - ident.len() - 1) as u16;
|
|
||||||
let ident_region = Region {
|
|
||||||
start_line,
|
|
||||||
start_col,
|
|
||||||
end_line: start_line,
|
|
||||||
end_col: start_col + ident.len() as u16 - 1,
|
|
||||||
};
|
|
||||||
let loc_expr = Loc {
|
|
||||||
region: ident_region,
|
|
||||||
value: expr,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Push the accumulated string into the pairs list,
|
|
||||||
// along with the ident that came after it.
|
|
||||||
interpolated_pairs.push((buf.into_bump_str(), loc_expr));
|
|
||||||
|
|
||||||
// Reset the buffer so we start working on a new string.
|
|
||||||
buf = String::new_in(arena);
|
|
||||||
|
|
||||||
// Advance the cached offset of how many chars we've parsed,
|
|
||||||
// so the next time we see an interpolated ident, we can
|
|
||||||
// correctly calculate its region.
|
|
||||||
buf_col_offset += parsed_length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// We ran out of characters before finding a closed quote;
|
|
||||||
// let the loop finish normally, so we end up returning
|
|
||||||
// the error that the string was not terminated.
|
|
||||||
//
|
|
||||||
// (There's the separate problem of a trailing backslash,
|
|
||||||
// but often that will get fixed in the course of
|
|
||||||
// addressing the missing closed quote.)
|
|
||||||
()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'"' => {
|
|
||||||
// We found a closed quote; this is the end of the string!
|
|
||||||
let len_with_quotes = buf.len() + 2;
|
|
||||||
let expr = if problems.is_empty() {
|
|
||||||
let final_str = buf.into_bump_str();
|
|
||||||
|
|
||||||
if interpolated_pairs.is_empty() {
|
|
||||||
Expr::Str(final_str)
|
|
||||||
} else {
|
} else {
|
||||||
let tuple_ref =
|
Expr::EmptyStr
|
||||||
arena.alloc((interpolated_pairs.into_bump_slice(), final_str));
|
|
||||||
|
|
||||||
Expr::InterpolatedStr(tuple_ref)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Expr::MalformedStr(problems.into_boxed_slice())
|
// Start at 1 so we omit the opening `"`.
|
||||||
|
// Subtract 1 from parsed_chars so we omit the closing `"`.
|
||||||
|
Expr::Str(&state.input[1..(parsed_chars - 1)])
|
||||||
};
|
};
|
||||||
|
|
||||||
let next_state = state.advance_without_indenting(len_with_quotes)?;
|
let next_state = state.advance_without_indenting(parsed_chars)?;
|
||||||
|
|
||||||
return Ok((expr, next_state));
|
return Ok((expr, next_state));
|
||||||
}
|
} else if ch == '\n' {
|
||||||
'\t' => {
|
// This is a single-line string, which cannot have newlines!
|
||||||
// Report the problem and continue. Tabs are syntax errors,
|
// Treat this as an unclosed string literal, and consume
|
||||||
// but maybe the rest of the string is fine!
|
// all remaining chars. This will mask all other errors, but
|
||||||
problems.push(loc_char(Problem::Tab, &state, buf.len()));
|
// it should make it easiest to debug; the file will be a giant
|
||||||
}
|
// error starting from where the open quote appeared.
|
||||||
'\r' => {
|
return Err(unexpected(
|
||||||
// Carriage returns aren't allowed in string literals,
|
'\n',
|
||||||
// but maybe the rest of the string is fine!
|
state.input.len() - 1,
|
||||||
problems.push(loc_char(Problem::CarriageReturn, &state, buf.len()));
|
state,
|
||||||
}
|
Attempting::StringLiteral,
|
||||||
'\n' => {
|
));
|
||||||
// We hit a newline before a close quote.
|
} else {
|
||||||
// We can't safely assume where the string was supposed
|
prev_ch = ch;
|
||||||
// to end, so this is an unrecoverable error.
|
|
||||||
return Err(unexpected('\n', 0, state, Attempting::StringLiteral));
|
|
||||||
}
|
|
||||||
normal_char => buf.push(normal_char),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We ran out of characters before finding a closed quote
|
// We ran out of characters before finding a closed quote
|
||||||
Err(unexpected_eof(
|
Err(unexpected_eof(
|
||||||
buf.len(),
|
parsed_chars,
|
||||||
Attempting::StringLiteral,
|
Attempting::StringLiteral,
|
||||||
state.clone(),
|
state.clone(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loc_char<'a, V>(value: V, state: &State<'a>, buf_len: usize) -> Loc<V> {
|
fn parse_block_string<'a, I>(
|
||||||
let start_line = state.line;
|
_arena: &'a Bump,
|
||||||
let start_col = state.column + buf_len as u16;
|
_state: State<'a>,
|
||||||
let end_line = start_line;
|
_chars: &mut I,
|
||||||
// All invalid chars should have a length of 1
|
) -> ParseResult<'a, Expr<'a>>
|
||||||
let end_col = state.column + 1;
|
|
||||||
|
|
||||||
let region = Region {
|
|
||||||
start_line,
|
|
||||||
start_col,
|
|
||||||
end_line,
|
|
||||||
end_col,
|
|
||||||
};
|
|
||||||
|
|
||||||
Loc { region, value }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn loc_escaped_char<'a, V>(value: V, state: &State<'a>, buf_len: usize) -> Loc<V> {
|
|
||||||
let start_line = state.line;
|
|
||||||
let start_col = state.column + buf_len as u16;
|
|
||||||
let end_line = start_line;
|
|
||||||
// escapes should all be 2 chars long
|
|
||||||
let end_col = state.column + 1;
|
|
||||||
|
|
||||||
let region = Region {
|
|
||||||
start_line,
|
|
||||||
start_col,
|
|
||||||
end_line,
|
|
||||||
end_col,
|
|
||||||
};
|
|
||||||
|
|
||||||
Loc { region, value }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn loc_escaped_unicode<'a, V>(
|
|
||||||
value: V,
|
|
||||||
state: &State<'a>,
|
|
||||||
buf_len: usize,
|
|
||||||
hex_str_len: usize,
|
|
||||||
) -> Loc<V> {
|
|
||||||
let start_line = state.line;
|
|
||||||
// +1 due to the `"` which precedes buf.
|
|
||||||
let start_col = state.column + buf_len as u16 + 1;
|
|
||||||
let end_line = start_line;
|
|
||||||
// +3 due to the `\u{` and another + 1 due to the `}`
|
|
||||||
// -1 to prevent overshooting because end col is inclusive.
|
|
||||||
let end_col = start_col + 3 + hex_str_len as u16 + 1 - 1;
|
|
||||||
|
|
||||||
let region = Region {
|
|
||||||
start_line,
|
|
||||||
start_col,
|
|
||||||
end_line,
|
|
||||||
end_col,
|
|
||||||
};
|
|
||||||
|
|
||||||
Loc { region, value }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn handle_escaped_char<'a, I>(
|
|
||||||
arena: &'a Bump,
|
|
||||||
state: &State<'a>,
|
|
||||||
ch: char,
|
|
||||||
chars: &mut Peekable<I>,
|
|
||||||
buf: &mut String<'a>,
|
|
||||||
problems: &mut Problems,
|
|
||||||
) -> Result<Option<&'a str>, (Fail, State<'a>)>
|
|
||||||
where
|
where
|
||||||
I: Iterator<Item = char>,
|
I: Iterator<Item = char>,
|
||||||
{
|
{
|
||||||
match ch {
|
// So far we have consumed the `"""` and that's it.
|
||||||
'\\' => buf.push('\\'),
|
let _parsed_chars = 3;
|
||||||
'"' => buf.push('"'),
|
panic!("TODO parse block string, advance state, etc");
|
||||||
't' => buf.push('\t'),
|
|
||||||
'n' => buf.push('\n'),
|
|
||||||
'r' => buf.push('\r'),
|
|
||||||
'0' => buf.push('\0'), // We explicitly support null characters, as we
|
|
||||||
// can't be sure we won't receive them from Rust.
|
|
||||||
'u' => handle_escaped_unicode(arena, &state, chars, buf, problems)?,
|
|
||||||
'(' => {
|
|
||||||
let ident = parse_interpolated_ident(arena, state, chars)?;
|
|
||||||
|
|
||||||
return Ok(Some(ident));
|
|
||||||
}
|
|
||||||
'\t' => {
|
|
||||||
// Report and continue.
|
|
||||||
// Tabs are syntax errors, but maybe the rest of the string is fine!
|
|
||||||
problems.push(loc_escaped_char(Problem::Tab, &state, buf.len()));
|
|
||||||
}
|
|
||||||
'\r' => {
|
|
||||||
// Report and continue.
|
|
||||||
// Carriage returns aren't allowed in string literals,
|
|
||||||
// but maybe the rest of the string is fine!
|
|
||||||
problems.push(loc_escaped_char(Problem::CarriageReturn, &state, buf.len()));
|
|
||||||
}
|
|
||||||
'\n' => {
|
|
||||||
// Report and bail out.
|
|
||||||
// We can't safely assume where the string was supposed to end.
|
|
||||||
problems.push(loc_escaped_char(
|
|
||||||
Problem::NewlineInLiteral,
|
|
||||||
&state,
|
|
||||||
buf.len(),
|
|
||||||
));
|
|
||||||
|
|
||||||
return Err(unexpected_eof(
|
|
||||||
buf.len(),
|
|
||||||
Attempting::UnicodeEscape,
|
|
||||||
state.clone(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// Report and continue.
|
|
||||||
// An unsupported escaped char (e.g. \q) shouldn't halt parsing.
|
|
||||||
problems.push(loc_escaped_char(
|
|
||||||
Problem::UnsupportedEscapedChar,
|
|
||||||
&state,
|
|
||||||
buf.len(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn handle_escaped_unicode<'a, I>(
|
|
||||||
arena: &'a Bump,
|
|
||||||
state: &State<'a>,
|
|
||||||
chars: &mut Peekable<I>,
|
|
||||||
buf: &mut String<'a>,
|
|
||||||
problems: &mut Problems,
|
|
||||||
) -> Result<(), (Fail, State<'a>)>
|
|
||||||
where
|
|
||||||
I: Iterator<Item = char>,
|
|
||||||
{
|
|
||||||
// \u{00A0} is how you specify a Unicode code point,
|
|
||||||
// so we should always see a '{' next.
|
|
||||||
if chars.next() != Some('{') {
|
|
||||||
let start_line = state.line;
|
|
||||||
// +1 due to the `"` which precedes buf
|
|
||||||
let start_col = state.column + 1 + buf.len() as u16;
|
|
||||||
let end_line = start_line;
|
|
||||||
|
|
||||||
// All we parsed was `\u`, so end on the column after `\`'s column.
|
|
||||||
let end_col = start_col + 1;
|
|
||||||
|
|
||||||
let region = Region {
|
|
||||||
start_line,
|
|
||||||
start_col,
|
|
||||||
end_line,
|
|
||||||
end_col,
|
|
||||||
};
|
|
||||||
|
|
||||||
problems.push(Loc {
|
|
||||||
region,
|
|
||||||
value: Problem::NoUnicodeDigits,
|
|
||||||
});
|
|
||||||
|
|
||||||
// The rest of the string literal might be fine. Keep parsing!
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record the point in the string literal where we started parsing `\u`
|
|
||||||
let start_of_unicode = buf.len();
|
|
||||||
|
|
||||||
// Stores the accumulated unicode digits
|
|
||||||
let mut hex_str = String::new_in(arena);
|
|
||||||
|
|
||||||
while let Some(hex_char) = chars.next() {
|
|
||||||
match hex_char {
|
|
||||||
'}' => {
|
|
||||||
// Done! Validate and add it to the buffer.
|
|
||||||
match u32::from_str_radix(&hex_str, 16) {
|
|
||||||
Ok(code_pt) => {
|
|
||||||
if code_pt > 0x10FFFF {
|
|
||||||
let start_line = state.line;
|
|
||||||
// +1 due to the `"` which precedes buf
|
|
||||||
// +3 due to the `\u{` which precedes the hex digits
|
|
||||||
let start_col = state.column + 1 + buf.len() as u16 + 3;
|
|
||||||
let end_line = start_line;
|
|
||||||
|
|
||||||
// We want to underline only the number. That's the error!
|
|
||||||
// -1 because we want to end on the last digit, not
|
|
||||||
// overshoot it.
|
|
||||||
let end_col = start_col + hex_str.len() as u16 - 1;
|
|
||||||
|
|
||||||
let region = Region {
|
|
||||||
start_line,
|
|
||||||
start_col,
|
|
||||||
end_line,
|
|
||||||
end_col,
|
|
||||||
};
|
|
||||||
|
|
||||||
problems.push(Loc {
|
|
||||||
region,
|
|
||||||
value: Problem::UnicodeCodePointTooLarge,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// If it all checked out, add it to
|
|
||||||
// the main buffer.
|
|
||||||
match char::from_u32(code_pt) {
|
|
||||||
Some(ch) => buf.push(ch),
|
|
||||||
None => {
|
|
||||||
problems.push(loc_escaped_unicode(
|
|
||||||
Problem::InvalidUnicodeCodePoint,
|
|
||||||
&state,
|
|
||||||
start_of_unicode,
|
|
||||||
hex_str.len(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
let problem = if hex_str.is_empty() {
|
|
||||||
Problem::NoUnicodeDigits
|
|
||||||
} else {
|
|
||||||
Problem::NonHexCharsInUnicodeCodePoint
|
|
||||||
};
|
|
||||||
|
|
||||||
problems.push(loc_escaped_unicode(
|
|
||||||
problem,
|
|
||||||
&state,
|
|
||||||
start_of_unicode,
|
|
||||||
hex_str.len(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are now done processing the unicode portion of the string,
|
|
||||||
// so exit the loop without further advancing the iterator.
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
'\t' => {
|
|
||||||
// Report and continue.
|
|
||||||
// Tabs are syntax errors, but maybe the rest of the string is fine!
|
|
||||||
problems.push(loc_escaped_unicode(
|
|
||||||
Problem::Tab,
|
|
||||||
&state,
|
|
||||||
start_of_unicode,
|
|
||||||
hex_str.len(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
'\r' => {
|
|
||||||
// Report and continue.
|
|
||||||
// Carriage returns aren't allowed in string literals,
|
|
||||||
// but maybe the rest of the string is fine!
|
|
||||||
problems.push(loc_escaped_unicode(
|
|
||||||
Problem::CarriageReturn,
|
|
||||||
&state,
|
|
||||||
start_of_unicode,
|
|
||||||
hex_str.len(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
'\n' => {
|
|
||||||
// Report and bail out.
|
|
||||||
// We can't safely assume where the string was supposed to end.
|
|
||||||
problems.push(loc_escaped_unicode(
|
|
||||||
Problem::NewlineInLiteral,
|
|
||||||
&state,
|
|
||||||
start_of_unicode,
|
|
||||||
hex_str.len(),
|
|
||||||
));
|
|
||||||
|
|
||||||
return Err(unexpected_eof(
|
|
||||||
buf.len(),
|
|
||||||
Attempting::UnicodeEscape,
|
|
||||||
state.clone(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
normal_char => hex_str.push(normal_char),
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're about to hit the end of the string, and we didn't already
|
|
||||||
// complete parsing a valid unicode escape sequence, this is a malformed
|
|
||||||
// escape sequence - it wasn't terminated!
|
|
||||||
if chars.peek() == Some(&'"') {
|
|
||||||
// Record a problem and exit the loop early, so the string literal
|
|
||||||
// parsing logic can consume the quote and do its job as normal.
|
|
||||||
let start_line = state.line;
|
|
||||||
// +1 due to the `"` which precedes buf.
|
|
||||||
let start_col = state.column + buf.len() as u16 + 1;
|
|
||||||
let end_line = start_line;
|
|
||||||
// +3 due to the `\u{`
|
|
||||||
// -1 to prevent overshooting because end col is inclusive.
|
|
||||||
let end_col = start_col + 3 + hex_str.len() as u16 - 1;
|
|
||||||
|
|
||||||
let region = Region {
|
|
||||||
start_line,
|
|
||||||
start_col,
|
|
||||||
end_line,
|
|
||||||
end_col,
|
|
||||||
};
|
|
||||||
|
|
||||||
problems.push(Loc {
|
|
||||||
region,
|
|
||||||
value: Problem::MalformedEscapedUnicode,
|
|
||||||
});
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn parse_interpolated_ident<'a, I>(
|
|
||||||
arena: &'a Bump,
|
|
||||||
state: &State<'a>,
|
|
||||||
chars: &mut Peekable<I>,
|
|
||||||
) -> Result<&'a str, (Fail, State<'a>)>
|
|
||||||
where
|
|
||||||
I: Iterator<Item = char>,
|
|
||||||
{
|
|
||||||
// This will return Err on invalid identifiers like "if"
|
|
||||||
let ((string, next_char), state) = ident::parse_into(arena, chars, state.clone())?;
|
|
||||||
|
|
||||||
// Make sure we got a closing ) to end the interpolation.
|
|
||||||
match next_char {
|
|
||||||
Some(')') => Ok(string),
|
|
||||||
Some(ch) => Err(unexpected(ch, 0, state, Attempting::InterpolatedString)),
|
|
||||||
None => Err(unexpected_eof(0, Attempting::InterpolatedString, state)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,19 @@ pub struct Located<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Located<T> {
|
impl<T> Located<T> {
|
||||||
pub fn new(value: T, region: Region) -> Located<T> {
|
pub fn new(
|
||||||
|
start_line: u32,
|
||||||
|
start_col: u16,
|
||||||
|
end_line: u32,
|
||||||
|
end_col: u16,
|
||||||
|
value: T,
|
||||||
|
) -> Located<T> {
|
||||||
|
let region = Region {
|
||||||
|
start_line,
|
||||||
|
start_col,
|
||||||
|
end_line,
|
||||||
|
end_col,
|
||||||
|
};
|
||||||
Located { value, region }
|
Located { value, region }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
// , _errors :: [Error.Error]
|
// , _errors :: [Error.Error]
|
||||||
// }
|
// }
|
||||||
|
|
||||||
use canonicalize::Symbol;
|
use can::symbol::Symbol;
|
||||||
use collections::ImMap;
|
use collections::ImMap;
|
||||||
use subs::{Content, Descriptor, FlatType, Subs, Variable};
|
use subs::{Content, Descriptor, FlatType, Subs, Variable};
|
||||||
use types::Constraint::{self, *};
|
use types::Constraint::{self, *};
|
||||||
|
@ -23,13 +23,15 @@ pub fn solve(env: &Env, subs: &mut Subs, constraint: Constraint) {
|
||||||
// println!("\nSolving:\n\n\t{:?}\n\n", constraint);
|
// println!("\nSolving:\n\n\t{:?}\n\n", constraint);
|
||||||
match constraint {
|
match constraint {
|
||||||
True => (),
|
True => (),
|
||||||
Eq(typ, expected_type, region) => {
|
Eq(typ, expected_type, _region) => {
|
||||||
|
// TODO use region?
|
||||||
let actual = type_to_variable(subs, typ);
|
let actual = type_to_variable(subs, typ);
|
||||||
let expected = type_to_variable(subs, expected_type.get_type());
|
let expected = type_to_variable(subs, expected_type.get_type());
|
||||||
|
|
||||||
subs.union(actual, expected);
|
subs.union(actual, expected);
|
||||||
}
|
}
|
||||||
Lookup(symbol, expected_type, region) => {
|
Lookup(symbol, expected_type, _region) => {
|
||||||
|
// TODO use region?
|
||||||
let actual =
|
let actual =
|
||||||
subs.copy_var(env.get(&symbol).unwrap_or_else(|| {
|
subs.copy_var(env.get(&symbol).unwrap_or_else(|| {
|
||||||
panic!("Could not find symbol {:?} in env {:?}", symbol, env)
|
panic!("Could not find symbol {:?} in env {:?}", symbol, env)
|
||||||
|
|
|
@ -192,7 +192,7 @@ impl Into<String> for RocStr {
|
||||||
fn into(self) -> String {
|
fn into(self) -> String {
|
||||||
let len_msbyte = self.len_msbyte();
|
let len_msbyte = self.len_msbyte();
|
||||||
|
|
||||||
panic!("I'm not sure this works the way we want it to. Need to review.");
|
// TODO I'm not sure this works the way we want it to. Need to review.
|
||||||
|
|
||||||
if flagged_as_short_string(len_msbyte) {
|
if flagged_as_short_string(len_msbyte) {
|
||||||
// Drop the "is this a short string?" flag
|
// Drop the "is this a short string?" flag
|
||||||
|
@ -208,7 +208,6 @@ impl Into<String> for RocStr {
|
||||||
// same memory layout as a Rust &str slice.
|
// same memory layout as a Rust &str slice.
|
||||||
let str_slice = unsafe { mem::transmute::<[u8; 16], &str>(self.0.raw) };
|
let str_slice = unsafe { mem::transmute::<[u8; 16], &str>(self.0.raw) };
|
||||||
let string = str_slice.to_string();
|
let string = str_slice.to_string();
|
||||||
let mut roc_str_mut = self;
|
|
||||||
|
|
||||||
// Drop will deallocate the bytes, which we don't want in this case.
|
// Drop will deallocate the bytes, which we don't want in this case.
|
||||||
// String is using those bytes now!
|
// String is using those bytes now!
|
||||||
|
@ -241,14 +240,14 @@ impl From<String> for RocStr {
|
||||||
|
|
||||||
RocStr(InnerStr { raw: buffer })
|
RocStr(InnerStr { raw: buffer })
|
||||||
} else {
|
} else {
|
||||||
let bytes_ptr = string.as_bytes().clone().as_ptr();
|
|
||||||
let long = LongStr {
|
|
||||||
bytes: MaybeUninit::new(bytes_ptr),
|
|
||||||
length: str_len,
|
|
||||||
};
|
|
||||||
|
|
||||||
panic!("TODO: use mem::forget on the string and steal its bytes!");
|
panic!("TODO: use mem::forget on the string and steal its bytes!");
|
||||||
RocStr(InnerStr { long })
|
// let bytes_ptr = string.as_bytes().clone().as_ptr();
|
||||||
|
// let long = LongStr {
|
||||||
|
// bytes: MaybeUninit::new(bytes_ptr),
|
||||||
|
// length: str_len,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// RocStr(InnerStr { long })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,6 @@ impl Subs {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mk_flex_var(&mut self) -> Variable {
|
pub fn mk_flex_var(&mut self) -> Variable {
|
||||||
/// TODO is "flex" the same as "unbound" and "rigid" the same as "bound"?!
|
|
||||||
self.fresh(flex_var_descriptor())
|
self.fresh(flex_var_descriptor())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use canonicalize::Symbol;
|
use can::symbol::Symbol;
|
||||||
use collections::ImMap;
|
use collections::ImMap;
|
||||||
use operator::{ArgSide, Operator};
|
use operator::{ArgSide, Operator};
|
||||||
use region::Located;
|
use region::Located;
|
||||||
|
|
|
@ -1,183 +1,76 @@
|
||||||
use combine::error::ParseError;
|
extern crate bumpalo;
|
||||||
use combine::stream::state::State;
|
extern crate roc;
|
||||||
use combine::stream::Stream;
|
|
||||||
use combine::{eof, Parser};
|
use self::bumpalo::Bump;
|
||||||
use roc::collections::MutMap;
|
use roc::can;
|
||||||
use roc::deprecated::parse_state::IndentablePosition;
|
use roc::can::expr::Expr;
|
||||||
use roc::expr::{Expr, Pattern};
|
use roc::can::problem::Problem;
|
||||||
|
use roc::can::procedure::Procedure;
|
||||||
|
use roc::can::symbol::Symbol;
|
||||||
|
use roc::can::Output;
|
||||||
|
use roc::collections::{ImMap, MutMap};
|
||||||
|
use roc::ident::Ident;
|
||||||
|
use roc::parse;
|
||||||
|
use roc::parse::ast::{self, Attempting};
|
||||||
|
use roc::parse::parser::{Fail, Parser, State};
|
||||||
use roc::region::{Located, Region};
|
use roc::region::{Located, Region};
|
||||||
use std::hash::Hash;
|
|
||||||
|
|
||||||
pub fn loc_box<T>(val: T) -> Box<Located<T>> {
|
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> {
|
||||||
Box::new(loc(val))
|
let state = State::new(&input, Attempting::Module);
|
||||||
|
let parser = parse::expr();
|
||||||
|
let answer = parser.parse(&arena, state);
|
||||||
|
|
||||||
|
answer.map(|(expr, _)| expr).map_err(|(fail, _)| fail)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn loc<T>(val: T) -> Located<T> {
|
#[allow(dead_code)]
|
||||||
Located::new(val, Region::zero())
|
pub fn can_expr(expr_str: &str) -> (Expr, Output, Vec<Problem>, MutMap<Symbol, Procedure>) {
|
||||||
}
|
can_expr_with(
|
||||||
|
&Bump::new(),
|
||||||
pub fn located<T>(
|
"blah",
|
||||||
start_line: u32,
|
expr_str,
|
||||||
start_col: u16,
|
&ImMap::default(),
|
||||||
end_line: u32,
|
&ImMap::default(),
|
||||||
end_col: u16,
|
|
||||||
val: T,
|
|
||||||
) -> Located<T> {
|
|
||||||
Located::new(
|
|
||||||
val,
|
|
||||||
Region {
|
|
||||||
start_line,
|
|
||||||
start_col,
|
|
||||||
end_line,
|
|
||||||
end_col,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn zero_loc<T>(located_val: Located<T>) -> Located<T> {
|
#[allow(dead_code)]
|
||||||
loc(located_val.value)
|
pub fn can_expr_with(
|
||||||
}
|
arena: &Bump,
|
||||||
|
name: &str,
|
||||||
/// Zero out the parse locations on everything in this Expr, so we can compare expected/actual without
|
expr_str: &str,
|
||||||
/// having to account for that.
|
declared_idents: &ImMap<Ident, (Symbol, Region)>,
|
||||||
pub fn zero_loc_expr(expr: Expr) -> Expr {
|
declared_variants: &ImMap<Symbol, Located<Box<ast::VariantName>>>,
|
||||||
use roc::expr::Expr::*;
|
) -> (Expr, Output, Vec<Problem>, MutMap<Symbol, Procedure>) {
|
||||||
|
let expr = parse_with(&arena, expr_str).unwrap_or_else(|_| {
|
||||||
match expr {
|
panic!(
|
||||||
Int(_) | Float(_) | EmptyStr | Str(_) | Char(_) | Var(_) | EmptyRecord | EmptyList => expr,
|
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?}",
|
||||||
InterpolatedStr(pairs, string) => InterpolatedStr(
|
expr_str
|
||||||
pairs
|
|
||||||
.into_iter()
|
|
||||||
.map(|(prefix, ident)| (prefix, zero_loc(ident)))
|
|
||||||
.collect(),
|
|
||||||
string,
|
|
||||||
),
|
|
||||||
List(elems) => {
|
|
||||||
let zeroed_elems = elems
|
|
||||||
.into_iter()
|
|
||||||
.map(|loc_expr| loc(zero_loc_expr(loc_expr.value)))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
List(zeroed_elems)
|
|
||||||
}
|
|
||||||
Assign(assignments, loc_ret) => {
|
|
||||||
let zeroed_assignments = assignments
|
|
||||||
.into_iter()
|
|
||||||
.map(|(pattern, loc_expr)| {
|
|
||||||
(
|
|
||||||
zero_loc_pattern(pattern),
|
|
||||||
loc(zero_loc_expr(loc_expr.value)),
|
|
||||||
)
|
)
|
||||||
})
|
});
|
||||||
.collect();
|
|
||||||
|
|
||||||
Assign(zeroed_assignments, loc_box(zero_loc_expr((*loc_ret).value)))
|
let home = "Test".to_string();
|
||||||
}
|
let (loc_expr, output, problems, procedures) = can::canonicalize_declaration(
|
||||||
Apply(fn_expr, args) => Apply(
|
home,
|
||||||
loc_box(zero_loc_expr((*fn_expr).value)),
|
|
||||||
args.into_iter()
|
|
||||||
.map(|arg| loc(zero_loc_expr(arg.value)))
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
Operator(left, op, right) => Operator(
|
|
||||||
loc_box(zero_loc_expr((*left).value)),
|
|
||||||
zero_loc(op),
|
|
||||||
loc_box(zero_loc_expr((*right).value)),
|
|
||||||
),
|
|
||||||
Closure(patterns, body) => Closure(
|
|
||||||
patterns.into_iter().map(zero_loc).collect(),
|
|
||||||
loc_box(zero_loc_expr((*body).value)),
|
|
||||||
),
|
|
||||||
ApplyVariant(_, None) => expr,
|
|
||||||
ApplyVariant(name, Some(args)) => ApplyVariant(
|
|
||||||
name,
|
name,
|
||||||
Some(
|
Located::new(0, 0, 0, 0, expr),
|
||||||
args.into_iter()
|
declared_idents,
|
||||||
.map(|arg| loc(zero_loc_expr(arg.value)))
|
declared_variants,
|
||||||
.collect(),
|
);
|
||||||
),
|
|
||||||
),
|
(loc_expr.value, output, problems, procedures)
|
||||||
If(condition, if_true, if_false) => If(
|
|
||||||
loc_box(zero_loc_expr((*condition).value)),
|
|
||||||
loc_box(zero_loc_expr((*if_true).value)),
|
|
||||||
loc_box(zero_loc_expr((*if_false).value)),
|
|
||||||
),
|
|
||||||
Case(condition, branches) => Case(
|
|
||||||
loc_box(zero_loc_expr((*condition).value)),
|
|
||||||
branches
|
|
||||||
.into_iter()
|
|
||||||
.map(|(pattern, loc_expr)| {
|
|
||||||
(
|
|
||||||
zero_loc_pattern(pattern),
|
|
||||||
loc(zero_loc_expr(loc_expr.value)),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Zero out the parse locations on everything in this Pattern, so we can compare expected/actual without
|
// pub fn mut_map_from_pairs<K, V, I>(pairs: I) -> MutMap<K, V>
|
||||||
/// having to account for that.
|
// where
|
||||||
pub fn zero_loc_pattern(loc_pattern: Located<Pattern>) -> Located<Pattern> {
|
// I: IntoIterator<Item = (K, V)>,
|
||||||
use roc::expr::Pattern::*;
|
// K: Hash + Eq,
|
||||||
|
// {
|
||||||
|
// let mut answer = MutMap::default();
|
||||||
|
|
||||||
let pattern = loc_pattern.value;
|
// for (key, value) in pairs {
|
||||||
|
// answer.insert(key, value);
|
||||||
|
// }
|
||||||
|
|
||||||
match pattern {
|
// answer
|
||||||
Identifier(_) | IntLiteral(_) | FloatLiteral(_) | ExactString(_) | EmptyRecordLiteral
|
// }
|
||||||
| Underscore => loc(pattern),
|
|
||||||
Variant(loc_name, None) => loc(Variant(loc(loc_name.value), None)),
|
|
||||||
Variant(loc_name, Some(opt_located_patterns)) => loc(Variant(
|
|
||||||
loc(loc_name.value),
|
|
||||||
Some(
|
|
||||||
opt_located_patterns
|
|
||||||
.into_iter()
|
|
||||||
.map(|loc_pat| zero_loc_pattern(loc_pat))
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)] // For some reason rustc thinks this isn't used. It is, though, in test_canonicalize.rs
|
|
||||||
pub fn mut_map_from_pairs<K, V, I>(pairs: I) -> MutMap<K, V>
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = (K, V)>,
|
|
||||||
K: Hash + Eq,
|
|
||||||
{
|
|
||||||
let mut answer = MutMap::default();
|
|
||||||
|
|
||||||
for (key, value) in pairs {
|
|
||||||
answer.insert(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
answer
|
|
||||||
}
|
|
||||||
|
|
||||||
// PARSE HELPERS
|
|
||||||
|
|
||||||
#[allow(dead_code)] // For some reason rustc thinks this isn't used. It is, though, in test_parse.rs
|
|
||||||
pub fn standalone_expr<I>() -> impl Parser<Input = I, Output = Expr>
|
|
||||||
where
|
|
||||||
I: Stream<Item = char, Position = IndentablePosition>,
|
|
||||||
I::Error: ParseError<I::Item, I::Range, I::Position>,
|
|
||||||
{
|
|
||||||
roc::deprecated::parse::expr().skip(eof())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)] // For some reason rustc thinks this isn't used. It is, though, in test_parse.rs
|
|
||||||
pub fn parse_without_loc(actual_str: &str) -> Result<(Expr, String), String> {
|
|
||||||
parse_standalone(actual_str).map(|(expr, leftover)| (zero_loc_expr(expr), leftover))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)] // For some reason rustc thinks this isn't used. It is, though, in test_parse.rs
|
|
||||||
pub fn parse_standalone(actual_str: &str) -> Result<(Expr, String), String> {
|
|
||||||
let parse_state: State<&str, IndentablePosition> =
|
|
||||||
State::with_positioner(actual_str, IndentablePosition::default());
|
|
||||||
|
|
||||||
match standalone_expr().easy_parse(parse_state) {
|
|
||||||
Ok((expr, state)) => Ok((expr, state.input.to_string())),
|
|
||||||
Err(errors) => Err(errors.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,127 +0,0 @@
|
||||||
// #[macro_use] extern crate pretty_assertions;
|
|
||||||
extern crate combine;
|
|
||||||
extern crate fraction;
|
|
||||||
|
|
||||||
extern crate roc;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test_eval {
|
|
||||||
// use roc::operator::Operator::*;
|
|
||||||
// use roc::expr::Pattern::*;
|
|
||||||
// use roc::expr::Expr::*;
|
|
||||||
// use roc::expr::{Expr, Raw};
|
|
||||||
// use roc::eval;
|
|
||||||
// use roc::eval::Evaluated;
|
|
||||||
// use roc::region::{Located, Region};
|
|
||||||
// use fraction::Fraction;
|
|
||||||
|
|
||||||
// fn loc_box<T>(val: T) -> Box<Located<T>> {
|
|
||||||
// Box::new(loc(val))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn eval(expr: Expr<Raw>) -> Evaluated {
|
|
||||||
// eval::eval(loc(expr))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn loc<T>(val: T) -> Located<T> {
|
|
||||||
// Located::new(val, Region {
|
|
||||||
// start_line: 0,
|
|
||||||
// start_col: 0,
|
|
||||||
|
|
||||||
// end_line: 0,
|
|
||||||
// end_col: 0,
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn one_plus_one() {
|
|
||||||
// assert_eq!(
|
|
||||||
// eval(Operator(loc_box(Int(1)), loc(Plus), loc_box(Int(1)))),
|
|
||||||
// Evaluated::Int(2)
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn point_one_plus_point_two() {
|
|
||||||
// // 0.1 + 0.2 == 0.3 THAT'S WHAT'S UP
|
|
||||||
// assert_eq!(
|
|
||||||
// eval(Operator(loc_box(Frac(1, 10)), loc(Plus), loc_box(Frac(2, 10)))),
|
|
||||||
// Evaluated::Frac(Fraction::new(3u64, 10u64))
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn addition_reduces() {
|
|
||||||
// assert_eq!(
|
|
||||||
// eval(Operator(loc_box(Frac(1, 3)), loc(Plus), loc_box(Frac(7, 14)))),
|
|
||||||
// Evaluated::Frac(Fraction::new(5u64, 6u64))
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn division_reduces() {
|
|
||||||
// assert_eq!(
|
|
||||||
// eval(Operator(loc_box(Frac(1, 3)), loc(Slash), loc_box(Frac(7, 14)))),
|
|
||||||
// Evaluated::ApplyVariant(
|
|
||||||
// "Ok".to_string(),
|
|
||||||
// Some(vec![Evaluated::Frac(Fraction::new(2u64, 3u64))])
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn division_by_zero() {
|
|
||||||
// assert_eq!(
|
|
||||||
// eval(Operator(loc_box(Frac(1, 10)), loc(Slash), loc_box(Frac(0, 10)))),
|
|
||||||
// Evaluated::ApplyVariant(
|
|
||||||
// "Err".to_string(),
|
|
||||||
// Some(vec![Evaluated::ApplyVariant("DivisionByZero".to_string(), None)])
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn string_interpolation() {
|
|
||||||
// assert_eq!(
|
|
||||||
// eval(
|
|
||||||
// Assign(loc(Identifier("foo".to_string())), loc_box(Str("one".to_string())),
|
|
||||||
// loc_box(Assign(loc(Identifier("bar".to_string())), loc_box(Str("two".to_string())),
|
|
||||||
// loc_box(Assign(loc(Identifier("baz".to_string())), loc_box(Str("three".to_string())),
|
|
||||||
// loc_box(InterpolatedStr(
|
|
||||||
// // "hi_\(foo)_\(bar)_\(baz)_string!"
|
|
||||||
// vec![
|
|
||||||
// ("hi_".to_string(), loc(Raw::new("foo".to_string()))),
|
|
||||||
// ("_".to_string(), loc(Raw::new("bar".to_string()))),
|
|
||||||
// ("_".to_string(), loc(Raw::new("baz".to_string()))),
|
|
||||||
// ],
|
|
||||||
// "_string!".to_string()
|
|
||||||
// ))
|
|
||||||
// )))))
|
|
||||||
// ),
|
|
||||||
// Evaluated::Str("hi_one_two_three_string!".to_string())
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn if_else() {
|
|
||||||
// assert_eq!(
|
|
||||||
// eval(
|
|
||||||
// If(loc_box(ApplyVariant("True".to_string(), None)),
|
|
||||||
// loc_box(Operator(loc_box(Int(1)), loc(Plus), loc_box(Int(2)))),
|
|
||||||
// loc_box(Operator(loc_box(Int(4)), loc(Plus), loc_box(Int(5))))
|
|
||||||
// )
|
|
||||||
// ),
|
|
||||||
// Evaluated::Int(3)
|
|
||||||
// );
|
|
||||||
|
|
||||||
// assert_eq!(
|
|
||||||
// eval(
|
|
||||||
// If(loc_box(ApplyVariant("False".to_string(), None)),
|
|
||||||
// loc_box(Operator(loc_box(Int(1)), loc(Plus), loc_box(Int(2)))),
|
|
||||||
// loc_box(Operator(loc_box(Int(4)), loc(Plus), loc_box(Int(5))))
|
|
||||||
// )
|
|
||||||
// ),
|
|
||||||
// Evaluated::Int(9)
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
}
|
|
|
@ -3,13 +3,10 @@ extern crate pretty_assertions;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate indoc;
|
extern crate indoc;
|
||||||
extern crate bumpalo;
|
extern crate bumpalo;
|
||||||
extern crate combine; // OBSOLETE
|
|
||||||
extern crate roc;
|
extern crate roc;
|
||||||
|
|
||||||
mod helpers;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_formatter {
|
mod test_format {
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use roc::parse;
|
use roc::parse;
|
||||||
use roc::parse::ast::{Attempting, Expr};
|
use roc::parse::ast::{Attempting, Expr};
|
||||||
|
@ -85,21 +82,28 @@ mod test_formatter {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn basic_string() {
|
fn basic_string() {
|
||||||
// assert_formats_same(indoc!(
|
assert_formats_same(indoc!(
|
||||||
// r#"
|
r#"
|
||||||
// "blah"
|
"blah"
|
||||||
// "#
|
"#
|
||||||
// ));
|
));
|
||||||
// }
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn escaped_unicode_string() {
|
fn escaped_unicode_string() {
|
||||||
// assert_formats_same(indoc!(
|
assert_formats_same(indoc!(
|
||||||
// r#"
|
r#"
|
||||||
// "unicode: \u{A00A}!"
|
"unicode: \u{A00A}!"
|
||||||
// "#
|
"#
|
||||||
// ));
|
));
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
// RECORD LITERALS
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_record() {
|
||||||
|
assert_formats_same("{}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,7 +3,6 @@ extern crate pretty_assertions;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate indoc;
|
extern crate indoc;
|
||||||
extern crate bumpalo;
|
extern crate bumpalo;
|
||||||
extern crate combine; // OBSOLETE
|
|
||||||
extern crate roc;
|
extern crate roc;
|
||||||
|
|
||||||
extern crate quickcheck;
|
extern crate quickcheck;
|
||||||
|
@ -14,25 +13,15 @@ extern crate quickcheck_macros;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_parser {
|
mod test_parse {
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use helpers::located;
|
use helpers::parse_with;
|
||||||
use roc::parse;
|
|
||||||
use roc::parse::ast::Attempting;
|
use roc::parse::ast::Attempting;
|
||||||
use roc::parse::ast::Expr::{self, *};
|
use roc::parse::ast::Expr::{self, *};
|
||||||
use roc::parse::parser::{Fail, FailReason, Parser, State};
|
use roc::parse::parser::{Fail, FailReason};
|
||||||
use roc::parse::problems::Problem;
|
use roc::region::Region;
|
||||||
use roc::region::{Located, Region};
|
|
||||||
use std::{f64, i64};
|
use std::{f64, i64};
|
||||||
|
|
||||||
fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Expr<'a>, Fail> {
|
|
||||||
let state = State::new(&input, Attempting::Module);
|
|
||||||
let parser = parse::expr();
|
|
||||||
let answer = parser.parse(&arena, state);
|
|
||||||
|
|
||||||
answer.map(|(expr, _)| expr).map_err(|(fail, _)| fail)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_parses_to<'a>(input: &'a str, expected_expr: Expr<'a>) {
|
fn assert_parses_to<'a>(input: &'a str, expected_expr: Expr<'a>) {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let actual = parse_with(&arena, input);
|
let actual = parse_with(&arena, input);
|
||||||
|
@ -48,16 +37,6 @@ mod test_parser {
|
||||||
assert_eq!(Err(expected_fail), actual);
|
assert_eq!(Err(expected_fail), actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_malformed_str<'a>(input: &'a str, expected_probs: Vec<Located<Problem>>) {
|
|
||||||
let arena = Bump::new();
|
|
||||||
let actual = parse_with(&arena, input);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Ok(Expr::MalformedStr(expected_probs.into_boxed_slice())),
|
|
||||||
actual
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// STRING LITERALS
|
// STRING LITERALS
|
||||||
|
|
||||||
fn expect_parsed_str(input: &str, expected: &str) {
|
fn expect_parsed_str(input: &str, expected: &str) {
|
||||||
|
@ -77,7 +56,7 @@ mod test_parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_char_list() {
|
fn one_char_string() {
|
||||||
assert_parses_to(
|
assert_parses_to(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
@ -89,7 +68,7 @@ mod test_parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multi_char_list() {
|
fn multi_char_string() {
|
||||||
assert_parses_to(
|
assert_parses_to(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
@ -113,24 +92,11 @@ mod test_parser {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn string_with_special_escapes() {
|
fn string_with_special_escapes() {
|
||||||
expect_parsed_str(r#"x\x"#, r#""x\\x""#);
|
expect_parsed_str(r#"x\\x"#, r#""x\\x""#);
|
||||||
expect_parsed_str(r#"x"x"#, r#""x\"x""#);
|
expect_parsed_str(r#"x\"x"#, r#""x\"x""#);
|
||||||
expect_parsed_str("x\tx", r#""x\tx""#);
|
expect_parsed_str(r#"x\tx"#, r#""x\tx""#);
|
||||||
expect_parsed_str("x\rx", r#""x\rx""#);
|
expect_parsed_str(r#"x\rx"#, r#""x\rx""#);
|
||||||
expect_parsed_str("x\nx", r#""x\nx""#);
|
expect_parsed_str(r#"x\nx"#, r#""x\nx""#);
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_with_escaped_interpolation() {
|
|
||||||
assert_parses_to(
|
|
||||||
// This should NOT be string interpolation, because of the \\
|
|
||||||
indoc!(
|
|
||||||
r#"
|
|
||||||
"abcd\\(efg)hij"
|
|
||||||
"#
|
|
||||||
),
|
|
||||||
Str(r#"abcd\(efg)hij"#.into()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -139,159 +105,6 @@ mod test_parser {
|
||||||
expect_parsed_str("x'x", r#""x'x""#);
|
expect_parsed_str("x'x", r#""x'x""#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_with_valid_unicode_escapes() {
|
|
||||||
expect_parsed_str("x\u{00A0}x", r#""x\u{00A0}x""#);
|
|
||||||
expect_parsed_str("x\u{101010}x", r#""x\u{101010}x""#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_with_too_large_unicode_escape() {
|
|
||||||
// Should be too big - max size should be 10FFFF.
|
|
||||||
// (Rust has this restriction. I assume it's a good idea.)
|
|
||||||
assert_malformed_str(
|
|
||||||
r#""abc\u{110000}def""#,
|
|
||||||
vec![located(0, 7, 0, 12, Problem::UnicodeCodePointTooLarge)],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_with_no_unicode_digits() {
|
|
||||||
// No digits specified
|
|
||||||
assert_malformed_str(
|
|
||||||
r#""blah\u{}foo""#,
|
|
||||||
vec![located(0, 5, 0, 8, Problem::NoUnicodeDigits)],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_with_no_unicode_opening_brace() {
|
|
||||||
// No opening curly brace. It can't be sure if the closing brace
|
|
||||||
// was intended to be a closing brace for the unicode escape, so
|
|
||||||
// report that there were no digits specified.
|
|
||||||
assert_malformed_str(
|
|
||||||
r#""abc\u00A0}def""#,
|
|
||||||
vec![located(0, 4, 0, 5, Problem::NoUnicodeDigits)],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_with_no_unicode_closing_brace() {
|
|
||||||
// No closing curly brace
|
|
||||||
assert_malformed_str(
|
|
||||||
r#""blah\u{stuff""#,
|
|
||||||
vec![located(0, 5, 0, 12, Problem::MalformedEscapedUnicode)],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_with_no_unicode_braces() {
|
|
||||||
// No curly braces
|
|
||||||
assert_malformed_str(
|
|
||||||
r#""zzzz\uzzzzz""#,
|
|
||||||
vec![located(0, 5, 0, 6, Problem::NoUnicodeDigits)],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_with_interpolation_at_start() {
|
|
||||||
let input = indoc!(
|
|
||||||
r#"
|
|
||||||
"\(abc)defg"
|
|
||||||
"#
|
|
||||||
);
|
|
||||||
let (args, ret) = (vec![("", located(0, 2, 0, 4, Var("abc")))], "defg");
|
|
||||||
let arena = Bump::new();
|
|
||||||
let actual = parse_with(&arena, input);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Ok(InterpolatedStr(&(arena.alloc_slice_clone(&args), ret))),
|
|
||||||
actual
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_with_interpolation_at_end() {
|
|
||||||
let input = indoc!(
|
|
||||||
r#"
|
|
||||||
"abcd\(efg)"
|
|
||||||
"#
|
|
||||||
);
|
|
||||||
let (args, ret) = (vec![("abcd", located(0, 6, 0, 8, Var("efg")))], "");
|
|
||||||
let arena = Bump::new();
|
|
||||||
let actual = parse_with(&arena, input);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Ok(InterpolatedStr(&(arena.alloc_slice_clone(&args), ret))),
|
|
||||||
actual
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_with_interpolation_in_middle() {
|
|
||||||
let input = indoc!(
|
|
||||||
r#"
|
|
||||||
"abc\(defg)hij"
|
|
||||||
"#
|
|
||||||
);
|
|
||||||
let (args, ret) = (vec![("abc", located(0, 5, 0, 8, Var("defg")))], "hij");
|
|
||||||
let arena = Bump::new();
|
|
||||||
let actual = parse_with(&arena, input);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Ok(InterpolatedStr(&(arena.alloc_slice_clone(&args), ret))),
|
|
||||||
actual
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_with_two_interpolations_in_middle() {
|
|
||||||
let input = indoc!(
|
|
||||||
r#"
|
|
||||||
"abc\(defg)hi\(jkl)mn"
|
|
||||||
"#
|
|
||||||
);
|
|
||||||
let (args, ret) = (
|
|
||||||
vec![
|
|
||||||
("abc", located(0, 5, 0, 8, Var("defg"))),
|
|
||||||
("hi", located(0, 14, 0, 16, Var("jkl"))),
|
|
||||||
],
|
|
||||||
"mn",
|
|
||||||
);
|
|
||||||
let arena = Bump::new();
|
|
||||||
let actual = parse_with(&arena, input);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Ok(InterpolatedStr(&(arena.alloc_slice_clone(&args), ret))),
|
|
||||||
actual
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_with_four_interpolations() {
|
|
||||||
let input = indoc!(
|
|
||||||
r#"
|
|
||||||
"\(abc)def\(ghi)jkl\(mno)pqrs\(tuv)"
|
|
||||||
"#
|
|
||||||
);
|
|
||||||
let (args, ret) = (
|
|
||||||
vec![
|
|
||||||
("", located(0, 2, 0, 4, Var("abc"))),
|
|
||||||
("def", located(0, 11, 0, 13, Var("ghi"))),
|
|
||||||
("jkl", located(0, 20, 0, 22, Var("mno"))),
|
|
||||||
("pqrs", located(0, 30, 0, 32, Var("tuv"))),
|
|
||||||
],
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
let arena = Bump::new();
|
|
||||||
let actual = parse_with(&arena, input);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Ok(InterpolatedStr(&(arena.alloc_slice_clone(&args), ret))),
|
|
||||||
actual
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_source_file() {
|
fn empty_source_file() {
|
||||||
assert_parsing_fails("", FailReason::Eof(Region::zero()), Attempting::Expression);
|
assert_parsing_fails("", FailReason::Eof(Region::zero()), Attempting::Expression);
|
||||||
|
@ -322,124 +135,109 @@ mod test_parser {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn zero_int() {
|
fn zero_int() {
|
||||||
assert_parses_to("0", Int(0));
|
assert_parses_to("0", Int("0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn positive_int() {
|
fn positive_int() {
|
||||||
assert_parses_to("1", Int(1));
|
assert_parses_to("1", Int("1"));
|
||||||
assert_parses_to("42", Int(42));
|
assert_parses_to("42", Int("42"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn negative_int() {
|
fn negative_int() {
|
||||||
assert_parses_to("-1", Int(-1));
|
assert_parses_to("-1", Int("-1"));
|
||||||
assert_parses_to("-42", Int(-42));
|
assert_parses_to("-42", Int("-42"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn highest_int() {
|
fn highest_int() {
|
||||||
assert_parses_to(i64::MAX.to_string().as_str(), Int(i64::MAX));
|
assert_parses_to(
|
||||||
|
i64::MAX.to_string().as_str(),
|
||||||
|
Int(i64::MAX.to_string().as_str()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lowest_int() {
|
fn lowest_int() {
|
||||||
assert_parses_to(i64::MIN.to_string().as_str(), Int(i64::MIN));
|
assert_parses_to(
|
||||||
|
i64::MIN.to_string().as_str(),
|
||||||
|
Int(i64::MIN.to_string().as_str()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn int_with_underscore() {
|
fn int_with_underscore() {
|
||||||
assert_parses_to("1_2_34_567", Int(1234567));
|
assert_parses_to("1_2_34_567", Int("1_2_34_567"));
|
||||||
assert_parses_to("-1_2_34_567", Int(-1234567));
|
assert_parses_to("-1_2_34_567", Int("-1_2_34_567"));
|
||||||
// The following cases are silly. They aren't supported on purpose,
|
// The following cases are silly. They aren't supported on purpose,
|
||||||
// but there would be a performance cost to explicitly disallowing them,
|
// but there would be a performance cost to explicitly disallowing them,
|
||||||
// which doesn't seem like it would benefit anyone.
|
// which doesn't seem like it would benefit anyone.
|
||||||
assert_parses_to("1_", Int(1));
|
assert_parses_to("1_", Int("1_"));
|
||||||
assert_parses_to("1__23", Int(123));
|
assert_parses_to("1__23", Int("1__23"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[quickcheck]
|
#[quickcheck]
|
||||||
fn all_i64_values_parse(num: i64) {
|
fn all_i64_values_parse(num: i64) {
|
||||||
assert_parses_to(num.to_string().as_str(), Int(num));
|
assert_parses_to(num.to_string().as_str(), Int(num.to_string().as_str()));
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn int_too_large() {
|
|
||||||
assert_parses_to(
|
|
||||||
(i64::MAX as i128 + 1).to_string().as_str(),
|
|
||||||
MalformedInt(Problem::OutsideSupportedRange),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn int_too_small() {
|
|
||||||
assert_parses_to(
|
|
||||||
(i64::MIN as i128 - 1).to_string().as_str(),
|
|
||||||
MalformedInt(Problem::OutsideSupportedRange),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FLOAT LITERALS
|
// FLOAT LITERALS
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn zero_float() {
|
fn zero_float() {
|
||||||
assert_parses_to("0.0", Float(0.0));
|
assert_parses_to("0.0", Float("0.0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn positive_float() {
|
fn positive_float() {
|
||||||
assert_parses_to("1.0", Float(1.0));
|
assert_parses_to("1.0", Float("1.0"));
|
||||||
assert_parses_to("1.1", Float(1.1));
|
assert_parses_to("1.1", Float("1.1"));
|
||||||
assert_parses_to("42.0", Float(42.0));
|
assert_parses_to("42.0", Float("42.0"));
|
||||||
assert_parses_to("42.9", Float(42.9));
|
assert_parses_to("42.9", Float("42.9"));
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn highest_float() {
|
|
||||||
assert_parses_to(&format!("{}.0", f64::MAX), Float(f64::MAX));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn negative_float() {
|
fn negative_float() {
|
||||||
assert_parses_to("-1.0", Float(-1.0));
|
assert_parses_to("-1.0", Float("-1.0"));
|
||||||
assert_parses_to("-1.1", Float(-1.1));
|
assert_parses_to("-1.1", Float("-1.1"));
|
||||||
assert_parses_to("-42.0", Float(-42.0));
|
assert_parses_to("-42.0", Float("-42.0"));
|
||||||
assert_parses_to("-42.9", Float(-42.9));
|
assert_parses_to("-42.9", Float("-42.9"));
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn lowest_float() {
|
|
||||||
assert_parses_to(&format!("{}.0", f64::MIN), Float(f64::MIN));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn float_with_underscores() {
|
fn float_with_underscores() {
|
||||||
assert_parses_to("1_23_456.0_1_23_456", Float(123456.0123456));
|
assert_parses_to("1_23_456.0_1_23_456", Float("1_23_456.0_1_23_456"));
|
||||||
assert_parses_to("-1_23_456.0_1_23_456", Float(-123456.0123456));
|
assert_parses_to("-1_23_456.0_1_23_456", Float("-1_23_456.0_1_23_456"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn highest_float() {
|
||||||
|
let string = format!("{}.0", f64::MAX);
|
||||||
|
|
||||||
|
assert_parses_to(&string, Float(&string));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lowest_float() {
|
||||||
|
let string = format!("{}.0", f64::MIN);
|
||||||
|
|
||||||
|
assert_parses_to(&string, Float(&string));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[quickcheck]
|
#[quickcheck]
|
||||||
fn all_f64_values_parse(num: f64) {
|
fn all_f64_values_parse(num: f64) {
|
||||||
assert_parses_to(num.to_string().as_str(), Float(num));
|
assert_parses_to(num.to_string().as_str(), Float(num.to_string().as_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RECORD LITERALS
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn float_too_large() {
|
fn empty_record() {
|
||||||
assert_parses_to(
|
assert_parses_to("{}", EmptyRecord);
|
||||||
format!("{}1.0", f64::MAX).as_str(),
|
|
||||||
MalformedFloat(Problem::OutsideSupportedRange),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
// TODO test hex/oct/binary parsing
|
||||||
fn float_too_small() {
|
|
||||||
assert_parses_to(
|
|
||||||
format!("{}1.0", f64::MIN).as_str(),
|
|
||||||
MalformedFloat(Problem::OutsideSupportedRange),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO test what happens when interpolated strings contain 1+ malformed idents
|
|
||||||
//
|
//
|
||||||
// TODO test for \t \r and \n in string literals *outside* unicode escape sequence!
|
// TODO test for \t \r and \n in string literals *outside* unicode escape sequence!
|
||||||
//
|
//
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue