add better string and number parsing to sexp. cleaning, more tests

This commit is contained in:
2020-10-09 00:41:09 +02:00
parent 548addc78c
commit 1f2dbaa81d
22 changed files with 612 additions and 424 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ edition = "2018"
publish = false
[dependencies]
sexp = { path = "../lib/spanned_sexp" }
sexp = { path = "crsn-sexp" }
thiserror = "1.0.20"
anyhow = "1.0.32"
dyn-clonable = "0.9.0"
+2
View File
@@ -0,0 +1,2 @@
target
Cargo.lock
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "sexp"
version = "1.1.4"
authors = ["Clark Gaebel <cg.wowus.cg@gmail.com>"]
documentation = "https://cgaebel.github.io/sexp"
homepage = "https://github.com/cgaebel/sexp"
repository = "https://github.com/cgaebel/sexp"
readme = "README.md"
keywords = ["sexp", "parsing", "s-expression", "file-format"]
description = "A small, simple, self-contained, s-expression parser and pretty-printer."
license = "MIT"
[dependencies]
log = "0.4.11"
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2015 Clark Gaebel <cg.wowus.cg@gmail.com>
Copyright (c) 2020 Ondřej Hruška <ondra@ondrovo.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+19
View File
@@ -0,0 +1,19 @@
CRSN Sexp
=========
This is an updated and extended version of the "sexp" crate by Clark Gaebel: [https://github.com/cgaebel/sexp](https://github.com/cgaebel/sexp).
## Changes from "cgaebel/sexp"
- Updated to the 2018 Rust edition (that is, removed `try!()` and such)
- All parsed atoms now track their source location. This enables better error reporting in subsequent parsing and processing.
- Quoted strings now support C-style escapes other than `\"`, such as `\n`, `\t` and `\\`.
- Unrecognized escapes result in the slash being removed and the next character being taken literally. Use `\\` to enter a backslash.
- Added special parsing of "character literals" that use single quotes (`'A'`) and may contain backslash escapes (`'\n'`).
- This does not interfere with "apostrophe tokens" like `'foo`, that becomes an unquoted string atom.
- Added "quoted string", "unsigned" and "character" atom types
- Added parsing for `0x123`, `#ff00ff` and `0b123456`
- Numeric literals may now contain underscores to separate digit groups
- Numbers are preferably parsed as the unsigned atom (u64). The signed atom (i64) is only used for negative numbers.
.
+85
View File
@@ -0,0 +1,85 @@
use std::{cmp, fmt};
/// The representation of an s-expression parse error.
pub struct Error {
/// The error message.
pub message: &'static str,
/// Position in the source string where the error was detected
pub pos: SourcePosition,
}
/// Position in the input string
#[derive(Debug, PartialEq, Clone, Default)]
pub struct SourcePosition {
/// The line number on which the error occurred.
pub line: u32,
/// The column number on which the error occurred.
pub column: u32,
/// The index in the given string which caused the error.
pub index: u32,
}
/// Since errors are the uncommon case, they're boxed. This keeps the size of
/// structs down, which helps performance in the common case.
///
/// For example, an `ERes<()>` becomes 8 bytes, instead of the 24 bytes it would
/// be if `Err` were unboxed.
type Err = Box<Error>;
/// Helps clean up type signatures, but shouldn't be exposed to the outside
/// world.
pub(crate) type ERes<T> = Result<T, Err>;
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{}:{}: {}", self.pos.line, self.pos.column, self.message)
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{}", self)
}
}
impl std::error::Error for Error {}
pub(crate) fn get_line_and_column(s: &str, pos: usize) -> SourcePosition {
let mut line: usize = 1;
let mut col: isize = -1;
for c in s.chars().take(pos + 1) {
if c == '\n' {
line += 1;
col = -1;
} else {
col += 1;
}
}
SourcePosition {
line: line as u32,
column: cmp::max(col, 0) as u32,
index: pos as u32,
}
}
#[cold]
fn err_impl(message: &'static str, s: &str, pos: usize) -> Err {
Box::new(Error {
message,
pos: get_line_and_column(s, pos),
})
}
/// Build an error with span information
pub(crate) fn err<T>(message: &'static str, s: &str, pos: usize) -> ERes<T> {
Err(err_impl(message, s, pos))
}
/// Build a span
pub(crate) fn spos(s: &str, pos: usize) -> SourcePosition {
if pos >= s.len() {
Default::default()
} else {
get_line_and_column(s, pos)
}
}
+444
View File
@@ -0,0 +1,444 @@
//! A lightweight, self-contained s-expression parser and data format.
//! Use `parse` to get an s-expression from its string representation, and the
//! `Display` trait to serialize it, potentially by doing `sexp.to_string()`.
#![deny(unsafe_code)]
#[macro_use]
extern crate log;
use std::borrow::Cow;
use std::fmt;
use std::str::{self, FromStr};
use error::{ERes, err, spos};
pub use error::Error;
pub use error::SourcePosition;
#[cfg(test)]
mod test;
mod error;
/// A single data element in an s-expression. Floats are excluded to ensure
/// atoms may be used as keys in ordered and hashed data structures.
///
/// All strings must be valid utf-8.
#[derive(Debug, PartialEq, Clone, PartialOrd)]
pub enum Atom {
/// Simple string atom
S(String),
/// Quoted string
QS(String),
/// Character literal (with single quotes)
C(char),
/// Signed integer (normally only used for negative values)
I(i64),
/// Unsigned integer
U(u64),
/// Float
F(f64),
}
/// An s-expression is either an atom or a list of s-expressions. This is
/// similar to the data format used by lisp.
#[derive(Debug, Clone)]
pub enum Sexp {
/// Atom
Atom(Atom, SourcePosition),
/// List of expressions
List(Vec<Sexp>, SourcePosition),
}
impl Sexp {
pub fn pos(&self) -> &SourcePosition {
match self {
Sexp::List(_, pos) | Sexp::Atom(_, pos) => pos
}
}
/// Check fi thsi Sexp is an atom
pub fn is_atom(&self) -> bool {
match self {
Sexp::Atom(_, _) => true,
_ => false,
}
}
/// Check fi thsi Sexp is a list
pub fn is_list(&self) -> bool {
match self {
Sexp::List(_, _) => true,
_ => false,
}
}
}
impl PartialEq for Sexp {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Sexp::Atom(a, _), Sexp::Atom(b, _)) => {
a == b
}
(Sexp::List(a, _), Sexp::List(b, _)) => {
a == b
}
_ => false
}
}
}
fn without_underscores(s: &str) -> Cow<str> {
if s.contains('_') {
s.chars()
.filter(|c| *c != '_')
.collect::<String>().into()
} else {
s.into()
}
}
fn atom_of_string(s: String) -> Atom {
if s.starts_with('#') {
match u64::from_str_radix(&without_underscores(&s[1..]), 16) {
Ok(u) => return Atom::U(u),
Err(_) => {}
};
}
if s.starts_with("0x") {
match u64::from_str_radix(&without_underscores(&s[2..]), 16) {
Ok(u) => return Atom::U(u),
Err(_) => {}
};
}
if s.starts_with("0b") {
match u64::from_str_radix(&without_underscores(&s[2..]), 2) {
Ok(u) => return Atom::U(u),
Err(_) => {}
};
}
if !s.starts_with('_') {
let filtered = without_underscores(&s);
match FromStr::from_str(&filtered) {
Ok(u) => return Atom::U(u),
Err(_) => {}
};
match FromStr::from_str(&filtered) {
Ok(i) => return Atom::I(i),
Err(_) => {}
};
match FromStr::from_str(&filtered) {
Ok(f) => return Atom::F(f),
Err(_) => {}
};
}
Atom::S(s)
}
// returns the char it found, and the new pos if you wish to consume that char
fn peek(s: &str, pos: usize) -> ERes<(char, usize)> {
trace!("peek {}", pos);
if pos == s.len() { return err("unexpected eof", s, pos); }
if s.is_char_boundary(pos) {
let ch = s[pos..].chars().next().unwrap();
let next = pos + ch.len_utf8();
Ok((ch, next))
} else {
// strings must be composed of valid utf-8 chars.
unreachable!()
}
}
// returns the char it found, and the new pos if you wish to consume that char
fn peekn(s: &str, nth: usize, pos: usize) -> Option<(char, usize)> {
trace!("peekn {}", pos);
if nth == 0 {
panic!("peekn with nth=0");
}
if s.is_char_boundary(pos) {
let mut iter = s[pos..].chars();
let mut bytelen = 0;
for n in 0..nth {
if let Some(ch) = iter.next() {
bytelen += ch.len_utf8();
if n == (nth - 1) {
return Some((ch, pos + bytelen));
}
} else {
return None;
}
}
unreachable!()
} else {
// strings must be composed of valid utf-8 chars.
unreachable!()
}
}
fn expect(s: &str, pos: &mut usize, c: char) -> ERes<()> {
trace!("expect {}", pos);
let (ch, next) = peek(s, *pos)?;
*pos = next;
if ch == c {
Ok(())
} else {
err("unexpected character", s, *pos)
}
}
fn consume_until_newline(s: &str, pos: &mut usize) -> ERes<()> {
loop {
if *pos == s.len() {
return Ok(());
}
let (ch, next) = peek(s, *pos)?;
*pos = next;
if ch == '\n' {
return Ok(());
}
}
}
// zero or more spaces
fn zspace(s: &str, pos: &mut usize) -> ERes<()> {
trace!("zspace {}", pos);
loop {
if *pos == s.len() {
return Ok(());
}
let (ch, next) = peek(s, *pos)?;
if ch == ';' {
consume_until_newline(s, pos)?
} else if ch.is_whitespace() {
*pos = next;
} else {
return Ok(());
}
}
}
fn parse_quoted_atom(s: &str, quote: char, pos: &mut usize) -> ERes<Atom> {
trace!("parse_quoted_atom {}", pos);
let pos0 = *pos;
let mut cs: String = String::new();
expect(s, pos, quote)?;
loop {
let (ch, next) = peek(s, *pos)?;
if ch == quote {
*pos = next;
break;
} else if ch == '\\' {
let (postslash, nextnext) = peek(s, next)?;
match postslash {
'r' => cs.push('\r'),
'n' => cs.push('\n'),
't' => cs.push('\t'),
other => cs.push(other)
}
*pos = nextnext;
} else {
cs.push(ch);
*pos = next;
}
}
if quote == '\'' {
// This is a character literal
if cs.chars().count() == 1 {
return Ok(Atom::C(cs.chars().next().unwrap()));
} else {
return err("Too long character literal!", s, pos0);
}
}
// Do not try i64 conversion, since this atom was explicitly quoted.
Ok(Atom::QS(cs))
}
fn parse_unquoted_atom(s: &str, pos: &mut usize) -> ERes<Atom> {
trace!("parse_unquoted_atom {}", pos);
let mut cs: String = String::new();
loop {
if *pos == s.len() { break; }
let (c, next) = peek(s, *pos)?;
if c == ';' {
consume_until_newline(s, pos)?;
break;
}
if c.is_whitespace() || c == '(' || c == ')' {
break;
}
cs.push(c);
*pos = next;
}
Ok(atom_of_string(cs))
}
fn parse_atom(s: &str, pos: &mut usize) -> ERes<Atom> {
trace!("parse_atom {}", pos);
let (ch, _) = peek(s, *pos)?;
if ch == '"' {
return parse_quoted_atom(s, ch, pos);
} else if ch == '\'' {
if let Some(('\\', _)) = peekn(s, 2, *pos) {
if let Some(('\'', _)) = peekn(s, 4, *pos) {
// Character literal with an escape sequence
return parse_quoted_atom(s, '\'', pos);
}
} else if let Some(('\'', _)) = peekn(s, 3, *pos) {
// Simple character literal
return parse_quoted_atom(s, '\'', pos);
}
}
parse_unquoted_atom(s, pos)
}
fn parse_list(s: &str, pos: &mut usize) -> ERes<Vec<Sexp>> {
trace!("parse_list {}", pos);
zspace(s, pos)?;
expect(s, pos, '(')?;
let mut sexps: Vec<Sexp> = Vec::new();
loop {
zspace(s, pos)?;
let (c, next) = peek(s, *pos)?;
if c == ')' {
*pos = next;
break;
}
sexps.push(parse_sexp(s, pos)?);
}
zspace(s, pos)?;
Ok(sexps)
}
fn parse_sexp(s: &str, pos: &mut usize) -> ERes<Sexp> {
trace!("parse_sexp {}", pos);
zspace(s, pos)?;
let (c, _) = peek(s, *pos)?;
let r = if c == '(' {
Ok(Sexp::List(parse_list(s, pos)?, spos(s, *pos)))
} else {
Ok(Sexp::Atom(parse_atom(s, pos)?, spos(s, *pos)))
};
zspace(s, pos)?;
r
}
/// Constructs an atomic s-expression from a string.
pub fn atom_s(s: &str) -> Sexp {
Sexp::Atom(Atom::S(s.to_owned()), Default::default())
}
/// Constructs an atomic s-expression from a string.
pub fn atom_qs(s: &str) -> Sexp {
Sexp::Atom(Atom::QS(s.to_owned()), Default::default())
}
/// Constructs an atomic s-expression from an int.
pub fn atom_i(i: i64) -> Sexp {
Sexp::Atom(Atom::I(i), Default::default())
}
/// Constructs an atomic s-expression from an unsigned int.
pub fn atom_u(u: u64) -> Sexp {
Sexp::Atom(Atom::U(u), Default::default())
}
/// Constructs an atomic s-expression from a char
pub fn atom_c(c: char) -> Sexp {
Sexp::Atom(Atom::C(c), Default::default())
}
/// Constructs an atomic s-expression from a float.
pub fn atom_f(f: f64) -> Sexp {
Sexp::Atom(Atom::F(f), Default::default())
}
/// Constructs a list s-expression given a slice of s-expressions.
pub fn list(xs: &[Sexp]) -> Sexp {
Sexp::List(xs.to_owned(), Default::default())
}
/// Reads an s-expression out of a `&str`.
#[inline(never)]
pub fn parse(s: &str) -> Result<Sexp, Box<Error>> {
let mut pos = 0;
let ret = parse_sexp(s, &mut pos)?;
if pos == s.len() {
Ok(ret)
} else {
err("unrecognized post-s-expression data", s, pos)
}
}
fn quote(s: &str) -> String {
s.chars().fold(String::new(), |mut s, ch| {
match ch {
'\'' | '\\' | '"' => {
s.push('\\');
s.push(ch);
}
'\n' => {
s.push_str("\\n");
}
'\r' => {
s.push_str("\\n");
}
'\t' => {
s.push_str("\\t");
}
other => {
s.push(other);
}
}
s
})
}
impl fmt::Display for Atom {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
Atom::QS(ref s) => write!(f, "\"{}\"", quote(s)),
Atom::S(ref s) => write!(f, "{}", s),
Atom::C(c) => write!(f, "'{}'", quote(&c.to_string())),
Atom::I(i) => write!(f, "{}", i),
Atom::U(u) => write!(f, "{}", u),
Atom::F(d) => write!(f, "{}", d),
}
}
}
impl fmt::Display for Sexp {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
Sexp::Atom(ref a, _) => write!(f, "{}", a),
Sexp::List(ref xs, _) => {
write!(f, "(")?;
for (i, x) in xs.iter().enumerate() {
let s = if i == 0 { "" } else { " " };
write!(f, "{}{}", s, x)?;
}
write!(f, ")")
}
}
}
}
+103
View File
@@ -0,0 +1,103 @@
use super::*;
use super::error::get_line_and_column;
#[test]
fn test_hello_world() {
let parsed = parse("(hello #ffcc00 'c' ' ' '\\\\' '\\n' 'not_a_char 0xf_f 0b01_01 18_446_744_073_709_551_615 9223372036854775807 9223372036854775808 -42\n\t -4.0 \"world\") ; comment").unwrap();
assert_eq!(
parsed,
list(&[
atom_s("hello"),
atom_u(0xffcc00),
atom_c('c'),
atom_c(' '),
atom_c('\\'),
atom_c('\n'),
atom_s("'not_a_char"),
atom_u(255),
atom_u(0b0101),
atom_u(18_446_744_073_709_551_615),
atom_u(9223372036854775807),
atom_u(9223372036854775808),
atom_i(-42),
atom_f(-4.0),
atom_qs("world")
]));
}
#[test]
fn test_escaping() {
assert_eq!(
parse(r#"("\"\\q\t\n\r " "1234" 1234)"#).unwrap(),
list(&[atom_qs("\"\\q\t\n\r "), atom_qs("1234"), atom_u(1234)]));
}
#[test]
fn test_pp() {
let s = "(hello world (what is (up) (4 6.4 you \"123\\\\ \\\"\")))";
let sexp = parse(s).unwrap();
assert_eq!(s, sexp.to_string());
assert_eq!(s, format!("{}", sexp));
}
#[test]
fn test_tight_parens() {
let s = "(hello(world))";
let sexp = parse(s).unwrap();
assert_eq!(sexp, Sexp::List(vec![Sexp::Atom(Atom::S("hello".into()), Default::default()),
Sexp::List(vec![Sexp::Atom(Atom::S("world".into()), Default::default())], Default::default())], Default::default()));
let s = "(this (has)tight(parens))";
let s2 = "( this ( has ) tight ( parens ) )";
assert_eq!(parse(s).unwrap(), parse(s2).unwrap());
}
#[test]
fn test_space_in_atom() {
let sexp = list(&[atom_qs("hello world")]);
let sexp_as_string = sexp.to_string();
assert_eq!("(\"hello world\")", sexp_as_string);
assert_eq!(sexp, parse(&sexp_as_string).unwrap());
}
#[test]
fn show_an_error() {
assert_eq!(format!("{:?}", parse("(aaaa").unwrap_err()), "1:4: unexpected eof");
}
#[test]
fn line_and_col_test() {
let s = "0123456789\n0123456789\n\n6";
assert_eq!(get_line_and_column(s, 4), SourcePosition { line: 1, column: 4, index: 4 });
assert_eq!(get_line_and_column(s, 10), SourcePosition { line: 2, column: 0, index: 10 });
assert_eq!(get_line_and_column(s, 11), SourcePosition { line: 2, column: 0, index: 11 });
assert_eq!(get_line_and_column(s, 15), SourcePosition { line: 2, column: 4, index: 15 });
assert_eq!(get_line_and_column(s, 21), SourcePosition { line: 3, column: 0, index: 21 });
assert_eq!(get_line_and_column(s, 22), SourcePosition { line: 4, column: 0, index: 22 });
assert_eq!(get_line_and_column(s, 23), SourcePosition { line: 4, column: 0, index: 23 });
assert_eq!(get_line_and_column(s, 500), SourcePosition { line: 4, column: 0, index: 500 });
}
#[test]
fn sexp_size() {
// I just want to see when this changes, in the diff.
use std::mem;
assert_eq!(mem::size_of::<Sexp>(), mem::size_of::<isize>() * 6);
}
#[test]
fn test_peek() {
assert_eq!(('a', 1), peek("ahoj", 0).unwrap());
assert_eq!(('j', 4), peek("ahoj", 3).unwrap());
assert!(peek("ahoj", 4).is_err());
}
#[test]
fn test_peekn() {
assert_eq!(Some(('a', 1)), peekn("ahoj", 1, 0));
assert_eq!(Some(('h', 2)), peekn("ahoj", 2, 0));
assert_eq!(Some(('j', 4)), peekn("ahoj", 4, 0));
assert_eq!(Some(('o', 3)), peekn("ahoj", 1, 2));
assert_eq!(None, peekn("ahoj", 3, 2));
}
+30 -42
View File
@@ -78,36 +78,26 @@ pub fn parse_data_disp(tok: Sexp, pcx: &ParserContext) -> Result<DataDisp, CrsnE
Sexp::Atom(Atom::I(val), _pos) => {
Ok(DataDisp::Immediate(unsafe { std::mem::transmute(val) }))
}
Sexp::Atom(Atom::F(val), _pos) => {
Ok(DataDisp::Immediate(unsafe { std::mem::transmute(val) }))
}
Sexp::Atom(Atom::U(val), _pos) => {
Ok(DataDisp::Immediate(val))
}
Sexp::Atom(Atom::C(val), _pos) => {
Ok(DataDisp::Immediate(val as u64))
}
Sexp::Atom(Atom::QS(_s), pos) => {
Err(CrsnError::Parse("Quoted string not expected here".into(), pos))
}
Sexp::List(_list, pos) => {
Err(CrsnError::Parse("List not expected here".into(), pos))
}
Sexp::Atom(Atom::S(s), pos) => {
if s == "_" {
return Ok(DataDisp::Discard);
}
if s.starts_with('\'') && s.ends_with('\'') {
if s.chars().count() == 3 {
let ch = s.chars().nth(1).unwrap();
if ch == '\'' {
return Err(CrsnError::Parse("Use '\\'' for apos".into(), pos));
}
return Ok(DataDisp::Immediate(ch as u64));
} else if s.chars().count() == 4 && s.chars().nth(1).unwrap() == '\\' {
let ch = s.chars().nth(2).unwrap();
let ch = match ch {
'\\' => '\\',
'n' => '\n',
'r' => '\r',
't' => '\t',
'\'' => '\'',
_ => {
return Err(CrsnError::Parse(format!("Unknown escape sequence: {}", s).into(), pos));
}
};
return Ok(DataDisp::Immediate(ch as u64));
} else {
return Err(CrsnError::Parse(format!("Invalid character literal synax {}", s).into(), pos));
}
}
if let Some(reference) = s.strip_prefix('@') {
/* extension constants (pre-defined handles) */
for p in pcx.parsers {
@@ -129,8 +119,6 @@ pub fn parse_data_disp(tok: Sexp, pcx: &ParserContext) -> Result<DataDisp, CrsnE
} else {
Ok(DataDisp::RegObject(reg))
}
} else if s.starts_with(|c: char| c.is_ascii_digit() || c == '-') {
Ok(DataDisp::Immediate(unsafe { std::mem::transmute(parse_i64(&s, &pos)?) }))
} else {
/* extension constants */
for p in pcx.parsers {
@@ -161,9 +149,6 @@ pub fn parse_data_disp(tok: Sexp, pcx: &ParserContext) -> Result<DataDisp, CrsnE
}
}
}
other => {
Err(CrsnError::Parse(format!("bad data disp: {:?}", other).into(), other.pos().clone()))
}
}
}
@@ -173,16 +158,28 @@ pub fn parse_value(tok: Sexp, pcx: &ParserContext) -> Result<Value, CrsnError> {
Sexp::Atom(Atom::I(val), _pos) => {
Ok(unsafe { std::mem::transmute(val) })
}
Sexp::Atom(Atom::U(val), _pos) => {
Ok(val)
}
Sexp::Atom(Atom::C(val), _pos) => {
Ok(val as u64)
}
Sexp::Atom(Atom::QS(_), pos) => {
Err(CrsnError::Parse("quoted string not expected here".into(), pos))
}
Sexp::Atom(Atom::F(val), _) => {
Ok(unsafe { std::mem::transmute(val) })
}
Sexp::Atom(Atom::S(s), pos) => {
let pstate = pcx.state.borrow();
if let Some(val) = pstate.constants.get(&s) {
return Ok(*val);
}
Ok(unsafe { std::mem::transmute(parse_i64(&s, &pos)?) })
Err(CrsnError::Parse(format!("unknown constant: {}", s).into(), pos))
}
other => {
Err(CrsnError::Parse(format!("bad value format: {:?}", other).into(), other.pos().clone()))
Sexp::List(_, pos) => {
Err(CrsnError::Parse("expected a value".into(), pos))
}
}
}
@@ -204,15 +201,6 @@ pub fn parse_u64(literal: &str, pos: &SourcePosition) -> Result<u64, CrsnError>
}
}
pub fn parse_i64(literal: &str, pos: &SourcePosition) -> Result<i64, CrsnError> {
// trace!("parse i64 from {}", literal);
if let Some(_value) = literal.strip_prefix("-") {
Ok(-1 * i64::try_from(parse_u64(literal, pos)?).err_pos(pos)?)
} else {
Ok(i64::try_from(parse_u64(literal, pos)?).err_pos(pos)?)
}
}
pub fn parse_rd(tok: Sexp, pcx: &ParserContext) -> Result<Rd, CrsnError> {
let pos = tok.pos().clone();
Ok(Rd::new(RdData::try_from(parse_data_disp(tok, pcx)?).err_pos(&pos)?))
+17 -7
View File
@@ -36,10 +36,20 @@ pub fn expect_string_atom(expr: Sexp) -> Result<(String, SourcePosition), CrsnEr
}
}
// pub fn expect_int_atom(expr: Option<Sexp>) -> Result<i64, Error> {
// match expect_atom(expr) {
// Ok(Atom::I(v)) => Ok(v),
// Ok(atom) => Err(Error::ParseIn("Expected int atom".into(), Sexp::Atom(atom))),
// Err(e) => Err(e),
// }
// }
pub fn expect_quoted_string_atom(expr: Sexp) -> Result<(String, SourcePosition), CrsnError> {
match expect_atom(expr) {
Ok((Atom::QS(s), pos)) => Ok((s, pos)),
Ok((_, pos)) => Err(CrsnError::Parse("Expected quoted string atom".into(), pos)),
Err(e) => Err(e),
}
}
/// Quoted or simple string
pub fn expect_any_string_atom(expr: Sexp) -> Result<(String, SourcePosition), CrsnError> {
match expect_atom(expr) {
Ok((Atom::S(s), pos)) => Ok((s, pos)),
Ok((Atom::QS(s), pos)) => Ok((s, pos)),
Ok((_, pos)) => Err(CrsnError::Parse("Expected quoted string atom".into(), pos)),
Err(e) => Err(e),
}
}
+1 -1
View File
@@ -53,7 +53,7 @@ pub enum BuiltinOp {
Fault(Option<DebugMsg>),
/// Deallocate an extension object.
/// The object is released and the handle becomes invalid.
Drop(RdObj),
Delete(RdObj),
/// Copy value
Move { dst: Wr, src: Rd },
/// Store runtime status to a register
+1 -1
View File
@@ -120,7 +120,7 @@ impl OpTrait for BuiltinOp {
BuiltinOp::Sleep { micros } => {
std::thread::sleep(Duration::from_micros(state.read(*micros)?))
}
BuiltinOp::Drop(obj) => {
BuiltinOp::Delete(obj) => {
let x = state.read(Rd::new(RdData::Register(obj.reg())))?;
trace!("Drop object: {:#x}", x);
+14 -8
View File
@@ -6,7 +6,7 @@ use crate::asm::error::CrsnError;
use crate::asm::instr::op::OpKind;
use crate::asm::parse::arg_parser::TokenParser;
use crate::asm::parse::parse_data::{parse_constant_name, parse_label, parse_label_str, parse_rd, parse_reg_alias, parse_value};
use crate::asm::parse::sexp_expect::expect_string_atom;
use crate::asm::parse::sexp_expect::{expect_any_string_atom};
use crate::asm::patches::ErrWithPos;
use crate::builtin::defs::{Barrier, BuiltinOp};
use crate::module::ParseRes;
@@ -135,7 +135,7 @@ pub(crate) fn parse_op<'a>(op_pos: &SourcePosition, keyword: &str, mut args: Tok
kind: Barrier::Standalone,
msg: match args.next() {
None => None,
Some(s) => Some(expect_string_atom(s)?.0.into()),
Some(s) => Some(expect_any_string_atom(s)?.0.into()),
},
}
}
@@ -157,7 +157,7 @@ pub(crate) fn parse_op<'a>(op_pos: &SourcePosition, keyword: &str, mut args: Tok
"fault" => {
BuiltinOp::Fault(match args.next() {
None => None,
Some(s) => Some(expect_string_atom(s)?.0.into()),
Some(s) => Some(expect_any_string_atom(s)?.0.into()),
})
}
@@ -180,8 +180,8 @@ pub(crate) fn parse_op<'a>(op_pos: &SourcePosition, keyword: &str, mut args: Tok
}
}
"drop" => {
BuiltinOp::Drop(args.next_rdobj()?)
"del" => {
BuiltinOp::Delete(args.next_rdobj()?)
}
"far" => {
@@ -277,7 +277,7 @@ pub(crate) fn to_sexp(op: &BuiltinOp) -> Sexp {
sexp::list(&[A("fault")])
}
}
BuiltinOp::Drop(obj) => sexp::list(&[A("drop"), A(obj)]),
BuiltinOp::Delete(obj) => sexp::list(&[A("del"), A(obj)]),
BuiltinOp::Move { dst, src } => sexp::list(&[A("ld"), A(dst), A(src)]),
BuiltinOp::StoreStatus { dst } => sexp::list(&[A("sst"), A(dst)]),
BuiltinOp::LoadStatus { src } => sexp::list(&[A("sld"), A(src)])
@@ -303,6 +303,12 @@ mod test {
("(nop)", "(nop)"),
("(halt)", "(halt)"),
("(sleep 1000)", "(sleep 1000)"),
("(sleep 1_0_0_0)", "(sleep 1000)"),
("(sleep ' ')", "(sleep 32)"),
("(sleep '\\n')", "(sleep 10)"),
("(sleep 0b111)", "(sleep 7)"),
("(sleep 0xab_cd)", "(sleep 43981)"),
("(sleep #ab_cd)", "(sleep 43981)"),
("(:x)", "(:x)"),
("(j :x)", "(j :x)"),
("(:#7)", "(:#7)"),
@@ -345,8 +351,8 @@ mod test {
("(sst r0)", "(sst r0)"),
("(sld r0)", "(sld r0)"),
("(far :label)", "(far :label)"),
("(drop @r5)", "(drop @r5)"),
("(sym cat r0)(drop @cat)", "(drop @r0)"),
("(del @r5)", "(del @r5)"),
("(sym cat r0)(del @cat)", "(del @r0)"),
];
let parser = BuiltinOps::new();
+1
View File
@@ -1,6 +1,7 @@
#[macro_use]
extern crate log;
// re-export our customized version of sexp
pub use sexp;
pub mod asm;
+10 -1
View File
@@ -11,6 +11,11 @@ mod option_ext;
pub fn A(s: impl Display) -> Sexp {
let s = s.to_string();
let x: Result<u64, _> = FromStr::from_str(&s);
if let Ok(x) = x {
return Sexp::Atom(Atom::U(x), Default::default());
}
let x: Result<i64, _> = FromStr::from_str(&s);
if let Ok(x) = x {
return Sexp::Atom(Atom::I(x), Default::default());
@@ -21,5 +26,9 @@ pub fn A(s: impl Display) -> Sexp {
return Sexp::Atom(Atom::F(y), Default::default());
}
Sexp::Atom(Atom::S(s), Default::default())
if s.contains(|c: char| " \t\"\\\n\t\r".contains(c)) {
Sexp::Atom(Atom::QS(s), Default::default())
} else {
Sexp::Atom(Atom::S(s), Default::default())
}
}