Skip to content

Commit

Permalink
Add example for Oberon-0 acceptor
Browse files Browse the repository at this point in the history
  • Loading branch information
0x2a-42 committed Dec 28, 2023
1 parent a51b4c0 commit 96ccb7e
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 0 deletions.
1 change: 1 addition & 0 deletions examples/oberon0/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target/
11 changes: 11 additions & 0 deletions examples/oberon0/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "lelwel-oberon0"
version = "1.0.0"
edition = "2021"

[dependencies]
logos = "0.13.0"
codespan-reporting = "0.11.1"

[build-dependencies]
lelwel = { path = "../.." }
2 changes: 2 additions & 0 deletions examples/oberon0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# lelwel-oberon0
An example for an acceptor of the [Oberon-0 programming language](https://en.wikipedia.org/wiki/PL/0#Oberon-0).
3 changes: 3 additions & 0 deletions examples/oberon0/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
lelwel::build("src/oberon0.llw");
}
31 changes: 31 additions & 0 deletions examples/oberon0/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use codespan_reporting::diagnostic::Severity;
use codespan_reporting::files::SimpleFile;
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
use codespan_reporting::term::{self, Config};
use logos::Logos;
use parser::*;

mod parser;

fn main() -> std::io::Result<()> {
let args: Vec<String> = std::env::args().collect();
if args.len() != 2 {
std::process::exit(1);
}
let contents = std::fs::read_to_string(&args[1])?;

let mut tokens = TokenStream::new(Token::lexer(&contents));
let mut diags = vec![];
Parser::parse(&mut tokens, &mut diags);

let writer = StandardStream::stderr(ColorChoice::Auto);
let config = Config::default();
let file = SimpleFile::new(&args[1], &contents);
for diag in diags.iter() {
term::emit(&mut writer.lock(), &config, &file, &diag).unwrap();
}
if diags.iter().any(|d| d.severity == Severity::Error) {
std::process::exit(1);
}
Ok(())
}
92 changes: 92 additions & 0 deletions examples/oberon0/src/oberon0.llw
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/// keyword
token Const='CONST' Var='VAR' Procedure='PROCEDURE' Begin='BEGIN'
End='END' If='IF' Then='THEN' While='WHILE' Do='DO' Elsif='ELSIF' Else='ELSE'
Type='TYPE' DivKw='DIV' ModKw='MOD' OrKw='OR' Array='ARRAY' Record='RECORD'
Of='OF' Module='MODULE' Repeat='REPEAT' Until='UNTIL';
/// punctuator
token Dot='.' Eq='=' Comma=',' Semi=';' Asn=':=' Hash='#'
Lt='<' Leq='<=' Gt='>' Geq='>=' Add='+' Sub='-' Mul='*' LPar='('
RPar=')' LBrak='[' RBrak=']' Not='~' And='&' Colon=':';
token Ident='<identifier>' Number='<number>';

start:
module
;

selector:
('.' Ident | '[' (expression !) ']')*
;
factor:
Ident selector | Number | '(' (expression !) ')' | '~' factor
;
term:
factor (('*' | 'DIV' | 'MOD' | '&') factor)*
;
simple_expression:
['+' | '-'] term (('+' | '-' | 'OR') term)*
;
expression:
simple_expression [('=' | '#' | '<' | '<=' | '>' | '>=') simple_expression]
;
assignment_or_procedure_call:
Ident selector (':=' expression | [actual_parameters])
;
actual_parameters:
'(' [expression (',' expression !)* !] ')'
;
if_statement:
'IF' (expression !) 'THEN' statement_sequence
('ELSIF' (expression !) 'THEN' statement_sequence)*
['ELSE' statement_sequence] 'END'
;
while_statement:
'WHILE' (expression !) 'DO' statement_sequence 'END'
;
repeat_statement:
'REPEAT' statement_sequence 'UNTIL' expression
;
statement:
[assignment_or_procedure_call | if_statement | while_statement | repeat_statement | !]
;
statement_sequence:
statement (';' statement)*
;
ident_list:
Ident (',' Ident !)*
;
array_type:
'ARRAY' (expression !) 'OF' type
;
field_list:
[ident_list ':' type]
;
record_type:
'RECORD' (field_list (';' field_list !)* !) 'END'
;
type:
Ident | array_type | record_type | !
;
fp_section:
['VAR'] ident_list ':' type
;
formal_parameters:
'(' [fp_section (';' fp_section !)* !] ')'
;
procedure_heading:
'PROCEDURE' Ident [formal_parameters] | !
;
procedure_body:
declarations ['BEGIN' statement_sequence] 'END' Ident | !
;
procedure_declaration:
procedure_heading ';' procedure_body
;
declarations:
['CONST' (Ident '=' expression ';' !)*]
['TYPE' (Ident '=' type ';' !)*]
['VAR' (ident_list ':' type ';' !)*]
(procedure_declaration ';' !)*
;
module:
'MODULE' Ident ';' declarations ['BEGIN' statement_sequence] 'END' Ident '.'
;
180 changes: 180 additions & 0 deletions examples/oberon0/src/parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
use codespan_reporting::diagnostic::Label;
use logos::{Lexer, Logos};

