radices, remove |\ and |\\

This commit is contained in:
TriMill 2022-11-07 17:10:23 -05:00
parent 076fe8f60c
commit 48a0fc81de
10 changed files with 189 additions and 63 deletions

View file

@ -301,22 +301,18 @@ The most basic pipeline operator is `|>`. `|>` will call the function on the rig
0..10 |? fn(x) (x % 3 != 0) |> list |> println;
```
`|//` and `|\\` fold the iterator on the left over the function on the right. This is commonly used to get the sum, product, minimum, or maximum of an iterator. `|//` starts folding from the beginning of the iterator, `|\\` starts from the end. In many use cases (specifically, when the function used is associative), either can be used. `|//` should be preferred in these cases, as it is more efficient (`|\\` has to collect the entire iterator into a list first).
`|//` folds the iterator on the left over the function on the right. This is commonly used to get the sum, product, minimum, or maximum of an iterator. `|//` starts folding from the beginning of the iterator and goes to the end.
```
# == ((((0+1)+2)+3)+4)
0..5 |// fn(x,y) (x + y) |> println;
# == (0+(1+(2+(3+4))))
0..5 |\\ fn(x,y) (x + y) |> println;
```
In some cases (particulary when the function is not commutative) it may be desirable to use an initial value instead of taking the first two values from the list. This can be accomplished with the operators `|/` and `|\`. These operators are ternary, so the right-hand side must include the initial value and the function, separated by a comma. For `|/` this initial value will be used as the first argument during the first iteration, for `|\` it will be used as the second argument during the first iteration.
In some cases (particulary when the function is not commutative) it may be desirable to use an initial value instead of taking the first two values from the list. This can be accomplished with the operator `|/`. This operators are ternary, so the right-hand side must include the initial value and the function, separated by a comma.
```
# == (((((0+2)+3)+4)+5)+6)
2..7 |/ 0, fn(x,y) (x + y) |> println;
# == (2+(3+(4+(5+(6+0)))))
2..7 |\ 0, fn(x,y) (x + y) |> println;
```
## Structs (WIP)

View file

@ -34,8 +34,10 @@ fn fn_input(_: Vec<Value>) -> Result<Value, RuntimeError> {
stdin.read_line(&mut buffer).map_err(|e| e.to_string())?;
if buffer.ends_with('\n') {
buffer.pop();
Ok(Value::from(buffer))
} else {
Ok(Value::Nil)
}
Ok(Value::from(buffer))
}
struct FileBox {

View file

@ -14,6 +14,7 @@ pub fn load(env: &mut Environment) {
declare_fn!(env, nth, 2);
declare_fn!(env, last, 1);
declare_fn!(env, void, 1);
declare_fn!(env, rev, 1);
}
fn fn_iter(mut args: Vec<Value>) -> Result<Value, RuntimeError> {
@ -125,3 +126,12 @@ fn fn_void(args: Vec<Value>) -> Result<Value, RuntimeError> {
for _ in args[0].iter()? {}
Ok(Value::Nil)
}
fn fn_rev(args: Vec<Value>) -> Result<Value, RuntimeError> {
let mut lst = vec![];
for item in args[0].iter()? {
lst.push(item?);
}
lst.reverse();
Ok(Value::from(lst))
}

View file

@ -17,6 +17,11 @@ pub fn load(env: &mut Environment) {
declare_fn!(env, repr, 1);
declare_fn!(env, ord, 1);
declare_fn!(env, chr, 1);
declare_fn!(env, to_radix, 2);
declare_fn!(env, bin, 1);
declare_fn!(env, hex, 1);
declare_fn!(env, sex, 1);
declare_fn!(env, from_radix, 2);
declare_fn!(env, has, 2);
declare_fn!(env, len, 1);
declare_fn!(env, args, 0);
@ -93,6 +98,71 @@ fn fn_chr(args: Vec<Value>) -> Result<Value, RuntimeError> {
Err("Argument to chr must be an integer".into())
}
}
fn to_radix(r: i64, mut n: i64) -> String {
let mut result = String::new();
let mut idx = 0;
if n == 0 {
result.push('0');
return result
} else if n < 0 {
n = -n;
idx = 1;
result.push('-');
}
while n != 0 {
let c = std::char::from_digit((n % r) as u32, r as u32).unwrap();
result.insert(idx, c);
n /= r;
}
result
}
fn fn_to_radix(args: Vec<Value>) -> Result<Value, RuntimeError> {
match (&args[0], &args[1]) {
(Value::Int(r), Value::Int(n))
if *r >= 2 && *r <= 36 => Ok(Value::from(to_radix(*r, *n))),
(Value::Int(_), Value::Int(_)) => Err(format!("Radix must be an integer in the range [2, 36]").into()),
(Value::Int(_), v) => Err(format!("Second argument to to_radix must be an integer, got {:?}", v).into()),
(v, _) => Err(format!("First argument to to_radix must be an integer, got {:?}", v).into()),
}
}
fn fn_bin(args: Vec<Value>) -> Result<Value, RuntimeError> {
match &args[0] {
Value::Int(n) => Ok(Value::from(to_radix(2, *n))),
v => Err(format!("Argument to bin must be an integer, got {:?}", v).into()),
}
}
fn fn_hex(args: Vec<Value>) -> Result<Value, RuntimeError> {
match &args[0] {
Value::Int(n) => Ok(Value::from(to_radix(16, *n))),
v => Err(format!("Argument to bin must be an integer, got {:?}", v).into()),
}
}
fn fn_sex(args: Vec<Value>) -> Result<Value, RuntimeError> {
match &args[0] {
Value::Int(n) => Ok(Value::from(to_radix(6, *n))),
v => Err(format!("Argument to bin must be an integer, got {:?}", v).into()),
}
}
fn fn_from_radix(args: Vec<Value>) -> Result<Value, RuntimeError> {
match (&args[0], &args[1]) {
(Value::Int(r), Value::String(s))
if *r >= 2 && *r <= 36
=> match i64::from_str_radix(s, *r as u32) {
Ok(n) => Ok(Value::from(n)),
Err(_) => Err(format!("Failed to convert string to integer in specified radix").into()),
},
(Value::Int(_), Value::String(_)) => Err(format!("Radix must be an integer in the range [2, 36]").into()),
(Value::Int(_), v) => Err(format!("Second argument to from_radix must be a string, got {:?}", v).into()),
(v, _) => Err(format!("First argument to from_radix must be an integer, got {:?}", v).into()),
}
}
fn fn_len(args: Vec<Value>) -> Result<Value, RuntimeError> {
Ok(Value::Int(args[0].len().map_err(RuntimeError::new_no_pos)? as i64))
}

View file

@ -115,7 +115,7 @@ pub fn eval_stmt(stmt: &Stmt, env: EnvRef) -> Result<(), Unwind> {
let name = name.ty.clone().as_ident().unwrap();
let func = Func::Func {
name: Some(name.clone()),
args: args.iter().map(|a| a.ty.clone().as_ident().unwrap()).collect(),
args: args.to_vec(),
env: env.clone(),
func: Box::new(body.as_ref().clone())
};
@ -219,7 +219,7 @@ pub fn eval_expr(expr: &Expr, env: EnvRef) -> Result<Value, RuntimeError> {
Expr::Fn { args, body } => {
let func = Func::Func {
name: None,
args: args.iter().map(|a| a.ty.clone().as_ident().unwrap()).collect(),
args: args.to_vec(),
env,
func: Box::new(body.as_ref().clone())
};
@ -356,7 +356,7 @@ pub fn eval_assignment(lhs: &Expr, rhs: &Expr, op: &Token, env: EnvRef) -> Resul
}
pub fn eval_standard_binary(l: Value, r: Value, opty: &TokenType, pos: &Position) -> Result<Value, RuntimeError> {
let mk_err = || format!("Cannot compare {:?} with {:?}", l, r);
let mk_err = || format!("Cannot compare {} with {}", l, r);
match opty {
TokenType::Plus => &l + &r,
TokenType::Minus => &l - &r,
@ -460,21 +460,6 @@ fn eval_pipeline(l: Value, r: &Func, op: &Token) -> Result<Value, RuntimeError>
}
Ok(result)
},
TokenType::PipeDoubleBackslash => {
let mut result = Value::Nil;
let mut first_iter = true;
let lst = l.iter().map_err(|e| RuntimeError::new(e, op.pos.clone()))?.collect::<Vec<Result<Value, RuntimeError>>>();
for v in lst.into_iter().rev() {
let v = v.map_err(exit_pipe(&op.pos))?;
if first_iter {
result = v;
first_iter = false;
} else {
result = r.call(vec![v, result]).map_err(exit_pipe(&op.pos))?;
}
}
Ok(result)
},
_ => todo!()
}
@ -495,19 +480,6 @@ pub fn eval_ternary(arg1: &Expr, arg2: &Expr, arg3: &Expr, op: &Token, env: EnvR
}
Ok(result)
},
TokenType::PipeBackslash => {
let iter = eval_expr(arg1, env.clone())?;
let mut result = eval_expr(arg2, env.clone())?;
let func = eval_expr(arg3, env)?;
let func = func.as_func()
.map_err(|e| RuntimeError::new(e, op.pos.clone()))?;
let lst = iter.iter().map_err(|e| RuntimeError::new(e, op.pos.clone()))?.collect::<Vec<Result<Value, RuntimeError>>>();
for v in lst.into_iter().rev() {
let v = v.map_err(exit_pipe(&op.pos))?;
result = func.call(vec![v, result]).map_err(exit_pipe(&op.pos))?;
}
Ok(result)
},
_ => unreachable!()
}
}

View file

@ -13,7 +13,7 @@ pub enum Stmt {
Break { pos: Position },
Continue { pos: Position },
Return { pos: Position, expr: Expr },
Fn { name: Token, args: Vec<Token>, body: Box<Stmt> },
Fn { name: Token, args: Vec<Rc<str>>, body: Box<Stmt> },
StructDef { name: Token, ty: Type },
}
@ -47,7 +47,7 @@ pub enum Expr {
Literal { value: Token },
List { items: Vec<Expr> },
Map { items: Vec<(Expr,Expr)> },
Fn { args: Vec<Token>, body: Box<Stmt> },
Fn { args: Vec<Rc<str>>, body: Box<Stmt> },
FuncCall { func: Box<Expr>, args: Vec<Expr>, pos: Position },
Index { lhs: Box<Expr>, index: Box<Expr>, pos: Position },
StructInit { ty: Box<Expr>, args: Vec<Expr>, pos: Position },

View file

@ -226,10 +226,6 @@ impl Lexer {
Some('/') => self.add_token(TokenType::PipeDoubleSlash, "|//"),
_ => self.add_token(TokenType::PipeSlash, "|/")
},
Some('\\') => match self.expect(&['\\']) {
Some('\\') => self.add_token(TokenType::PipeDoubleBackslash, "|\\\\"),
_ => self.add_token(TokenType::PipeBackslash, "|\\")
},
_ => self.add_token(TokenType::Pipe, "|"),
},
'~' => self.add_token(TokenType::Tilde, "~"),

View file

@ -79,9 +79,9 @@ impl Parser {
if self.peek().ty == TokenType::Comma {
self.next();
} else if self.peek().ty == terminator {
break;
break
} else {
return Err(self.mk_error(format!("Expected Comma or {:?} after list", terminator)))
return Err(self.mk_error(format!("Expected comma or {} after list", terminator)))
}
}
self.err_on_eof()?;
@ -102,7 +102,11 @@ impl Parser {
TokenType::Semicolon => {
// skip lonely semicolon
self.next();
self.statement()
if self.at_end() {
Ok(Stmt::Block { stmts: vec![] })
} else {
self.statement()
}
}
TokenType::Let => {
// let statement
@ -249,6 +253,7 @@ impl Parser {
return Err(self.mk_error("Expected left parenthesis to start arguments list"))
}
let args = self.commalist(TokenType::RParen, Self::ident)?;
let args = args.iter().map(|a| a.ty.clone().as_ident().unwrap()).collect();
self.err_on_eof()?;
if self.peek().ty == TokenType::LParen {
self.next();
@ -333,7 +338,7 @@ impl Parser {
while !self.at_end() && self.peek().ty.get_op_type() == Some(OpType::Pipeline) {
let op = self.next();
let right = self.logical_or()?;
if op.ty == TokenType::PipeSlash || op.ty == TokenType::PipeBackslash {
if op.ty == TokenType::PipeSlash {
self.err_on_eof()?;
if !self.expect(TokenType::Comma).0 {
return Err(self.mk_error("Expected comma after first argument"))
@ -508,7 +513,7 @@ impl Parser {
self.err_on_eof()?;
let next = self.next();
if matches!(next.ty,
TokenType::True | TokenType::False | TokenType::Nil
TokenType::True | TokenType::False | TokenType::Nil
| TokenType::Int(_) | TokenType::Float(_) | TokenType::ImFloat(_)
| TokenType::String(_) | TokenType::Char(_)
) {
@ -528,16 +533,17 @@ impl Parser {
} else if next.ty == TokenType::Backslash {
self.err_on_eof()?;
let op = self.next();
if !op.ty.is_infix_op() {
return Err(self.mk_error("Expected infix operator after backslash"))
if op.ty.is_infix_op() {
let func = Func::BuiltinClosure {
arg_count: 2,
func: Rc::new(move |args| {
eval_standard_binary(args[0].clone(), args[1].clone(), &op.ty, &op.pos)
})
};
Ok(Expr::BoxedInfix { func })
} else {
Err(self.mk_error("Expected infix operator after backslash"))
}
let func = Func::BuiltinClosure {
arg_count: 2,
func: Rc::new(move |args| {
eval_standard_binary(args[0].clone(), args[1].clone(), &op.ty, &op.pos)
})
};
Ok(Expr::BoxedInfix { func })
} else if next.ty == TokenType::LBrack {
// list literal
let items = self.commalist(TokenType::RBrack, Self::assignment)?;
@ -553,6 +559,7 @@ impl Parser {
return Err(self.mk_error("Expected left parenthesis to start arguments list"))
}
let args = self.commalist(TokenType::RParen, Self::ident)?;
let args = args.iter().map(|a| a.ty.clone().as_ident().unwrap()).collect();
self.err_on_eof()?;
if self.peek().ty == TokenType::LParen {
self.next();
@ -572,7 +579,7 @@ impl Parser {
Err(self.mk_error("Expected '(' or '{' after function arguments list to begin body"))
}
} else {
Err(self.mk_error(format!("Unexpected token: {:?}", next.ty)))
Err(self.mk_error(format!("Unexpected token: {}", next.ty)))
}
}
}

View file

@ -31,19 +31,92 @@ pub enum TokenType {
DoubleEqual, BangEqual, Greater, GreaterEqual, Less, LessEqual, Spaceship,
PipeColon, PipePoint, PipeQuestion, PipeAmper,
PipeSlash, PipeBackslash, PipeDoubleSlash, PipeDoubleBackslash,
PipeSlash, PipeDoubleSlash,
Backslash,
Comma, Semicolon, Colon,
LParen, RParen, LBrack, RBrack, LBrace, RBrace,
True, False, Nil,
True, False, Nil,
If, Elif, Else, For, While,
Fn, Let, Struct,
Break, Continue, Return
}
impl fmt::Display for TokenType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TokenType::Int(n) => write!(f, "integer literal {}", n),
TokenType::Float(x) => write!(f, "float literal {}", x),
TokenType::ImFloat(x) => write!(f, "imaginary float literal {}i", x),
TokenType::String(s) => write!(f, "string literal {}", crate::value::Value::from(s.as_ref())),
TokenType::Char(c) => write!(f, "character literal {}", crate::value::Value::from(*c)),
TokenType::Ident(n) => write!(f, "identifier '{}'", n),
TokenType::Plus => f.write_str("operator +"),
TokenType::Minus => f.write_str("operator -"),
TokenType::Star => f.write_str("operator *"),
TokenType::Slash => f.write_str("operator /"),
TokenType::Percent => f.write_str("operator %"),
TokenType::DoubleSlash => f.write_str("operator //"),
TokenType::Caret => f.write_str("operator ^"),
TokenType::Bang => f.write_str("operator !"),
TokenType::DoubleAmper => f.write_str("operator &&"),
TokenType::DoublePipe => f.write_str("operator ||"),
TokenType::Tilde => f.write_str("operator ~"),
TokenType::Amper => f.write_str("operator &"),
TokenType::Pipe => f.write_str("operator |"),
TokenType::Dot => f.write_str("operator ."),
TokenType::DoubleDot => f.write_str("operator .."),
TokenType::Equal => f.write_str("operator ="),
TokenType::PlusEqual => f.write_str("operator +="),
TokenType::MinusEqual => f.write_str("operator -="),
TokenType::StarEqual => f.write_str("operator *="),
TokenType::SlashEqual => f.write_str("operator /="),
TokenType::PercentEqual => f.write_str("operator %="),
TokenType::DoubleSlashEqual => f.write_str("operator //="),
TokenType::CaretEqual => f.write_str("operator ^="),
TokenType::DoubleEqual => f.write_str("operator =="),
TokenType::BangEqual => f.write_str("operator !="),
TokenType::Greater => f.write_str("operator >"),
TokenType::GreaterEqual => f.write_str("operator >="),
TokenType::Less => f.write_str("operator <"),
TokenType::LessEqual => f.write_str("operator <="),
TokenType::Spaceship => f.write_str("operator <=>"),
TokenType::PipeColon => f.write_str("operator |:"),
TokenType::PipePoint => f.write_str("operator |>"),
TokenType::PipeQuestion => f.write_str("operator |?"),
TokenType::PipeAmper => f.write_str("operator |&"),
TokenType::PipeSlash => f.write_str("operator |/"),
TokenType::PipeDoubleSlash => f.write_str("operator |//"),
TokenType::Backslash => f.write_str("backslash"),
TokenType::Comma => f.write_str("comma"),
TokenType::Semicolon => f.write_str("semicolon"),
TokenType::Colon => f.write_str("colon"),
TokenType::LParen => f.write_str("left paren"),
TokenType::RParen => f.write_str("right paren"),
TokenType::LBrack => f.write_str("left bracket"),
TokenType::RBrack => f.write_str("right bracket"),
TokenType::LBrace => f.write_str("left brace"),
TokenType::RBrace => f.write_str("right brace"),
TokenType::True => f.write_str("literal 'true'"),
TokenType::False => f.write_str("literal 'false'"),
TokenType::Nil => f.write_str("literal 'nil'"),
TokenType::If => f.write_str("keyword 'if'"),
TokenType::Elif => f.write_str("keyword 'elif'"),
TokenType::Else => f.write_str("keyword 'else'"),
TokenType::For => f.write_str("keyword 'for'"),
TokenType::While => f.write_str("keyword 'while'"),
TokenType::Fn => f.write_str("keyword 'fn'"),
TokenType::Let => f.write_str("keyword 'let'"),
TokenType::Struct => f.write_str("keyword 'struct'"),
TokenType::Break => f.write_str("keyword 'break'"),
TokenType::Continue => f.write_str("keyword 'continue'"),
TokenType::Return => f.write_str("keyword 'return'"),
}
}
}
impl TokenType {
pub fn get_op_type(&self) -> Option<OpType> {
match self {
@ -55,7 +128,7 @@ impl TokenType {
Self::Caret => Some(OpType::Exponential),
Self::PipeColon | Self::PipeAmper | Self::PipePoint | Self::PipeQuestion
| Self::PipeSlash | Self::PipeDoubleSlash | Self::PipeBackslash | Self::PipeDoubleBackslash => Some(OpType::Pipeline),
| Self::PipeSlash | Self::PipeDoubleSlash => Some(OpType::Pipeline),
Self::Greater | Self::GreaterEqual | Self::Less | Self::LessEqual
| Self::DoubleEqual | Self::BangEqual | Self::Spaceship => Some(OpType::Comparison),

View file

@ -473,7 +473,7 @@ value_from!(Float, f32 f64);
value_from!(Complex, Complex);
value_from!(Rational, Rational);
value_from!(Bool, bool);
value_from!(String, String Rc<str>);
value_from!(String, String Rc<str> &str);
value_from!(List, RefCell<Vec<Value>>);
value_from!(Char, char);
value_from!(Map, RefCell<HashMap<Value,Value>>);