Skip to content

Commit

Permalink
✨ Add syntax highlighting
Browse files Browse the repository at this point in the history
semver: minor
  • Loading branch information
Somfic committed Oct 29, 2024
1 parent a030b0d commit 4926fa7
Show file tree
Hide file tree
Showing 10 changed files with 486 additions and 69 deletions.
306 changes: 304 additions & 2 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ authors = ["Somfic"]
description = "The 'som' programming language"

[dependencies]
miette = { version = "7.2.0", features = ["fancy"] }
miette = { version = "7.2.0", features = ["fancy", "syntect-highlighter"] }
owo-colors = "4.1.0"
pretty_assertions = "1.4.1"
regex = "1.10"
52 changes: 28 additions & 24 deletions src/lexer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ impl<'de> Lexer<'de> {
help = format!("expected {}, got {} instead", expected, token.kind),
"{unexpected}",
}
.with_source_code(self.whole.to_string())),
),
Some(Err(e)) => Err(e),
None => Err(miette::miette! {
labels = vec![
Expand All @@ -44,7 +44,7 @@ impl<'de> Lexer<'de> {
"unexpected end of input",
}
.wrap_err(unexpected.to_string())
.with_source_code(self.whole.to_string())),
),
}
}

Expand All @@ -61,17 +61,15 @@ impl<'de> Lexer<'de> {
],
help = format!("expected {token:?}"),
"{unexpected}",
}
.with_source_code(self.whole.to_string())),
}),
Some(Err(e)) => Err(e),
None => Err(miette::miette! {
labels = vec![
LabeledSpan::at_offset(self.byte_offset - 1, "expected more source code here")
],
help = "more source code was expected, but none was found",
"{unexpected}",
}
.with_source_code(self.whole.to_string())),
}),
}
}

Expand Down Expand Up @@ -149,8 +147,14 @@ impl<'de> Iterator for Lexer<'de> {
'%' => Ok((TokenKind::Percent, TokenValue::None)),
'=' => self.parse_compound_operator(TokenKind::Equal, TokenKind::Equality, '='),
'!' => self.parse_compound_operator(TokenKind::Not, TokenKind::Inequality, '='),
'<' => self.parse_compound_operator(TokenKind::LessThan, TokenKind::LessThanOrEqual, '='),
'>' => self.parse_compound_operator(TokenKind::GreaterThan, TokenKind::GreaterThanOrEqual, '='),
'<' => {
self.parse_compound_operator(TokenKind::LessThan, TokenKind::LessThanOrEqual, '=')
}
'>' => self.parse_compound_operator(
TokenKind::GreaterThan,
TokenKind::GreaterThanOrEqual,
'=',
),
'a'..='z' | 'A'..='Z' | '_' => {
// Identifiers
let mut ident = String::new();
Expand All @@ -170,8 +174,12 @@ impl<'de> Iterator for Lexer<'de> {
"else" => Ok((TokenKind::Else, TokenValue::None)),
"fn" => Ok((TokenKind::Function, TokenValue::None)),
"true" => Ok((TokenKind::Boolean, TokenValue::Boolean(true))),
"false" =>Ok((TokenKind::Boolean, TokenValue::Boolean(false))),
ident => Ok((TokenKind::Identifier, TokenValue::Identifier(ident.to_string().into()))),
"false" => Ok((TokenKind::Boolean, TokenValue::Boolean(false))),
"let" => Ok((TokenKind::Let, TokenValue::None)),
ident => Ok((
TokenKind::Identifier,
TokenValue::Identifier(ident.to_string().into()),
)),
}
}
// Whole and decimal numbers
Expand All @@ -191,15 +199,14 @@ impl<'de> Iterator for Lexer<'de> {
if let Ok(num) = number.parse::<i64>() {
Ok((TokenKind::Integer, TokenValue::Integer(num)))
} else if let Ok(num) = number.parse::<f64>() {
Ok((TokenKind::Decimal, TokenValue::Decimal(num)))
Ok((TokenKind::Decimal, TokenValue::Decimal(num)))
} else {
Err(miette::miette! {
labels = vec![
LabeledSpan::at(self.byte_offset - number.len()..self.byte_offset, "this number")
],
"invalid number"
}
.with_source_code(self.whole.to_string()))
})
}
}
'"' => {
Expand Down Expand Up @@ -233,22 +240,18 @@ impl<'de> Iterator for Lexer<'de> {
LabeledSpan::at(self.byte_offset..self.byte_offset + c.len_utf8(), "this character")
],
"expected closing single quote"
}
.with_source_code(self.whole.to_string()))
})
}
}
' ' | '\r' | '\t' | '\n' => {
return self.next();
}
_ => {
Err(miette::miette! {
labels = vec![
LabeledSpan::at(self.byte_offset - c.len_utf8()..self.byte_offset, "this character")
],
"unexpected character '{c}' in input"
}
.with_source_code(self.whole.to_string()))
}
_ => Err(miette::miette! {
labels = vec![
LabeledSpan::at(self.byte_offset - c.len_utf8()..self.byte_offset, "this character")
],
"unexpected character '{c}' in input"
}),
};

let byte_length = self
Expand All @@ -260,6 +263,7 @@ impl<'de> Iterator for Lexer<'de> {
kind,
value,
span: SourceSpan::new(start_offset.into(), byte_length),
original: &self.whole[start_offset..self.byte_offset],
}))
}
}
Expand Down
1 change: 1 addition & 0 deletions src/lexer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::{borrow::Cow, fmt::Display, hash::Hash};
pub struct Token<'de> {
pub kind: TokenKind,
pub value: TokenValue<'de>,
pub original: &'de str,
pub span: miette::SourceSpan,
}

