Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for hexadecimal numbers in Lisp #540

Merged
merged 13 commits into from
Nov 17, 2023
7 changes: 6 additions & 1 deletion doc/lisp.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ MOROS Lisp is a Lisp-1 dialect inspired by Scheme, Clojure, and Ruby!

### Types
- Basics: `bool`, `list`, `symbol`, `string`
- Numbers: `float`, `int`, `bigint`
- Number: `float`, `int`, `bigint`

### Literals
- String: `"Hello, World!"`
- Number: `2.5`, `-25`, `255`, `0xFF`, `0xDEAD_C0DE`

### Built-in Operators
- `quote` (abbreviated with `'`)
Expand Down Expand Up @@ -203,3 +207,4 @@ language and reading from the filesystem.
- Add socket functions

### Unreleased
- Add hexadecimal number literals
22 changes: 22 additions & 0 deletions src/usr/lisp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,25 @@ fn test_lisp() {
};
}

// num
assert_eq!(eval!("6"), "6");
assert_eq!(eval!("16"), "16");
assert_eq!(eval!("0x6"), "6");
assert_eq!(eval!("0xf"), "15");
assert_eq!(eval!("0x10"), "16");
assert_eq!(eval!("1.5"), "1.5");
assert_eq!(eval!("0xff"), "255");
assert_eq!(eval!("0XFF"), "255");

assert_eq!(eval!("-6"), "-6");
assert_eq!(eval!("-16"), "-16");
assert_eq!(eval!("-0x6"), "-6");
assert_eq!(eval!("-0xF"), "-15");
assert_eq!(eval!("-0x10"), "-16");
assert_eq!(eval!("-1.5"), "-1.5");
assert_eq!(eval!("-0xff"), "-255");
assert_eq!(eval!("-0XFF"), "-255");

// quote
assert_eq!(eval!("(quote (1 2 3))"), "(1 2 3)");
assert_eq!(eval!("'(1 2 3)"), "(1 2 3)");
Expand Down Expand Up @@ -500,6 +519,9 @@ fn test_lisp() {
// bigint
assert_eq!(eval!("9223372036854775807"), "9223372036854775807"); // -> int
assert_eq!(eval!("9223372036854775808"), "9223372036854775808"); // -> bigint
assert_eq!(eval!("0x7fffffffffffffff"), "9223372036854775807"); // -> int
assert_eq!(eval!("0x8000000000000000"), "9223372036854775808"); // -> bigint
assert_eq!(eval!("0x800000000000000f"), "9223372036854775823"); // -> bigint
assert_eq!(eval!("(+ 9223372036854775807 0)"), "9223372036854775807"); // -> int
assert_eq!(eval!("(- 9223372036854775808 1)"), "9223372036854775807"); // -> bigint
assert_eq!(eval!("(+ 9223372036854775807 1)"), "9223372036854775808"); // -> bigint
Expand Down
56 changes: 33 additions & 23 deletions src/usr/lisp/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ use alloc::format;
use alloc::vec::Vec;
use core::convert::TryFrom;
use core::fmt;
use core::num::ParseIntError;
use core::ops::{Neg, Add, Div, Mul, Sub, Rem, Shl, Shr};
use core::str::FromStr;
use num_bigint::BigInt;
use num_bigint::ParseBigIntError;
use num_traits::Num;
use num_traits::Zero;
use num_traits::cast::ToPrimitive;

Expand Down Expand Up @@ -196,39 +199,46 @@ operator!(Rem, rem);
operator!(Shl, shl);
operator!(Shr, shr);

fn parse_int(s: &str) -> Result<i64, ParseIntError> {
if s.starts_with("0x") || s.starts_with("0X") {
i64::from_str_radix(&s[2..], 16)
} else if s.starts_with("-0x") || s.starts_with("-0X") {
i64::from_str_radix(&s[3..], 16).map(|n| -n)
} else {
i64::from_str_radix(s, 10)
}
}

fn parse_float(s: &str) -> Result<BigInt, ParseBigIntError> {
if s.starts_with("0x") || s.starts_with("0X") {
BigInt::from_str_radix(&s[2..], 16)
} else if s.starts_with("-0x") || s.starts_with("-0X") {
BigInt::from_str_radix(&s[3..], 16).map(|n| -n)
} else {
BigInt::from_str_radix(s, 10)
}
}

impl FromStr for Number {
type Err = Err;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let err = could_not!("parse number");
if s.is_empty() {
return Ok(Number::Int(0));
Ok(Number::Int(0))
} else if s.contains('.') {
if let Ok(n) = s.parse() {
return Ok(Number::Float(n));
Ok(Number::Float(n))
} else {
err
}
} else if let Ok(n) = s.parse() {
return Ok(Number::Int(n));
} else if let Ok(n) = parse_int(s) {
Ok(Number::Int(n))
} else if let Ok(n) = parse_float(s) {
Ok(Number::BigInt(n))
} else {
let mut chars = s.chars().peekable();
let is_neg = chars.peek() == Some(&'-');
if is_neg {
chars.next().unwrap();
}
let mut res = BigInt::from(0);
for c in chars {
if !c.is_ascii_digit() {
return err;
}
let d = c as u8 - b'0';
res = res * BigInt::from(10) + BigInt::from(d as u32);
}
res *= BigInt::from(if is_neg { -1 } else { 1 });
return Ok(Number::BigInt(res));
} /* else if let Ok(n) = s.parse() { // FIXME: rust-lld: error: undefined symbol: fmod
return Ok(Number::BigInt(n));
} */
err
err
}
}
}

