Browse Source

Scanner done

pull/1/head
Stephen 3 months ago
commit
4ce7149765
9 changed files with 578 additions and 0 deletions
  1. +1
    -0
      .gitignore
  2. +185
    -0
      Cargo.lock
  3. +10
    -0
      Cargo.toml
  4. +63
    -0
      example.rscript
  5. +2
    -0
      rustfmt.toml
  6. +14
    -0
      src/error_handler.rs
  7. +37
    -0
      src/main.rs
  8. +221
    -0
      src/scanner.rs
  9. +45
    -0
      src/token.rs

+ 1
- 0
.gitignore View File

@ -0,0 +1 @@
/target

+ 185
- 0
Cargo.lock View File

@ -0,0 +1,185 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "electroscript"
version = "0.1.0"
dependencies = [
"phf",
]
[[package]]
name = "getrandom"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "libc"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
[[package]]
name = "phf"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [
"phf_macros",
"phf_shared",
"proc-macro-hack",
]
[[package]]
name = "phf_generator"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
dependencies = [
"phf_shared",
"rand",
]
[[package]]
name = "phf_macros"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
dependencies = [
"siphasher",
]
[[package]]
name = "ppv-lite86"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom",
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
"rand_pcg",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core",
]
[[package]]
name = "rand_pcg"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
dependencies = [
"rand_core",
]
[[package]]
name = "siphasher"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7"
[[package]]
name = "syn"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"

+ 10
- 0
Cargo.toml View File

@ -0,0 +1,10 @@
[package]
name = "electroscript"
version = "0.1.0"
authors = ["Stephen <webmaster@scd31.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
phf = { version = "0.8", features = ["macros"] }

+ 63
- 0
example.rscript View File

@ -0,0 +1,63 @@
extern module and("and.sch") ; a kicad schematic in the same directory which defines some input/output signals and wires them together in some way
extern module or("or.sch") ; same thing, but for or
extern module xor("xor.sch")
; a binary half adder
module half_adder(input a, input b, output sum, output carryOut)
{
xor(a, b, output)
and(a, b, carryOut)
}
; a binary full adder
module full_adder(input a, input b, input carryIn, output sum, output carryOut)
{
signal sum1
signal carry1
signal carry2
half_adder(a, b, sum1, carry1)
half_adder(sum1, carryIn, sum, carry2)
or(carry1, carry2, carryOut)
}
; a byte full adder
; takes in busses(arrays of signals)
module byte_adder(input A[0:7], input B[0:7], input carryIn, output Sum[0:7], output carryOut)
{
signal Carries[0:6]
full_adder(A[0], B[0], carryIn, Sum[0], carry[0])
full_adder(A[1], B[1], carry[0], Sum[1], carry[1])
full_adder(A[2], B[2], carry[1], Sum[2], carry[2])
full_adder(A[3], B[3], carry[2], Sum[3], carry[3])
full_adder(A[4], B[4], carry[3], Sum[4], carry[4])
full_adder(A[5], B[5], carry[4], Sum[5], carry[5])
full_adder(A[6], B[6], carry[5], Sum[6], carry[6])
full_adder(A[7], B[7], carry[6], Sum[7], carryOut)
}
; this is what gets converted into a schematic
; it's just a re-export of the byte adder.
module main(input A[0:7], input B[0:7], input cIn, output Sum[0:7], output cOut)
{
byte_adder(A, B, cIn, Sum, cOut)
}
; tests
; we need to define how and, or, xor work
; compiler will look at the signals to figure out what are inputs and what are outputs
expect and(0, 0, 0)
expect and(0, 1, 0)
expect and(1, 0, 0)
expect and(1, 1, 1) ; can also write `expect and(HIGH, HIGH, HIGH)` - HIGH = 1 and LOW = 0; The compiler doesn't care
test adder works ; start with "test", then the name of the test case
{
signal Sum[0:7]
signal cOut
; numbers are automatically converted into bus values
byte_adder(2, 3, LOW, Sum, cOut)
assert_equal(5, Sum)
assert_equal(LOW, cOut)
}

+ 2
- 0
rustfmt.toml View File

@ -0,0 +1,2 @@
tab_spaces = 4
hard_tabs = true

+ 14
- 0
src/error_handler.rs View File

@ -0,0 +1,14 @@
pub struct ErrorHandler {
pub errors: Vec<String>,
}
impl ErrorHandler {
pub fn new() -> Self {
Self { errors: Vec::new() }
}
pub fn error(&mut self, line: usize, message: &str) {
self.errors
.push(format!("Error on line {}: {}", line, message));
}
}

+ 37
- 0
src/main.rs View File

@ -0,0 +1,37 @@
mod error_handler;
mod scanner;
mod token;
use crate::scanner::Scanner;
use std::env;
use std::fs;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 2 {
println!("Usage: {} my_script.es", args[0]);
return;
}
match process_file(&args[1]) {
Ok(()) => {
println!("Success");
}
Err(errs) => {
for err in errs {
println!("{}", err);
}
}
}
}
fn process_file(file_name: &str) -> Result<(), Vec<String>> {
let code = fs::read_to_string(file_name)
.map_err(|err| vec![format!("Could not open {}: {}", file_name, err)])?;
let scanner = Scanner::new(&code);
let tokens = scanner.scan_tokens()?;
println!("{:?}", tokens);
Ok(())
}

+ 221
- 0
src/scanner.rs View File