pub type Span = core::ops::Range<usize>;
pub type Diagnostic = codespan_reporting::diagnostic::Diagnostic<()>;

pub struct TokenStream<'a> {
lexer: Lexer<'a, Token>,
}

impl<'a> TokenStream<'a> {
pub fn new(lexer: Lexer<'a, Token>) -> Self {
Self { lexer }
}
#[inline]
pub fn next_token(&mut self) -> Result<Token, LexerError> {
if let Some(token) = Iterator::next(&mut self.lexer) {
return token;
}
Ok(Token::EOF)
}
#[inline]
fn span(&self) -> Span {
self.lexer.span()
}
}

macro_rules! check_limit {
($input:expr, $current:expr, $depth:expr) => {
if $depth > 128 {
*$current = Token::EOF;
return Err(Diagnostic::error()
.with_message("exceeded recursion depth limit")
.with_labels(vec![Label::primary((), $input.span())]));
}
};
}

macro_rules! err {
[$input:expr, $($tk:literal),*] => {
{
let expected = [$($tk),*];
let mut msg = "invalid syntax, expected".to_string();
if expected.len() > 1 {
msg.push_str(" one of: ");
} else {
msg.push_str(": ");
}
let mut count = 0;
for e in expected {
count += 1;
let s = format!("{}", e);
let s = if s.starts_with('<') && s.ends_with('>') && s.len() > 2 {
s
} else {
format!("'{}'", s)
};
msg.push_str(&s);
if count < expected.len() {
msg.push_str(", ");
}
}
Err(Diagnostic::error()
.with_message(msg)
.with_labels(vec![Label::primary((), $input.span())]))
}
}
}

#[derive(Debug, Clone, PartialEq, Default)]
pub enum LexerError {
#[default]
Invalid,
}

impl LexerError {
pub fn into_diagnostic(self, span: Span) -> Diagnostic {
match self {
Self::Invalid => Diagnostic::error()
.with_message("invalid token")
.with_labels(vec![Label::primary((), span)]),
}
}
}

#[derive(Logos, Debug, PartialEq, Clone)]
#[logos(skip r"\s+")]
#[logos(skip r"\(\*[^*]*\*+([^)*][^*]*\*+)*\)")]
#[logos(error = LexerError)]
pub enum Token {
EOF,
#[token("CONST")]
Const,
#[token("VAR")]
Var,
#[token("PROCEDURE")]
Procedure,
#[token("BEGIN")]
Begin,
#[token("END")]
End,
#[token("IF")]
If,
#[token("THEN")]
Then,
#[token("WHILE")]
While,
#[token("DO")]
Do,
#[token("ELSIF")]
Elsif,
#[token("ELSE")]
Else,
#[token("TYPE")]
Type,
#[token("DIV")]
DivKw,
#[token("MOD")]
ModKw,
#[token("OR")]
OrKw,
#[token("ARRAY")]
Array,
#[token("RECORD")]
Record,
#[token("OF")]
Of,
#[token("MODULE")]
Module,
#[token("REPEAT")]
Repeat,
#[token("UNTIL")]
Until,
#[token(".")]
Dot,
#[token("=")]
Eq,
#[token(",")]
Comma,
#[token(";")]
Semi,
#[token(":=")]
Asn,
#[token("#")]
Hash,
#[token("<")]
Lt,
#[token("<=")]
Leq,
#[token(">")]
Gt,
#[token(">=")]
Geq,
#[token("+")]
Add,
#[token("-")]
Sub,
#[token("*")]
Mul,
#[token("(")]
LPar,
#[token(")")]
RPar,
#[token("[")]
LBrak,
#[token("]")]
RBrak,
#[token("~")]
Not,
#[token("&")]
And,
#[token(":")]
Colon,
#[regex("[[:alpha:]][[:alnum:]]*")]
Ident,
#[regex(r"\d+")]
Number,
}

include!(concat!(env!("OUT_DIR"), "/generated.rs"));

0 comments on commit 96ccb7e

Please sign in to comment.