Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions compiler/v1/src/ast/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,10 @@ pub enum Expr {
name: String,
value: Box<Expr>,
},

Update {
name: String,
op: TokenKind,
prefix: bool,
},
}
1 change: 1 addition & 0 deletions compiler/v1/src/ast/literal.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#[derive(Debug, Clone, PartialEq)]
pub enum Literal {
String(String),
Char(char),
Number(f64),
Bool(bool),
Null,
Expand Down
32 changes: 32 additions & 0 deletions compiler/v1/src/interpreter/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,27 @@ impl Executor {
env.assign(name, val.clone())?;
Ok(val)
}
Expr::Update { name, op, prefix } => {
let current = env
.get(name)
.cloned()
.ok_or_else(|| format!("Undefined variable '{}'", name))?;

let delta = match op {
TokenKind::PlusPlus => 1.0,
TokenKind::MinusMinus => -1.0,
_ => return Err("Invalid update operator".to_string()),
};

let current_num = match current {
Value::Number(n) => n,
_ => return Err("Can only apply ++/-- to numbers".to_string()),
};

let new_num = current_num + delta;
env.assign(name, Value::Number(new_num))?;
Ok(Value::Number(if *prefix { new_num } else { current_num }))
}
Expr::Call { callee, args } => {
// Check if it's a built-in function first
if let Expr::Variable(name) = callee.as_ref() {
Expand Down Expand Up @@ -354,6 +375,17 @@ impl Executor {
Ok(Value::Number(l / r))
}
}
(Value::Number(l), Percent, Value::Number(r)) => {
if r == 0.0 {
Err("Division by zero".to_string())
} else {
Ok(Value::Number(l % r))
}
}
(Value::Null, EqualEqual, Value::Null) => Ok(Value::Bool(true)),
(Value::Null, NotEqual, Value::Null) => Ok(Value::Bool(false)),
(Value::Null, EqualEqual, _) | (_, EqualEqual, Value::Null) => Ok(Value::Bool(false)),
(Value::Null, NotEqual, _) | (_, NotEqual, Value::Null) => Ok(Value::Bool(true)),
(Value::Number(l), EqualEqual, Value::Number(r)) => Ok(Value::Bool(l == r)),
(Value::Number(l), NotEqual, Value::Number(r)) => Ok(Value::Bool(l != r)),
(Value::Number(l), Less, Value::Number(r)) => Ok(Value::Bool(l < r)),
Expand Down
1 change: 1 addition & 0 deletions compiler/v1/src/interpreter/std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ impl StdLib {
fn formatValue(value: &Value) -> String {
match value {
Value::String(s) => s.clone(),
Value::Char(c) => c.to_string(),
Value::Number(n) => {
if n.fract() == 0.0 {
format!("{}", *n as i64)
Expand Down
3 changes: 3 additions & 0 deletions compiler/v1/src/interpreter/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::rc::Rc;
#[derive(Debug, Clone)]
pub enum Value {
String(String),
Char(char),
Number(f64),
Bool(bool),
Function(Function),
Expand All @@ -20,6 +21,7 @@ impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Value::String(a), Value::String(b)) => a == b,
(Value::Char(a), Value::Char(b)) => a == b,
(Value::Number(a), Value::Number(b)) => a == b,
(Value::Bool(a), Value::Bool(b)) => a == b,
(Value::Function(a), Value::Function(b)) => a == b,
Expand Down Expand Up @@ -52,6 +54,7 @@ impl From<Literal> for Value {
fn from(lit: Literal) -> Self {
match lit {
Literal::String(s) => Value::String(s),
Literal::Char(c) => Value::Char(c),
Literal::Number(n) => Value::Number(n),
Literal::Bool(b) => Value::Bool(b),
Literal::Null => Value::Null, }
Expand Down
4 changes: 4 additions & 0 deletions compiler/v1/src/lexer/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ impl<'a> Cursor<'a> {
self.source[self.position..].chars().next()
}

pub fn peekN(&self, n: usize) -> Option<char> {
self.source[self.position..].chars().nth(n)
}

pub fn advance(&mut self) -> Option<char> {
let ch = self.peek()?;
self.position += ch.len_utf8();
Expand Down
1 change: 1 addition & 0 deletions compiler/v1/src/lexer/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ pub enum LexerError {
UnexpectedCharacter { found: char, span: Span },

UnterminatedString { span: Span },
UnterminatedChar { span: Span },
}
167 changes: 161 additions & 6 deletions compiler/v1/src/lexer/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,16 @@ impl<'a> Lexer<'a> {
}
};
match ch {
'"' => self.lexString(start),
'"' => {
if self.cursor.peek() == Some('"') && self.cursor.peekN(1) == Some('"') {
self.cursor.advance();
self.cursor.advance();
self.lexMultilineString(start)
} else {
self.lexString(start)
}
}
'\'' => self.lexChar(start),

c if c.is_alphabetic() || c == '_' => Ok(self.lexIdentifier(start, c)),

Expand All @@ -51,9 +60,43 @@ impl<'a> Lexer<'a> {
'[' => Ok(self.simpleToken(TokenKind::LeftBracket, start)),
']' => Ok(self.simpleToken(TokenKind::RightBracket, start)),
';' => Ok(self.simpleToken(TokenKind::Semicolon, start)),
'+' => Ok(self.simpleToken(TokenKind::Plus, start)),
'-' => Ok(self.simpleToken(TokenKind::Minus, start)),
'*' => Ok(self.simpleToken(TokenKind::Star, start)),
'+' => {
let kind = if self.matchNext('+') {
TokenKind::PlusPlus
} else if self.matchNext('=') {
TokenKind::PlusEqual
} else {
TokenKind::Plus
};
Ok(Token {
kind,
span: Span::new(start, self.cursor.position()),
})
}
'-' => {
let kind = if self.matchNext('-') {
TokenKind::MinusMinus
} else if self.matchNext('=') {
TokenKind::MinusEqual
} else {
TokenKind::Minus
};
Ok(Token {
kind,
span: Span::new(start, self.cursor.position()),
})
}
'*' => {
let kind = if self.matchNext('=') {
TokenKind::StarEqual
} else {
TokenKind::Star
};
Ok(Token {
kind,
span: Span::new(start, self.cursor.position()),
})
}
'/' => {
// check for comment
if let Some('/') = self.cursor.peek() {
Expand All @@ -69,12 +112,58 @@ impl<'a> Lexer<'a> {
// recurse to get next token
return self.nextToken();
}
Ok(self.simpleToken(TokenKind::Slash, start))
let kind = if self.matchNext('=') {
TokenKind::SlashEqual
} else {
TokenKind::Slash
};
Ok(Token {
kind,
span: Span::new(start, self.cursor.position()),
})
}
':' => Ok(self.simpleToken(TokenKind::Colon, start)),
'?' => Ok(self.simpleToken(TokenKind::Question, start)),
'.' => Ok(self.simpleToken(TokenKind::Dot, start)),
',' => Ok(self.simpleToken(TokenKind::Comma, start)),
'%' => Ok(self.simpleToken(TokenKind::Percent, start)),
'%' => {
let kind = if self.matchNext('=') {
TokenKind::PercentEqual
} else {
TokenKind::Percent
};
Ok(Token {
kind,
span: Span::new(start, self.cursor.position()),
})
}

'&' => {
if self.matchNext('&') {
Ok(Token {
kind: TokenKind::AndAnd,
span: Span::new(start, self.cursor.position()),
})
} else {
Err(LexerError::UnexpectedCharacter {
found: ch,
span: Span::new(start, self.cursor.position()),
})
}
}
'|' => {
if self.matchNext('|') {
Ok(Token {
kind: TokenKind::OrOr,
span: Span::new(start, self.cursor.position()),
})
} else {
Err(LexerError::UnexpectedCharacter {
found: ch,
span: Span::new(start, self.cursor.position()),
})
}
}

'=' => {
let kind = if self.matchNext('=') {
Expand Down Expand Up @@ -161,6 +250,72 @@ impl<'a> Lexer<'a> {
span: Span::new(start, self.cursor.position()),
})
}

fn lexMultilineString(&mut self, start: usize) -> Result<Token, LexerError> {
let mut value = String::new();

while let Some(ch) = self.cursor.advance() {
if ch == '"' && self.cursor.peek() == Some('"') && self.cursor.peekN(1) == Some('"') {
self.cursor.advance();
self.cursor.advance();
if value.starts_with("\r\n") {
value.drain(..2);
} else if value.starts_with('\n') {
value.drain(..1);
}
return Ok(Token {
kind: TokenKind::StringLiteral(value),
span: Span::new(start, self.cursor.position()),
});
}
value.push(ch);
}

Err(LexerError::UnterminatedString {
span: Span::new(start, self.cursor.position()),
})
}

fn lexChar(&mut self, start: usize) -> Result<Token, LexerError> {
let ch = match self.cursor.advance() {
Some(c) => c,
None => {
return Err(LexerError::UnterminatedChar {
span: Span::new(start, self.cursor.position()),
});
}
};

let value = if ch == '\\' {
let esc = self.cursor.advance().ok_or_else(|| LexerError::UnterminatedChar {
span: Span::new(start, self.cursor.position()),
})?;
match esc {
'n' => '\n',
't' => '\t',
'r' => '\r',
'\\' => '\\',
'\'' => '\'',
other => other,
}
} else {
ch
};

match self.cursor.advance() {
Some('\'') => Ok(Token {
kind: TokenKind::CharLiteral(value),
span: Span::new(start, self.cursor.position()),
}),
Some(other) => Err(LexerError::UnexpectedCharacter {
found: other,
span: Span::new(start, self.cursor.position()),
}),
None => Err(LexerError::UnterminatedChar {
span: Span::new(start, self.cursor.position()),
}),
}
}
fn lexIdentifier(&mut self, start: usize, first: char) -> Token {
let mut ident = String::new();
ident.push(first);
Expand Down
11 changes: 11 additions & 0 deletions compiler/v1/src/lexer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,20 @@ pub enum TokenKind {
Star,
Percent,
Colon,
Question,
Not,
AndAnd,
OrOr,

//multi-char operators
PlusPlus,
MinusMinus,
PlusEqual,
MinusEqual,
StarEqual,
SlashEqual,
PercentEqual,

//keywords
Var,
Func,
Expand All @@ -48,6 +58,7 @@ pub enum TokenKind {
//literals
Identifier(String),
StringLiteral(String),
CharLiteral(char),
NumberLiteral(f64),

//operators
Expand Down
Loading
Loading