summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/aas.rs41
-rw-r--r--src/app.rs38
-rw-r--r--src/app/init.rs108
-rw-r--r--src/app/main.rs48
-rw-r--r--src/app/print_help.rs50
-rw-r--r--src/app/print_version.rs35
-rw-r--r--src/app/run.rs56
-rw-r--r--src/cpu.rs52
-rw-r--r--src/format.rs59
-rw-r--r--src/is_valid_character.rs106
-rw-r--r--src/log.rs41
-rw-r--r--src/token.rs36
-rw-r--r--src/token/tokenise.rs131
13 files changed, 801 insertions, 0 deletions
diff --git a/src/aas.rs b/src/aas.rs
new file mode 100644
index 0000000..33ae452
--- /dev/null
+++ b/src/aas.rs
@@ -0,0 +1,41 @@
+/*
+ Copyright 2023 Gabriel Jensen.
+
+ This file is part of aas.
+
+ aas is free software: you can redistribute it
+ and/or modify it under the terms of the GNU
+ General Public License as published by the Free
+ Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ aas is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without
+ even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU
+ General Public License along with aas. If not,
+ see <https://www.gnu.org/licenses/>.
+*/
+
+extern crate enum_iterator;
+
+mod app;
+mod cpu;
+mod format;
+mod token;
+
+mod is_valid_character;
+mod log;
+
+pub use is_valid_character::*;
+
+pub const VERSION: (u32, u32, u32) = (
+ 0x0, // Major
+ 0x0, // Minor
+ 0x0, // Patch
+);
+
+fn main() { app::App::main() }
diff --git a/src/app.rs b/src/app.rs
new file mode 100644
index 0000000..caa9379
--- /dev/null
+++ b/src/app.rs
@@ -0,0 +1,38 @@
+/*
+ Copyright 2023 Gabriel Jensen.
+
+ This file is part of aas.
+
+ aas is free software: you can redistribute it
+ and/or modify it under the terms of the GNU
+ General Public License as published by the Free
+ Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ aas is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without
+ even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU
+ General Public License along with aas. If not,
+ see <https://www.gnu.org/licenses/>.
+*/
+
+use crate::cpu::Cpu;
+use crate::format::Format;
+
+mod init;
+mod main;
+mod print_help;
+mod print_version;
+mod run;
+
+pub struct App {
+ input: String,
+ output: String,
+
+ cpu: Cpu,
+ format: Format,
+}
diff --git a/src/app/init.rs b/src/app/init.rs
new file mode 100644
index 0000000..8ed43ac
--- /dev/null
+++ b/src/app/init.rs
@@ -0,0 +1,108 @@
+/*
+ Copyright 2023 Gabriel Jensen.
+
+ This file is part of aas.
+
+ aas is free software: you can redistribute it
+ and/or modify it under the terms of the GNU
+ General Public License as published by the Free
+ Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ aas is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without
+ even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU
+ General Public License along with aas. If not,
+ see <https://www.gnu.org/licenses/>.
+*/
+
+use crate::app::App;
+use crate::cpu::Cpu;
+use crate::format::Format;
+
+use std::env::args;
+use std::str::FromStr;
+
+macro_rules! use_remainder {
+ ($destination: ident, $argument: expr, $index: expr) => {{
+ let argument: &[char] = $argument;
+ let index: usize = $index + 0x1;
+
+ if argument.len() <= index {
+ let c = argument[index - 0x1];
+ Err(format!("missing value for short '{}'", c))
+ } else {
+ let value: String = argument[$index + 0x1..].iter().collect();
+ $destination = Some(value);
+
+ break;
+ }
+ }};
+}
+
+impl App {
+ pub fn init() -> Result<Self, String> {
+ if args().len() < 0x2 { Self::print_help() };
+
+ let mut input: Option<String> = None;
+ let mut output: Option<String> = None;
+ let mut cpu: Option<String> = None;
+ let mut format: Option<String> = None;
+
+ let mut handle_short = |argument: &String| -> Result<(), String> {
+ let argument: Vec<char> = argument.chars().collect();
+
+ for (index, c) in argument.iter().enumerate().skip(0x1) {
+ match c {
+ 'f' => use_remainder!(format, &argument, index)?,
+ 'h' => Self::print_help(),
+ 'o' => use_remainder!(output, &argument, index)?,
+ 'm' => use_remainder!(cpu, &argument, index)?,
+ 'v' => Self::print_version(),
+
+ _ => { return Err(format!("invalid short parameter '{c}'")) },
+ };
+ }
+
+ return Ok(());
+ };
+
+ for argument in args().skip(0x1) {
+ if argument.is_empty() { continue };
+
+ if argument.chars().nth(0x0) != Some('-') {
+ // This argument is the input path.
+
+ if input.is_some() { return Err(format!("input pathy is already set (to \"{}\")", input.as_ref().unwrap())) };
+
+ input = Some(argument.to_owned());
+ continue;
+ }
+
+ handle_short(&argument)?;
+ }
+
+ // Check if any of the mandatory parameters have
+ // been set.
+ if input.is_none() { return Err("missing input path".to_string()) };
+ if output.is_none() { output = Some("a.out".to_string()) };
+ if cpu.is_none() { return Err("missing cpu".to_string()) };
+
+ let format = match format {
+ Some(format) => Format::from_str(&format)?,
+ _ => Format::default(),
+ };
+
+ return Ok(Self {
+ input: input.unwrap(),
+ output: output.unwrap(),
+
+ cpu: Cpu::from_str(&cpu.unwrap())?,
+ format,
+ });
+ }
+}
diff --git a/src/app/main.rs b/src/app/main.rs
new file mode 100644
index 0000000..bd82623
--- /dev/null
+++ b/src/app/main.rs
@@ -0,0 +1,48 @@
+/*
+ Copyright 2023 Gabriel Jensen.
+
+ This file is part of aas.
+
+ aas is free software: you can redistribute it
+ and/or modify it under the terms of the GNU
+ General Public License as published by the Free
+ Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ aas is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without
+ even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU
+ General Public License along with aas. If not,
+ see <https://www.gnu.org/licenses/>.
+*/
+
+use crate::log;
+use crate::app::App;
+
+use std::process::exit;
+
+impl App {
+ pub fn main() -> ! {
+ let app = match App::init() {
+ Ok(app) => app,
+
+ Err(message) => {
+ log!(error, "{message}");
+ exit(0x1);
+ },
+ };
+
+ exit(match app.run() {
+ Err(message) => {
+ log!(error, "{message}");
+ 0x1
+ },
+
+ _ => 0x0,
+ });
+ }
+}
diff --git a/src/app/print_help.rs b/src/app/print_help.rs
new file mode 100644
index 0000000..1df895e
--- /dev/null
+++ b/src/app/print_help.rs
@@ -0,0 +1,50 @@
+/*
+ Copyright 2023 Gabriel Jensen.
+
+ This file is part of aas.
+
+ aas is free software: you can redistribute it
+ and/or modify it under the terms of the GNU
+ General Public License as published by the Free
+ Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ aas is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without
+ even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU
+ General Public License along with aas. If not,
+ see <https://www.gnu.org/licenses/>.
+*/
+
+use crate::app::App;
+use crate::cpu::Cpu;
+use crate::format::Format;
+
+use enum_iterator::all;
+use std::process::exit;
+
+impl App {
+ pub fn print_help() -> ! {
+ println!("\u{1B}[1mUsage\u{1B}[0m: \u{1B}[1maas\u{1B}[0m \u{1B}[3m[options]\u{1B}[0m <input>");
+ println!();
+ println!("\u{1B}[1mOptions\u{1B}[0m:");
+ println!(" \u{1B}[1m-f\u{1B}[0m\u{1B}[3m<format>\u{1B}[0m set the target executable format (see below)");
+ println!(" \u{1B}[1m-h\u{1B}[0m print help");
+ println!(" \u{1B}[1m-m\u{1B}[0m\u{1B}[3m<target>\u{1B}[0m set the target cpu (see below)");
+ println!(" \u{1B}[1m-v\u{1B}[0m print version");
+
+ println!();
+ println!("\u{1B}[1mCPUs\u{1B}[0m:");
+ for cpu in all::<Cpu>() { println!(" \u{1B}[3m{cpu}\u{1B}[0m") }
+
+ println!();
+ println!("\u{1B}[1mFormats\u{1B}[0m:");
+ for format in all::<Format>() { println!(" \u{1B}[3m{format}\u{1B}[0m") }
+
+ exit(0x0);
+ }
+}
diff --git a/src/app/print_version.rs b/src/app/print_version.rs
new file mode 100644
index 0000000..d28e457
--- /dev/null
+++ b/src/app/print_version.rs
@@ -0,0 +1,35 @@
+/*
+ Copyright 2023 Gabriel Jensen.
+
+ This file is part of aas.
+
+ aas is free software: you can redistribute it
+ and/or modify it under the terms of the GNU
+ General Public License as published by the Free
+ Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ aas is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without
+ even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU
+ General Public License along with aas. If not,
+ see <https://www.gnu.org/licenses/>.
+*/
+
+use crate::VERSION;
+use crate::app::App;
+
+use std::process::exit;
+
+impl App {
+ pub fn print_version() -> ! {
+ println!("\u{1B}[1maas\u{1B}[0m {:X}.{:X}.{:X}", VERSION.0, VERSION.1, VERSION.2);
+ println!("\u{1B}[3mCopyright \u{A9} 2023 Gabriel Bj\u{F8}rnager Jensen\u{1B}[0m.");
+
+ exit(0x0);
+ }
+}
diff --git a/src/app/run.rs b/src/app/run.rs
new file mode 100644
index 0000000..88d0983
--- /dev/null
+++ b/src/app/run.rs
@@ -0,0 +1,56 @@
+/*
+ Copyright 2023 Gabriel Jensen.
+
+ This file is part of aas.
+
+ aas is free software: you can redistribute it
+ and/or modify it under the terms of the GNU
+ General Public License as published by the Free
+ Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ aas is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without
+ even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU
+ General Public License along with aas. If not,
+ see <https://www.gnu.org/licenses/>.
+*/
+
+use crate::app::App;
+use crate::token::Token;
+
+use std::fs::read_to_string;
+
+impl App {
+ #[must_use]
+ pub fn run(self) -> Result<(), String> {
+ if cfg!(debug_assertions) {
+ println!("\u{1B}[1mSettings\u{1B}[0m:");
+ println!("\u{B7} input \u{1B}[3m\"{}\"\u{1B}[0m", self.input);
+ println!("\u{B7} output \u{1B}[3m\"{}\"\u{1B}[0m", self.output);
+ println!();
+ println!("\u{B7} cpu \u{1B}[3m{}\u{1B}[0m", self.cpu);
+ println!("\u{B7} format \u{1B}[3m{}\u{1B}[0m", self.format);
+ println!();
+ }
+
+ let input = match read_to_string(&self.input) {
+ Ok(content) => content,
+
+ _ => return Err(format!("unable to read file \"{}\"", &self.input)),
+ };
+
+ let tokens = Token::tokenise(&input)?;
+
+ eprintln!("\u{1B}[1mTokens\u{1B}[0m:");
+ for token in &tokens {
+ eprintln!("\u{B7} {token:?}");
+ }
+
+ return Ok(());
+ }
+}
diff --git a/src/cpu.rs b/src/cpu.rs
new file mode 100644
index 0000000..71a82f2
--- /dev/null
+++ b/src/cpu.rs
@@ -0,0 +1,52 @@
+/*
+ Copyright 2023 Gabriel Jensen.
+
+ This file is part of aas.
+
+ aas is free software: you can redistribute it
+ and/or modify it under the terms of the GNU
+ General Public License as published by the Free
+ Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ aas is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without
+ even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU
+ General Public License along with aas. If not,
+ see <https://www.gnu.org/licenses/>.
+*/
+
+use enum_iterator::Sequence;
+use std::fmt::{Display, Formatter};
+use std::str::FromStr;
+
+#[derive(Clone, Copy, Eq, PartialEq, Sequence)]
+pub enum Cpu {
+ Arm7tdmi,
+}
+
+impl Display for Cpu {
+ fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
+ use Cpu::*;
+ return match *self {
+ Arm7tdmi => write!(f, "arm7tdmi"),
+ };
+ }
+}
+
+impl FromStr for Cpu {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ use Cpu::*;
+ return match s.to_string().to_lowercase().as_str() {
+ "arm7tdmi" => Ok(Arm7tdmi),
+
+ _ => Err(format!("invalid target \"{s}\"")),
+ };
+ }
+}
diff --git a/src/format.rs b/src/format.rs
new file mode 100644
index 0000000..b8afcf1
--- /dev/null
+++ b/src/format.rs
@@ -0,0 +1,59 @@
+/*
+ Copyright 2023 Gabriel Jensen.
+
+ This file is part of aas.
+
+ aas is free software: you can redistribute it
+ and/or modify it under the terms of the GNU
+ General Public License as published by the Free
+ Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ aas is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without
+ even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU
+ General Public License along with aas. If not,
+ see <https://www.gnu.org/licenses/>.
+*/
+
+use enum_iterator::Sequence;
+use std::fmt::{Display, Formatter};
+use std::str::FromStr;
+
+#[derive(Clone, Copy, Eq, PartialEq, Sequence)]
+pub enum Format {
+ Elf,
+}
+
+impl Display for Format {
+ fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
+ use Format::*;
+ return match *self {
+ Elf => write!(f, "elf"),
+ };
+ }
+}
+
+impl Default for Format {
+ fn default() -> Self {
+ use Format::*;
+ return Elf;
+ }
+}
+
+impl FromStr for Format {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ use Format::*;
+ return match s.to_string().to_lowercase().as_str() {
+ "elf" => Ok(Elf),
+
+ _ => Err(format!("invalid format \"{s}\"")),
+ };
+ }
+}
diff --git a/src/is_valid_character.rs b/src/is_valid_character.rs
new file mode 100644
index 0000000..acec7fb
--- /dev/null
+++ b/src/is_valid_character.rs
@@ -0,0 +1,106 @@
+/*
+ Copyright 2023 Gabriel Jensen.
+
+ This file is part of aas.
+
+ aas is free software: you can redistribute it
+ and/or modify it under the terms of the GNU
+ General Public License as published by the Free
+ Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ aas is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without
+ even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU
+ General Public License along with aas. If not,
+ see <https://www.gnu.org/licenses/>.
+*/
+
+pub fn is_valid_character(c: char) -> bool {
+ return match c {
+ | '\t'
+ | '\n'
+ | ' '
+ | '!'
+ | '"'
+ | '#'
+ | '*'
+ | ','
+ | '.'
+ | '0'
+ | '1'
+ | '2'
+ | '3'
+ | '4'
+ | '5'
+ | '6'
+ | '7'
+ | '8'
+ | '9'
+ | ':'
+ | ';'
+ | '@'
+ | 'A'
+ | 'B'
+ | 'C'
+ | 'D'
+ | 'E'
+ | 'F'
+ | 'G'
+ | 'H'
+ | 'I'
+ | 'J'
+ | 'K'
+ | 'L'
+ | 'M'
+ | 'N'
+ | 'O'
+ | 'P'
+ | 'Q'
+ | 'R'
+ | 'S'
+ | 'T'
+ | 'U'
+ | 'V'
+ | 'W'
+ | 'X'
+ | 'Y'
+ | 'Z'
+ | '['
+ | ']'
+ | '_'
+ | 'a'
+ | 'b'
+ | 'c'
+ | 'd'
+ | 'e'
+ | 'f'
+ | 'g'
+ | 'h'
+ | 'i'
+ | 'j'
+ | 'k'
+ | 'l'
+ | 'm'
+ | 'n'
+ | 'o'
+ | 'p'
+ | 'q'
+ | 'r'
+ | 's'
+ | 't'
+ | 'u'
+ | 'v'
+ | 'w'
+ | 'x'
+ | 'y'
+ | 'z'
+ => true,
+
+ _ => false,
+ };
+}
diff --git a/src/log.rs b/src/log.rs
new file mode 100644
index 0000000..99601b3
--- /dev/null
+++ b/src/log.rs
@@ -0,0 +1,41 @@
+/*
+ Copyright 2023 Gabriel Jensen.
+
+ This file is part of aas.
+
+ aas is free software: you can redistribute it
+ and/or modify it under the terms of the GNU
+ General Public License as published by the Free
+ Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ aas is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without
+ even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU
+ General Public License along with aas. If not,
+ see <https://www.gnu.org/licenses/>.
+*/
+
+#[macro_export]
+macro_rules! log {
+ (error, $($message: tt)*) => {{
+ eprintln!("\u{1B}[1m\u{1B}[91merror\u{1B}[0m: {}", format!($($message)?));
+ }};
+
+ (note, $($message: tt)*) => {{q
+ eprintln!("\u{1B}[1m\u{1B}[95mnote\u{1B}[0m: {}", format!($($message)?));
+ }};
+
+ (status, $($message: tt)*) => {{
+ eprintln!("{}", format!($($message)?));
+ }};
+
+ (warning, $($message: tt)*) => {{
+ use crate::log::log;
+ eprintln!("\u{1B}[1m\u{1B}[93mwarning\u{1B}[0m: {}", format!($($message)?));
+ }};
+}
diff --git a/src/token.rs b/src/token.rs
new file mode 100644
index 0000000..68b7f4e
--- /dev/null
+++ b/src/token.rs
@@ -0,0 +1,36 @@
+/*
+ Copyright 2023 Gabriel Jensen.
+
+ This file is part of aas.
+
+ aas is free software: you can redistribute it
+ and/or modify it under the terms of the GNU
+ General Public License as published by the Free
+ Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ aas is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without
+ even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU
+ General Public License along with aas. If not,
+ see <https://www.gnu.org/licenses/>.
+*/
+
+mod tokenise;
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum Token {
+ BracketLeft,
+ BracketRight,
+ Colon,
+ Comma,
+ Fullstop,
+ Hashtag,
+ Return,
+ StringLiteral(String),
+ Word(String),
+}
diff --git a/src/token/tokenise.rs b/src/token/tokenise.rs
new file mode 100644
index 0000000..44ce683
--- /dev/null
+++ b/src/token/tokenise.rs
@@ -0,0 +1,131 @@
+/*
+ Copyright 2023 Gabriel Jensen.
+
+ This file is part of aas.
+
+ aas is free software: you can redistribute it
+ and/or modify it under the terms of the GNU
+ General Public License as published by the Free
+ Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ aas is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without
+ even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU
+ General Public License along with aas. If not,
+ see <https://www.gnu.org/licenses/>.
+*/
+
+use crate::is_valid_character;
+use crate::token::Token;
+
+impl Token {
+ #[must_use]
+ pub fn tokenise(input: &str) -> Result<Vec<Self>, String> {
+ let mut tokens: Vec<Self> = Vec::new();
+
+ let mut input_index: usize = 0x0;
+ while let Some(token) = get_next_token(&input, &mut input_index)? { tokens.push(token) }
+
+ return Ok(tokens);
+ }
+}
+
+#[must_use]
+fn get_next_token(input: &str, index: &mut usize) -> Result<Option<Token>, String> {
+ use Token::*;
+
+ let mut word = String::new();
+
+ let mut in_comment = false;
+ let mut in_string = false;
+
+ for c in input.chars().skip(*index) {
+ // Skip until we're out of the comment.
+ if in_comment {
+ if c != '\n' {
+ *index += 0x1;
+ continue;
+ }
+
+ in_comment = false;
+ }
+
+ // Finish the string (if inside one) and return.
+ if in_string {
+ *index += 0x1;
+
+ if c != '"' {
+ word.push(c);
+ continue;
+ }
+
+ return Ok(Some(StringLiteral(word)));
+ }
+
+ // We don't care about invalid character inside of
+ // comments or strings.
+ if !is_valid_character(c) { return Err(format!("invalid character U+{:04X} '{c}' ({index} / {})", c as u32, input.len())) };
+
+ // Check if the word is terminated. If it was, we
+ // don't count this character.
+ if !word.is_empty() {
+ match c {
+ | ' '
+ | '\t'
+ | '\n'
+ | '.'
+ | ','
+ | ':'
+ | ';'
+ | '@'
+ => return Ok(Some(Word(word))),
+
+ _ => {},
+ };
+ }
+
+ // There aren't any more things to complete
+ // (comments, strings, or words), so we know now
+ // that no more characters will be skipped.
+ *index += 0x1;
+
+ match c {
+ | ' '
+ | '\t'
+ => continue,
+
+ '\n' => return Ok(Some(Return)),
+ '[' => return Ok(Some(BracketLeft)),
+ ']' => return Ok(Some(BracketRight)),
+ '.' => return Ok(Some(Fullstop)),
+ ',' => return Ok(Some(Comma)),
+ ':' => return Ok(Some(Colon)),
+ '#' => return Ok(Some(Hashtag)),
+
+ | ';'
+ | '@'
+ => {
+ in_comment = true;
+ continue;
+ },
+
+ '"' => {
+ in_string = true;
+ continue;
+ }
+
+ _ => {},
+ };
+
+ word.push(c);
+ }
+
+ if in_string { return Err("unterminated string".to_string()) };
+
+ return Ok(None);
+}