@ -0,0 +1,221 @@
use crate::error_handler::ErrorHandler;
use crate::token::{Token, TokenType};
use phf::phf_map;
use std::str::FromStr;
static KEYWORDS: phf::Map<&'static str, TokenType> = phf_map! {
"extern" => TokenType::Extern,
"module" => TokenType::Module,
"input" => TokenType::Input,
"output" => TokenType::Output,
"signal" => TokenType::Signal,
"HIGH" => TokenType::High,
"LOW" => TokenType::Low,
"HIZ" => TokenType::Hiz,
};
struct ScanError {
line: usize,
msg: String,
}
impl ScanError {
fn new(line: usize, msg: String) -> Self {
Self { line, msg }
}
}
pub struct Scanner {
chars: Vec<char>,
tokens: Vec<Token>,
start: usize,
current: usize,
line: usize,
error_handler: ErrorHandler,
}
impl Scanner {
pub fn new(source: &str) -> Self {
let chars = source.chars().collect();
Self {
chars,
tokens: Vec::new(),
start: 0,
current: 0,
line: 1,
error_handler: ErrorHandler::new(),
}
}
// Consumes the scanner, creating a vec of tokens
pub fn scan_tokens(mut self) -> Result<Vec<Token>, Vec<String>> {
while !self.is_at_end() {
self.start = self.current;
if let Err(e) = self.scan_token() {
self.error_handler.error(e.line, &e.msg);
return Err(self.error_handler.errors.clone());
}
}
self.tokens
.push(Token::new(TokenType::Eof, "".to_string(), self.line));
Ok(self.tokens.clone())
}
fn scan_token(&mut self) -> Result<(), ScanError> {
let c = self.advance();
let token = match c {
'[' => TokenType::LeftBracket,
']' => TokenType::RightBracket,
'(' => TokenType::LeftParen,
')' => TokenType::RightParen,
'{' => TokenType::LeftBrace,
'}' => TokenType::RightBrace,
':' => TokenType::Colon,
',' => TokenType::Comma,
';' => {
while self.peek() != '\n' && !self.is_at_end() {
self.advance();
}
return Ok(());
}
'"' => return self.string(),
'\n' => {
self.line += 1;
return Ok(());
}
' ' | '\r' | '\t' => return Ok(()),
'0'..='9' => {
self.number();
return Ok(());
}
_ => {
if is_alpha(c) {
self.identifier();
return Ok(());
} else {
return Err(ScanError::new(
self.line,
format!("Unexpected character '{}'.", c),
));
}
}
};
self.add_token(token);
Ok(())
}
fn string(&mut self) -> Result<(), ScanError> {
while self.peek() != '"' && !self.is_at_end() {
if self.peek() == '\n' {
self.line += 1;
}
self.advance();
}
if self.is_at_end() {
return Err(ScanError::new(
self.line,
"Unterminated string.".to_string(),
));
}
self.advance(); // closing "
let value: String = self.chars[(self.start + 1)..(self.current - 1)]
.iter()
.collect();
self.add_token(TokenType::String(value));
Ok(())
}
fn number(&mut self) {
while is_digit(self.peek()) {
self.advance();
}
let s: String = self.chars[self.start..self.current].iter().collect();
// Safe to unwrap here because we know s can only contain digits
self.add_token(TokenType::Number(u32::from_str(&s).unwrap()));
}
fn identifier(&mut self) {
while is_alphanumeric(self.peek()) {
self.advance();
}
let text: String = self.chars[self.start..self.current].iter().collect();
let token = match KEYWORDS.get(text.as_str()) {
Some(x) => x.clone(),
None => TokenType::Identifier,
};
self.add_token(token);
}
fn advance(&mut self) -> char {
self.current += 1;
self.chars[self.current - 1]
}
fn previous(&self) -> char {
if self.current == 0 {
'\0'
} else {
self.chars[self.current - 1]
}
}
fn peek(&self) -> char {
if self.is_at_end() {
return '\0';
}
self.chars[self.current]
}
fn is_at_end(&self) -> bool {
self.current >= self.chars.len()
}
fn add_token(&mut self, token_type: TokenType) {
let text: String = self.chars[self.start..self.current].iter().collect();
self.tokens.push(Token::new(token_type, text, self.line));
}
}
fn is_digit(c: char) -> bool {
('0'..='9').contains(&c)
}
fn is_alpha(c: char) -> bool {
('a'..='z').contains(&c) || ('A'..='Z').contains(&c) || c == '_'
}
fn is_alphanumeric(c: char) -> bool {
is_digit(c) || is_alpha(c)
}
mod test {
use crate::error_handler::ErrorHandler;
use crate::scanner::Scanner;
use crate::token::TokenType;
#[test]
fn scanner_works() {
let mut scanner = Scanner::new("module half_adder(input a) {}");
let token_types: Vec<(usize, TokenType)> = scanner
.scan_tokens()
.unwrap()
.into_iter()
.map(|x| (x.line, x.token_type))
.collect();
println!("{:?}", token_types);
assert_eq!(9, token_types.len());
}
}

+ 45
- 0
src/token.rs View File

@ -0,0 +1,45 @@
#[derive(Debug, Clone)]
pub enum TokenType {
Extern,
Module,
Identifier,
Input,
Output,
Signal,
High,
Low,
Hiz,
LeftBracket,
RightBracket,
LeftParen,
RightParen,
LeftBrace,
RightBrace,
Colon,
Comma,
Number(u32),
String(String),
Eof,
}
#[derive(Debug, Clone)]
pub struct Token {
pub line: usize,
pub lexeme: String,
pub token_type: TokenType,
}
impl Token {
pub fn new(token_type: TokenType, lexeme: String, line: usize) -> Self {
Self {
token_type,
lexeme,
line,
}
}
}

Loading…
Cancel
Save