mirror of
https://github.com/roc-lang/roc.git
synced 2024-11-09 12:16:43 +03:00
Merge pull request #1591 from rtfeldman/single-quote-literal
Single Quote literal
This commit is contained in:
commit
d3acf34415
@ -1474,6 +1474,15 @@ pub fn constrain_pattern<'a>(
|
||||
));
|
||||
}
|
||||
|
||||
CharacterLiteral(_) => {
|
||||
state.constraints.push(Constraint::Pattern(
|
||||
region,
|
||||
PatternCategory::Character,
|
||||
num_unsigned32(env.pool),
|
||||
expected,
|
||||
));
|
||||
}
|
||||
|
||||
RecordDestructure {
|
||||
whole_var,
|
||||
ext_var,
|
||||
@ -1927,6 +1936,26 @@ fn _num_signed64(pool: &mut Pool) -> Type2 {
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn num_unsigned32(pool: &mut Pool) -> Type2 {
|
||||
let alias_content = Type2::TagUnion(
|
||||
PoolVec::new(
|
||||
std::iter::once((
|
||||
TagName::Private(Symbol::NUM_UNSIGNED32),
|
||||
PoolVec::empty(pool),
|
||||
)),
|
||||
pool,
|
||||
),
|
||||
pool.add(Type2::EmptyTagUnion),
|
||||
);
|
||||
|
||||
Type2::Alias(
|
||||
Symbol::NUM_UNSIGNED32,
|
||||
PoolVec::empty(pool),
|
||||
pool.add(alias_content),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 {
|
||||
let range_type = pool.get(range);
|
||||
|
@ -39,6 +39,7 @@ pub enum Pattern2 {
|
||||
IntLiteral(IntVal), // 16B
|
||||
FloatLiteral(FloatVal), // 16B
|
||||
StrLiteral(PoolStr), // 8B
|
||||
CharacterLiteral(char), // 4B
|
||||
Underscore, // 0B
|
||||
GlobalTag {
|
||||
whole_var: Variable, // 4B
|
||||
@ -249,6 +250,26 @@ pub fn to_pattern2<'a>(
|
||||
ptype => unsupported_pattern(env, ptype, region),
|
||||
},
|
||||
|
||||
SingleQuote(string) => match pattern_type {
|
||||
WhenBranch => {
|
||||
let mut it = string.chars().peekable();
|
||||
if let Some(char) = it.next() {
|
||||
if it.peek().is_none() {
|
||||
Pattern2::CharacterLiteral(char)
|
||||
} else {
|
||||
// multiple chars is found
|
||||
let problem = MalformedPatternProblem::MultipleCharsInSingleQuote;
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
} else {
|
||||
// no characters found
|
||||
let problem = MalformedPatternProblem::EmptySingleQuote;
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
}
|
||||
ptype => unsupported_pattern(env, ptype, region),
|
||||
},
|
||||
|
||||
GlobalTag(name) => {
|
||||
// Canonicalize the tag's name.
|
||||
Pattern2::GlobalTag {
|
||||
@ -506,6 +527,7 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec<Symbol> {
|
||||
| IntLiteral(_)
|
||||
| FloatLiteral(_)
|
||||
| StrLiteral(_)
|
||||
| CharacterLiteral(_)
|
||||
| Underscore
|
||||
| MalformedPattern(_, _)
|
||||
| Shadowed { .. }
|
||||
@ -566,6 +588,7 @@ pub fn symbols_and_variables_from_pattern(
|
||||
| IntLiteral(_)
|
||||
| FloatLiteral(_)
|
||||
| StrLiteral(_)
|
||||
| CharacterLiteral(_)
|
||||
| Underscore
|
||||
| MalformedPattern(_, _)
|
||||
| Shadowed { .. }
|
||||
|
@ -607,6 +607,7 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
|
||||
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
|
||||
Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
Expr::SingleQuote(a) => Expr::Num(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -649,6 +650,7 @@ impl<'a> RemoveSpaces<'a> for Pattern<'a> {
|
||||
}
|
||||
Pattern::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Pattern::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
Pattern::SingleQuote(a) => Pattern::NumLiteral(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1
cli_utils/Cargo.lock
generated
1
cli_utils/Cargo.lock
generated
@ -2696,6 +2696,7 @@ dependencies = [
|
||||
"roc_mono",
|
||||
"roc_problem",
|
||||
"roc_region",
|
||||
"roc_reporting",
|
||||
"roc_solve",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
|
@ -854,6 +854,7 @@ fn pattern_to_vars_by_symbol(
|
||||
| IntLiteral(..)
|
||||
| FloatLiteral(..)
|
||||
| StrLiteral(_)
|
||||
| SingleQuote(_)
|
||||
| Underscore
|
||||
| MalformedPattern(_, _)
|
||||
| UnsupportedPattern(_)
|
||||
|
@ -73,6 +73,7 @@ pub enum Expr {
|
||||
Int(Variable, Variable, Box<str>, IntValue, IntBound),
|
||||
Float(Variable, Variable, Box<str>, f64, FloatBound),
|
||||
Str(Box<str>),
|
||||
SingleQuote(char),
|
||||
List {
|
||||
elem_var: Variable,
|
||||
loc_elems: Vec<Loc<Expr>>,
|
||||
@ -323,6 +324,28 @@ pub fn canonicalize_expr<'a>(
|
||||
}
|
||||
}
|
||||
ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal),
|
||||
|
||||
ast::Expr::SingleQuote(string) => {
|
||||
let mut it = string.chars().peekable();
|
||||
if let Some(char) = it.next() {
|
||||
if it.peek().is_none() {
|
||||
(Expr::SingleQuote(char), Output::default())
|
||||
} else {
|
||||
// multiple chars is found
|
||||
let error = roc_problem::can::RuntimeError::MultipleCharsInSingleQuote(region);
|
||||
let answer = Expr::RuntimeError(error);
|
||||
|
||||
(answer, Output::default())
|
||||
}
|
||||
} else {
|
||||
// no characters found
|
||||
let error = roc_problem::can::RuntimeError::EmptySingleQuote(region);
|
||||
let answer = Expr::RuntimeError(error);
|
||||
|
||||
(answer, Output::default())
|
||||
}
|
||||
}
|
||||
|
||||
ast::Expr::List(loc_elems) => {
|
||||
if loc_elems.is_empty() {
|
||||
(
|
||||
@ -1267,6 +1290,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
||||
| other @ Int(..)
|
||||
| other @ Float(..)
|
||||
| other @ Str { .. }
|
||||
| other @ SingleQuote(_)
|
||||
| other @ RuntimeError(_)
|
||||
| other @ EmptyRecord
|
||||
| other @ Accessor { .. }
|
||||
|
@ -572,6 +572,7 @@ fn fix_values_captured_in_closure_pattern(
|
||||
| IntLiteral(..)
|
||||
| FloatLiteral(..)
|
||||
| StrLiteral(_)
|
||||
| SingleQuote(_)
|
||||
| Underscore
|
||||
| Shadowed(..)
|
||||
| MalformedPattern(_, _)
|
||||
@ -629,6 +630,7 @@ fn fix_values_captured_in_closure_expr(
|
||||
| Int(..)
|
||||
| Float(..)
|
||||
| Str(_)
|
||||
| SingleQuote(_)
|
||||
| Var(_)
|
||||
| EmptyRecord
|
||||
| RuntimeError(_)
|
||||
|
@ -126,6 +126,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
|
||||
| Num(..)
|
||||
| NonBase10Int { .. }
|
||||
| Str(_)
|
||||
| SingleQuote(_)
|
||||
| AccessorFunction(_)
|
||||
| Var { .. }
|
||||
| Underscore { .. }
|
||||
|
@ -39,6 +39,7 @@ pub enum Pattern {
|
||||
IntLiteral(Variable, Variable, Box<str>, IntValue, IntBound),
|
||||
FloatLiteral(Variable, Variable, Box<str>, f64, FloatBound),
|
||||
StrLiteral(Box<str>),
|
||||
SingleQuote(char),
|
||||
Underscore,
|
||||
|
||||
// Runtime Exceptions
|
||||
@ -108,6 +109,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
||||
| IntLiteral(..)
|
||||
| FloatLiteral(..)
|
||||
| StrLiteral(_)
|
||||
| SingleQuote(_)
|
||||
| Underscore
|
||||
| MalformedPattern(_, _)
|
||||
| UnsupportedPattern(_)
|
||||
@ -309,6 +311,23 @@ pub fn canonicalize_pattern<'a>(
|
||||
ptype => unsupported_pattern(env, ptype, region),
|
||||
},
|
||||
|
||||
SingleQuote(string) => {
|
||||
let mut it = string.chars().peekable();
|
||||
if let Some(char) = it.next() {
|
||||
if it.peek().is_none() {
|
||||
Pattern::SingleQuote(char)
|
||||
} else {
|
||||
// multiple chars is found
|
||||
let problem = MalformedPatternProblem::MultipleCharsInSingleQuote;
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
} else {
|
||||
// no characters found
|
||||
let problem = MalformedPatternProblem::EmptySingleQuote;
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
}
|
||||
|
||||
SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => {
|
||||
return canonicalize_pattern(env, var_store, scope, pattern_type, sub_pattern, region)
|
||||
}
|
||||
@ -560,6 +579,7 @@ fn add_bindings_from_patterns(
|
||||
| IntLiteral(..)
|
||||
| FloatLiteral(..)
|
||||
| StrLiteral(_)
|
||||
| SingleQuote(_)
|
||||
| Underscore
|
||||
| MalformedPattern(_, _)
|
||||
| UnsupportedPattern(_)
|
||||
|
@ -193,6 +193,21 @@ pub fn num_floatingpoint(range: Type) -> Type {
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn num_u32() -> Type {
|
||||
builtin_alias(Symbol::NUM_U32, vec![], Box::new(num_int(num_unsigned32())))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn num_unsigned32() -> Type {
|
||||
let alias_content = Type::TagUnion(
|
||||
vec![(TagName::Private(Symbol::NUM_AT_UNSIGNED32), vec![])],
|
||||
Box::new(Type::EmptyTagUnion),
|
||||
);
|
||||
|
||||
builtin_alias(Symbol::NUM_UNSIGNED32, vec![], Box::new(alias_content))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn num_binary64() -> Type {
|
||||
let alias_content = Type::TagUnion(
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::builtins::{
|
||||
empty_list_type, float_literal, int_literal, list_type, num_literal, str_type,
|
||||
empty_list_type, float_literal, int_literal, list_type, num_literal, num_u32, str_type,
|
||||
};
|
||||
use crate::pattern::{constrain_pattern, PatternState};
|
||||
use roc_can::annotation::IntroducedVariables;
|
||||
@ -213,6 +213,7 @@ pub fn constrain_expr(
|
||||
exists(vars, And(cons))
|
||||
}
|
||||
Str(_) => Eq(str_type(), expected, Category::Str, region),
|
||||
SingleQuote(_) => Eq(num_u32(), expected, Category::Character, region),
|
||||
List {
|
||||
elem_var,
|
||||
loc_elems,
|
||||
|
@ -60,6 +60,7 @@ fn headers_from_annotation_help(
|
||||
| NumLiteral(..)
|
||||
| IntLiteral(..)
|
||||
| FloatLiteral(..)
|
||||
| SingleQuote(_)
|
||||
| StrLiteral(_) => true,
|
||||
|
||||
RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() {
|
||||
@ -252,6 +253,15 @@ pub fn constrain_pattern(
|
||||
));
|
||||
}
|
||||
|
||||
SingleQuote(_) => {
|
||||
state.constraints.push(Constraint::Pattern(
|
||||
region,
|
||||
PatternCategory::Character,
|
||||
builtins::num_u32(),
|
||||
expected,
|
||||
));
|
||||
}
|
||||
|
||||
RecordDestructure {
|
||||
whole_var,
|
||||
ext_var,
|
||||
|
@ -30,6 +30,7 @@ impl<'a> Formattable for Expr<'a> {
|
||||
Float(..)
|
||||
| Num(..)
|
||||
| NonBase10Int { .. }
|
||||
| SingleQuote(_)
|
||||
| Access(_, _)
|
||||
| AccessorFunction(_)
|
||||
| Var { .. }
|
||||
@ -209,6 +210,11 @@ impl<'a> Formattable for Expr<'a> {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string)
|
||||
}
|
||||
SingleQuote(string) => {
|
||||
buf.push('\'');
|
||||
buf.push_str(string);
|
||||
buf.push('\'');
|
||||
}
|
||||
&NonBase10Int {
|
||||
base,
|
||||
string,
|
||||
|
@ -36,6 +36,7 @@ impl<'a> Formattable for Pattern<'a> {
|
||||
| Pattern::NonBase10Literal { .. }
|
||||
| Pattern::FloatLiteral(..)
|
||||
| Pattern::StrLiteral(_)
|
||||
| Pattern::SingleQuote(_)
|
||||
| Pattern::Underscore(_)
|
||||
| Pattern::Malformed(_)
|
||||
| Pattern::MalformedIdent(_, _)
|
||||
@ -147,6 +148,11 @@ impl<'a> Formattable for Pattern<'a> {
|
||||
StrLiteral(literal) => {
|
||||
todo!("Format string literal: {:?}", literal);
|
||||
}
|
||||
SingleQuote(string) => {
|
||||
buf.push('\'');
|
||||
buf.push_str(string);
|
||||
buf.push('\'');
|
||||
}
|
||||
Underscore(name) => {
|
||||
buf.indent(indent);
|
||||
buf.push('_');
|
||||
|
@ -2040,8 +2040,11 @@ fn pattern_to_when<'a>(
|
||||
}
|
||||
|
||||
UnwrappedOpaque { .. } => todo_opaques!(),
|
||||
|
||||
IntLiteral(..) | NumLiteral(..) | FloatLiteral(..) | StrLiteral(_) => {
|
||||
IntLiteral(..)
|
||||
| NumLiteral(..)
|
||||
| FloatLiteral(..)
|
||||
| StrLiteral(..)
|
||||
| roc_can::pattern::Pattern::SingleQuote(..) => {
|
||||
// These patters are refutable, and thus should never occur outside a `when` expression
|
||||
// They should have been replaced with `UnsupportedPattern` during canonicalization
|
||||
unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value)
|
||||
@ -3147,6 +3150,13 @@ pub fn with_hole<'a>(
|
||||
hole,
|
||||
),
|
||||
|
||||
SingleQuote(character) => Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(Literal::Int(character as _)),
|
||||
Layout::int_width(IntWidth::I32),
|
||||
hole,
|
||||
),
|
||||
|
||||
Num(var, num_str, num, _bound) => {
|
||||
// first figure out what kind of number this is
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, var, false) {
|
||||
@ -7746,6 +7756,7 @@ fn from_can_pattern_help<'a>(
|
||||
}
|
||||
}
|
||||
StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())),
|
||||
SingleQuote(c) => Ok(Pattern::IntLiteral(*c as _, IntWidth::I32)),
|
||||
Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing {
|
||||
original_region: *region,
|
||||
shadow: ident.clone(),
|
||||
|
@ -152,6 +152,8 @@ pub enum Expr<'a> {
|
||||
Access(&'a Expr<'a>, &'a str),
|
||||
/// e.g. `.foo`
|
||||
AccessorFunction(&'a str),
|
||||
/// eg 'b'
|
||||
SingleQuote(&'a str),
|
||||
|
||||
// Collection Literals
|
||||
List(Collection<'a, &'a Loc<Expr<'a>>>),
|
||||
@ -462,6 +464,7 @@ pub enum Pattern<'a> {
|
||||
FloatLiteral(&'a str),
|
||||
StrLiteral(StrLiteral<'a>),
|
||||
Underscore(&'a str),
|
||||
SingleQuote(&'a str),
|
||||
|
||||
// Space
|
||||
SpaceBefore(&'a Pattern<'a>, &'a [CommentOrNewline<'a>]),
|
||||
|
@ -195,6 +195,7 @@ fn parse_loc_term_or_underscore<'a>(
|
||||
one_of!(
|
||||
loc_expr_in_parens_etc_help(min_indent),
|
||||
loc!(specialize(EExpr::Str, string_literal_help())),
|
||||
loc!(specialize(EExpr::SingleQuote, single_quote_literal_help())),
|
||||
loc!(specialize(EExpr::Number, positive_number_literal_help())),
|
||||
loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))),
|
||||
loc!(underscore_expression()),
|
||||
@ -217,6 +218,7 @@ fn parse_loc_term<'a>(
|
||||
one_of!(
|
||||
loc_expr_in_parens_etc_help(min_indent),
|
||||
loc!(specialize(EExpr::Str, string_literal_help())),
|
||||
loc!(specialize(EExpr::SingleQuote, single_quote_literal_help())),
|
||||
loc!(specialize(EExpr::Number, positive_number_literal_help())),
|
||||
loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))),
|
||||
loc!(record_literal_help(min_indent)),
|
||||
@ -1534,6 +1536,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
|
||||
| Expr::UnaryOp(_, _) => Err(()),
|
||||
|
||||
Expr::Str(string) => Ok(Pattern::StrLiteral(*string)),
|
||||
Expr::SingleQuote(string) => Ok(Pattern::SingleQuote(*string)),
|
||||
Expr::MalformedIdent(string, _problem) => Ok(Pattern::Malformed(string)),
|
||||
}
|
||||
}
|
||||
@ -2352,6 +2355,13 @@ fn string_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EString<'a>> {
|
||||
map!(crate::string_literal::parse(), Expr::Str)
|
||||
}
|
||||
|
||||
fn single_quote_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EString<'a>> {
|
||||
map!(
|
||||
crate::string_literal::parse_single_quote(),
|
||||
Expr::SingleQuote
|
||||
)
|
||||
}
|
||||
|
||||
fn positive_number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> {
|
||||
map!(
|
||||
crate::number_literal::positive_number_literal(),
|
||||
|
@ -355,6 +355,7 @@ pub enum EExpr<'a> {
|
||||
InParens(EInParens<'a>, Position),
|
||||
Record(ERecord<'a>, Position),
|
||||
Str(EString<'a>, Position),
|
||||
SingleQuote(EString<'a>, Position),
|
||||
Number(ENumber, Position),
|
||||
List(EList<'a>, Position),
|
||||
|
||||
|
@ -61,6 +61,7 @@ pub fn loc_pattern_help<'a>(min_indent: u32) -> impl Parser<'a, Loc<Pattern<'a>>
|
||||
)),
|
||||
loc!(number_pattern_help()),
|
||||
loc!(string_pattern_help()),
|
||||
loc!(single_quote_pattern_help()),
|
||||
)
|
||||
}
|
||||
|
||||
@ -108,6 +109,7 @@ fn loc_parse_tag_pattern_arg<'a>(
|
||||
crate::pattern::record_pattern_help(min_indent)
|
||||
)),
|
||||
loc!(string_pattern_help()),
|
||||
loc!(single_quote_pattern_help()),
|
||||
loc!(number_pattern_help())
|
||||
)
|
||||
.parse(arena, state)
|
||||
@ -159,6 +161,16 @@ fn string_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
|
||||
)
|
||||
}
|
||||
|
||||
fn single_quote_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
|
||||
specialize(
|
||||
|_, pos| EPattern::Start(pos),
|
||||
map!(
|
||||
crate::string_literal::parse_single_quote(),
|
||||
Pattern::SingleQuote
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn loc_ident_pattern_help<'a>(
|
||||
min_indent: u32,
|
||||
can_have_arguments: bool,
|
||||
|
@ -35,6 +35,100 @@ macro_rules! advance_state {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn parse_single_quote<'a>() -> impl Parser<'a, &'a str, EString<'a>> {
|
||||
move |arena: &'a Bump, mut state: State<'a>| {
|
||||
if state.bytes().starts_with(b"\'") {
|
||||
// we will be parsing a single-quote-string
|
||||
} else {
|
||||
return Err((NoProgress, EString::Open(state.pos()), state));
|
||||
}
|
||||
|
||||
// early return did not hit, just advance one byte
|
||||
state = advance_state!(state, 1)?;
|
||||
|
||||
// Handle back slaches in byte literal
|
||||
// - starts with a backslash and used as an escape character. ex: '\n', '\t'
|
||||
// - single quote floating (un closed single quote) should be an error
|
||||
match state.bytes().first() {
|
||||
Some(b'\\') => {
|
||||
state = advance_state!(state, 1)?;
|
||||
match state.bytes().first() {
|
||||
Some(&ch) => {
|
||||
state = advance_state!(state, 1)?;
|
||||
if (ch == b'n' || ch == b'r' || ch == b't' || ch == b'\'' || ch == b'\\')
|
||||
&& (state.bytes().first() == Some(&b'\''))
|
||||
{
|
||||
state = advance_state!(state, 1)?;
|
||||
let test = match ch {
|
||||
b'n' => '\n',
|
||||
b't' => '\t',
|
||||
b'r' => '\r',
|
||||
// since we checked the current char between the single quotes we
|
||||
// know they are valid UTF-8, allowing us to use 'from_u32_unchecked'
|
||||
_ => unsafe { char::from_u32_unchecked(ch as u32) },
|
||||
};
|
||||
|
||||
return Ok((MadeProgress, &*arena.alloc_str(&test.to_string()), state));
|
||||
}
|
||||
// invalid error, backslah escaping something we do not recognize
|
||||
return Err((NoProgress, EString::CodePtEnd(state.pos()), state));
|
||||
}
|
||||
None => {
|
||||
// no close quote found
|
||||
return Err((NoProgress, EString::CodePtEnd(state.pos()), state));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(_) => {
|
||||
// do nothing for other characters, handled below
|
||||
}
|
||||
None => return Err((NoProgress, EString::CodePtEnd(state.pos()), state)),
|
||||
}
|
||||
|
||||
let mut bytes = state.bytes().iter();
|
||||
let mut end_index = 1;
|
||||
|
||||
// Copy paste problem in mono
|
||||
|
||||
loop {
|
||||
match bytes.next() {
|
||||
Some(b'\'') => {
|
||||
break;
|
||||
}
|
||||
Some(_) => end_index += 1,
|
||||
None => {
|
||||
return Err((NoProgress, EString::Open(state.pos()), state));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if end_index == 1 {
|
||||
// no progress was made
|
||||
// this case is a double single quote, ex: ''
|
||||
// not supporting empty single quotes
|
||||
return Err((NoProgress, EString::Open(state.pos()), state));
|
||||
}
|
||||
|
||||
if end_index > (std::mem::size_of::<u32>() + 1) {
|
||||
// bad case: too big to fit into u32
|
||||
return Err((NoProgress, EString::Open(state.pos()), state));
|
||||
}
|
||||
|
||||
// happy case -> we have some bytes that will fit into a u32
|
||||
// ending up w/ a slice of bytes that we want to convert into an integer
|
||||
let raw_bytes = &state.bytes()[0..end_index - 1];
|
||||
|
||||
state = advance_state!(state, end_index)?;
|
||||
match std::str::from_utf8(raw_bytes) {
|
||||
Ok(string) => Ok((MadeProgress, string, state)),
|
||||
Err(_) => {
|
||||
// invalid UTF-8
|
||||
return Err((NoProgress, EString::CodePtEnd(state.pos()), state));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> {
|
||||
use StrLiteral::*;
|
||||
|
||||
|
@ -507,19 +507,28 @@ mod test_parse {
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn all_f64_values_parse(num: f64) {
|
||||
let string = num.to_string();
|
||||
if string.contains('.') {
|
||||
assert_parses_to(&string, Float(&string));
|
||||
} else if num.is_nan() {
|
||||
assert_parses_to(&string, Expr::GlobalTag(&string));
|
||||
} else if num.is_finite() {
|
||||
// These are whole numbers. Add the `.0` back to make float.
|
||||
let float_string = format!("{}.0", string);
|
||||
assert_parses_to(&float_string, Float(&float_string));
|
||||
fn all_f64_values_parse(mut num: f64) {
|
||||
// NaN, Infinity, -Infinity (these would all parse as tags in Roc)
|
||||
if !num.is_finite() {
|
||||
num = 0.0;
|
||||
}
|
||||
|
||||
// These can potentially be whole numbers. `Display` omits the decimal point for those,
|
||||
// causing them to no longer be parsed as fractional numbers by Roc.
|
||||
// Using `Debug` instead of `Display` ensures they always have a decimal point.
|
||||
let float_string = format!("{:?}", num);
|
||||
|
||||
assert_parses_to(float_string.as_str(), Float(float_string.as_str()));
|
||||
}
|
||||
|
||||
// SINGLE QUOTE LITERAL
|
||||
#[test]
|
||||
fn single_quote() {
|
||||
assert_parses_to("'b'", Expr::SingleQuote("b"));
|
||||
}
|
||||
|
||||
// RECORD LITERALS
|
||||
|
||||
// #[test]
|
||||
// fn type_signature_def() {
|
||||
// let arena = Bump::new();
|
||||
|
@ -230,6 +230,11 @@ pub enum RuntimeError {
|
||||
VoidValue,
|
||||
|
||||
ExposedButNotDefined(Symbol),
|
||||
|
||||
/// where ''
|
||||
EmptySingleQuote(Region),
|
||||
/// where 'aa'
|
||||
MultipleCharsInSingleQuote(Region),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
@ -240,4 +245,6 @@ pub enum MalformedPatternProblem {
|
||||
Unknown,
|
||||
QualifiedIdentifier,
|
||||
BadIdent(roc_parse::ident::BadIdent),
|
||||
EmptySingleQuote,
|
||||
MultipleCharsInSingleQuote,
|
||||
}
|
||||
|
@ -357,6 +357,66 @@ fn u8_hex_int_alias() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn character_literal() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x = 'A'
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
65,
|
||||
u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn character_literal_back_slash() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x = '\\'
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
92,
|
||||
u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn character_literal_single_quote() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x = '\''
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
39,
|
||||
u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn character_literal_new_line() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x = '\n'
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
10,
|
||||
u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn dec_float_alias() {
|
||||
|
@ -1304,6 +1304,7 @@ pub enum Category {
|
||||
Num,
|
||||
List,
|
||||
Str,
|
||||
Character,
|
||||
|
||||
// records
|
||||
Record,
|
||||
@ -1325,6 +1326,7 @@ pub enum PatternCategory {
|
||||
Num,
|
||||
Int,
|
||||
Float,
|
||||
Character,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
|
@ -945,13 +945,17 @@ fn pretty_runtime_error<'b>(
|
||||
}
|
||||
Unknown => " ",
|
||||
QualifiedIdentifier => " qualified ",
|
||||
EmptySingleQuote => " empty character literal ",
|
||||
MultipleCharsInSingleQuote => " overfull literal ",
|
||||
};
|
||||
|
||||
let tip = match problem {
|
||||
MalformedInt | MalformedFloat | MalformedBase(_) => alloc
|
||||
.tip()
|
||||
.append(alloc.reflow("Learn more about number literals at TODO")),
|
||||
Unknown | BadIdent(_) => alloc.nil(),
|
||||
EmptySingleQuote | MultipleCharsInSingleQuote | Unknown | BadIdent(_) => {
|
||||
alloc.nil()
|
||||
}
|
||||
QualifiedIdentifier => alloc.tip().append(
|
||||
alloc.reflow("In patterns, only private and global tags can be qualified"),
|
||||
),
|
||||
@ -1341,6 +1345,37 @@ fn pretty_runtime_error<'b>(
|
||||
|
||||
title = MISSING_DEFINITION;
|
||||
}
|
||||
RuntimeError::EmptySingleQuote(region) => {
|
||||
let tip = alloc
|
||||
.tip()
|
||||
.append(alloc.reflow("Learn more about character literals at TODO"));
|
||||
|
||||
doc = alloc.stack(vec![
|
||||
alloc.concat(vec![alloc.reflow("This character literal is empty.")]),
|
||||
alloc.region(lines.convert_region(region)),
|
||||
tip,
|
||||
]);
|
||||
|
||||
title = SYNTAX_PROBLEM;
|
||||
}
|
||||
RuntimeError::MultipleCharsInSingleQuote(region) => {
|
||||
let tip = alloc
|
||||
.tip()
|
||||
.append(alloc.reflow("Learn more about character literals at TODO"));
|
||||
|
||||
doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("This character literal contains more than one code point.")
|
||||
]),
|
||||
alloc.region(lines.convert_region(region)),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Character literals can only contain one code point.")
|
||||
]),
|
||||
tip,
|
||||
]);
|
||||
|
||||
title = SYNTAX_PROBLEM;
|
||||
}
|
||||
RuntimeError::OpaqueNotDefined {
|
||||
usage:
|
||||
Loc {
|
||||
|
@ -1100,7 +1100,6 @@ fn format_category<'b>(
|
||||
]),
|
||||
alloc.text(" produces:"),
|
||||
),
|
||||
|
||||
List => (
|
||||
alloc.concat(vec![this_is, alloc.text(" a list")]),
|
||||
alloc.text(" of type:"),
|
||||
@ -1128,17 +1127,18 @@ fn format_category<'b>(
|
||||
]),
|
||||
alloc.text(" which was of type:"),
|
||||
),
|
||||
|
||||
Character => (
|
||||
alloc.concat(vec![this_is, alloc.text(" a character")]),
|
||||
alloc.text(" of type:"),
|
||||
),
|
||||
Lambda => (
|
||||
alloc.concat(vec![this_is, alloc.text(" an anonymous function")]),
|
||||
alloc.text(" of type:"),
|
||||
),
|
||||
|
||||
ClosureSize => (
|
||||
alloc.concat(vec![this_is, alloc.text(" the closure size of a function")]),
|
||||
alloc.text(" of type:"),
|
||||
),
|
||||
|
||||
TagApply {
|
||||
tag_name: TagName::Global(name),
|
||||
args_count: 0,
|
||||
@ -1472,6 +1472,7 @@ fn add_pattern_category<'b>(
|
||||
Num => alloc.reflow(" numbers:"),
|
||||
Int => alloc.reflow(" integers:"),
|
||||
Float => alloc.reflow(" floats:"),
|
||||
Character => alloc.reflow(" characters:"),
|
||||
};
|
||||
|
||||
alloc.concat(vec![i_am_trying_to_match, rest])
|
||||
|
Loading…
Reference in New Issue
Block a user