Expand Down
64 changes: 61 additions & 3 deletions src/usr/lisp/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use nom::bytes::complete::is_not;
use nom::bytes::complete::tag;
use nom::bytes::complete::take_while1;
use nom::character::complete::char;
use nom::character::complete::digit1;
use nom::character::complete::multispace0;
use nom::combinator::map;
use nom::combinator::opt;
Expand All @@ -23,6 +22,66 @@ use nom::multi::many0;
use nom::sequence::delimited;
use nom::sequence::preceded;
use nom::sequence::tuple;
use nom::character::complete::one_of;
use nom::multi::many1;
use nom::sequence::terminated;

// https://docs.rs/nom/latest/nom/recipes/index.html#hexadecimal
fn hexadecimal(input: &str) -> IResult<&str, &str> {
preceded(
alt((tag("0x"), tag("0X"))),
recognize(
many1(
terminated(one_of("0123456789abcdefABCDEF"), many0(char('_')))
)
)
)(input)
}

// https://docs.rs/nom/latest/nom/recipes/index.html#decimal
fn decimal(input: &str) -> IResult<&str, &str> {
recognize(
many1(
terminated(one_of("0123456789"), many0(char('_')))
)
)(input)
}

// https://docs.rs/nom/latest/nom/recipes/index.html#floating-point-numbers
fn float(input: &str) -> IResult<&str, &str> {
alt((
recognize( // .42
tuple((
char('.'),
decimal,
opt(tuple((
one_of("eE"),
opt(one_of("+-")),
decimal
)))
))
),
recognize( // 42e42 and 42.42e42
tuple((
decimal,
opt(preceded(
char('.'),
decimal,
)),
one_of("eE"),
opt(one_of("+-")),
decimal
))
),
recognize( // 42. and 42.42
tuple((
decimal,
char('.'),
opt(decimal)
))
)
))(input)
}

fn is_symbol_letter(c: char) -> bool {
let chars = "<>=-+*/%^?.";
Expand All @@ -49,8 +108,7 @@ fn parse_sym(input: &str) -> IResult<&str, Exp> {
fn parse_num(input: &str) -> IResult<&str, Exp> {
let (input, num) = recognize(tuple((
opt(alt((char('+'), char('-')))),
digit1,
opt(tuple((char('.'), digit1)))
alt((float, hexadecimal, decimal))
)))(input)?;
Ok((input, Exp::Num(Number::from(num))))
}
Expand Down
13 changes: 12 additions & 1 deletion www/lisp.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ <h3>Types</h3>

<ul>
<li>Basics: <code>bool</code>, <code>list</code>, <code>symbol</code>, <code>string</code></li>
<li>Numbers: <code>float</code>, <code>int</code>, <code>bigint</code></li>
<li>Number: <code>float</code>, <code>int</code>, <code>bigint</code></li>
</ul>

<h3>Literals</h3>

<ul>
<li>String: <code>&quot;Hello, World!&quot;</code></li>
<li>Number: <code>2.5</code>, <code>-25</code>, <code>255</code>, <code>0xFF</code>, <code>0xDEAD_C0DE</code></li>
</ul>

<h3>Built-in Operators</h3>
Expand Down Expand Up @@ -244,5 +251,9 @@ <h3>0.6.0 (2023-09-23)</h3>
</ul>

<h3>Unreleased</h3>

<ul>
<li>Add hexadecimal number literals</li>
</ul>
</body>
</html>
Loading