Expand Down
93 changes: 89 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,106 @@
use lexer::Lexer;
use miette::LabeledSpan;
use std::vec;

use lexer::{Lexer, TokenKind};
use miette::{highlighters::Highlighter, LabeledSpan};
use owo_colors::{colors, styles, OwoColorize, Style, Styled};
use parser::Parser;

pub mod lexer;
pub mod parser;

fn main() {
let input = "{(1); (3); (2);}";
let input = "let value = a == { let b = 1 if true else 2 };";

miette::set_hook(Box::new(|_| {
Box::new(
miette::MietteHandlerOpts::new()
.terminal_links(true)
.unicode(true)
.context_lines(3)
.with_syntax_highlighting(SomHighlighter {})
.build(),
)
}))
.unwrap();

let mut parser = Parser::new(input);
let symbol = match parser.parse() {
Ok(symbol) => symbol,
Err(err) => {
println!("{:?}", err);
println!("{:?}", err.with_source_code(input.to_string()));
return;
}
};

println!("{:?}", symbol);
}

struct SomHighlighter {}
struct SomHighlighterState {}

impl miette::highlighters::Highlighter for SomHighlighter {
fn start_highlighter_state<'h>(
&'h self,
source: &dyn miette::SpanContents<'_>,
) -> Box<dyn miette::highlighters::HighlighterState + 'h> {
Box::new(SomHighlighterState {})
}
}

impl miette::highlighters::HighlighterState for SomHighlighterState {
fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<Styled<&'s str>> {
let mut sections: Vec<Styled<&'s str>> = vec![];

for word in line.split(' ') {
for token in Lexer::<'s>::new(word) {
let style: Style = match &token {
Ok(token) => match &token.kind {
// Comment / quote -> 92, 99, 112 + italic
TokenKind::If | TokenKind::Else | TokenKind::Let => {
Style::new().fg_rgb::<197, 120, 221>()
}
TokenKind::Identifier => Style::new().fg_rgb::<224, 108, 117>(),
TokenKind::String => Style::new().fg_rgb::<152, 195, 121>().italic(),
TokenKind::Integer | TokenKind::Decimal => {
Style::new().fg_rgb::<209, 154, 102>()
}
TokenKind::Boolean => Style::new().fg_rgb::<86, 156, 214>(),
TokenKind::CurlyOpen
| TokenKind::CurlyClose
| TokenKind::ParenOpen
| TokenKind::ParenClose
| TokenKind::SquareOpen
| TokenKind::SquareClose
| TokenKind::Equal
| TokenKind::LessThan
| TokenKind::GreaterThan
| TokenKind::LessThanOrEqual
| TokenKind::GreaterThanOrEqual
| TokenKind::Equality
| TokenKind::Inequality
| TokenKind::Plus
| TokenKind::Minus
| TokenKind::Star
| TokenKind::Slash
| TokenKind::Percent
| TokenKind::Not
| TokenKind::And
| TokenKind::Pipe
| TokenKind::Caret
| TokenKind::Or
| TokenKind::Semicolon
| TokenKind::Comma => Style::new().fg_rgb::<200, 200, 200>(),
_ => Style::new().fg_rgb::<171, 178, 191>(),
},
Err(_) => return vec![Style::new().remove_all_effects().white().style(line)],
};

let token = token.unwrap();
sections.push(style.style(token.original));
}
sections.push(Style::new().remove_all_effects().style(" "));
}

sections
}
}
2 changes: 2 additions & 0 deletions src/parser/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ pub enum Symbol<'de> {

#[derive(Debug)]
pub enum Statement<'de> {
Block(Vec<Statement<'de>>),
Expression(Expression<'de>),
Assignment(Cow<'de, str>, Expression<'de>),
}

#[derive(Debug)]
Expand Down
14 changes: 7 additions & 7 deletions src/parser/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@ pub fn parse<'de>(
return Err(miette::miette! {
help = "expected an expression",
"expected an expression"
}
.with_source_code(parser.source.to_string()))
})
}
};

let handler = parser.lookup.expression_lookup.get(&token.kind).ok_or(
miette::miette! {
let handler = parser
.lookup
.expression_lookup
.get(&token.kind)
.ok_or(miette::miette! {
labels = vec![token.label("expected an expression")],
help = format!("{} is not an expression", token.kind),
"expected an expression, found {}", token.kind
}
.with_source_code(parser.source.to_string()),
)?;
})?;
let mut lhs = handler(parser)?;

let mut next_token = parser.lexer.peek();
Expand Down
3 changes: 2 additions & 1 deletion src/parser/lookup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::collections::HashMap;

use super::{
ast::{BinaryOperator, Expression, Primitive, Statement},
expression, Parser,
expression, statement, Parser,
};

#[derive(Debug, Clone, PartialEq, PartialOrd)]
Expand Down Expand Up @@ -185,6 +185,7 @@ impl<'de> Default for Lookup<'de> {
BindingPower::Relational,
expression::binary::or,
)
.add_statement_handler(TokenKind::Let, statement::let_)
}
}

Expand Down
8 changes: 7 additions & 1 deletion src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ impl<'de> Parser<'de> {
}

pub fn parse(&mut self) -> Result<Symbol<'de>> {
Ok(Symbol::Statement(statement::parse(self, false)?))
let mut statements = vec![];

while self.lexer.peek().is_some() {
statements.push(statement::parse(self, false).wrap_err("while parsing main block")?);
}

Ok(Symbol::Statement(Statement::Block(statements)))
}
}
Loading

0 comments on commit 4926fa7

Please sign in to comment.