Skip to content

Commit

Permalink
doc: docstrings for every module! special offer today only!
Browse files Browse the repository at this point in the history
  • Loading branch information
birkenfeld committed Aug 2, 2015
1 parent 8e57db9 commit 55d274e
Show file tree
Hide file tree
Showing 11 changed files with 155 additions and 6 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,9 @@ The test suite consists of input and output files for the demo programs in
`code`. Run `python test.py` to run the test suite. Run `python test.py
--compiled` to also test compiled code, note however that this takes a while.
Use the `--short` flag to skip the most time consuming tests.

## Hacking

I tried to put at least rudimentary comments into the code where it matters. If
you actually want to delve deeper into the cesspool that is INTERCAL, let me
know what I can do better!
54 changes: 49 additions & 5 deletions src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@
// if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
// -------------------------------------------------------------------------------------------------

/// Translates AST to Rust.
///
/// There is quite a bit of ugly code generation here. A library for quasi-quoting
/// and pretty-printing Rust code would help a lot here. I tried, at least, to make
/// the actual generated code as nice as possible, so that it is not too hard to
/// see what is going on at runtime.
///
/// All of the INTERCAL statements are generated into one monstrous function with a
/// big switch over the current logical line (pctr is the "program counter"). This
/// function is called from main(), which does not much else.
///
/// The code for the modules err.rs and stdops.rs are included in the generated code
/// courtesy of the syntax extension, and provide runtime support (that is hopefully
/// inlined in all the right places by the Rust/LLVM optimizer).
///
/// A lot of the generated code is similar to what eval.rs does at runtime, but most
/// of the common code lives in stdops.rs.
use std::fs::File;
use std::io::{ BufWriter, Write };
use std::rc::Rc;
Expand All @@ -38,6 +56,7 @@ pub struct Generator {
line: SrcLine,
}

/// An ad-hoc way to generate a newline followed by a certain amount of indentation.
fn indentation<'a>(n: usize) -> &'a str {
&"
"[..n+1]
Expand Down Expand Up @@ -70,11 +89,7 @@ impl Generator {
}
}

fn write(&mut self, s: &str) -> WRes {
try!(self.o.write(s.as_bytes()));
Ok(())
}

/// The main (and only) public method of the generator.
pub fn generate(&mut self) -> WRes {
let program = self.program.clone();
try!(self.gen_attrs());
Expand All @@ -91,6 +106,11 @@ impl Generator {
Ok(())
}

fn write(&mut self, s: &str) -> WRes {
try!(self.o.write(s.as_bytes()));
Ok(())
}

fn gen_attrs(&mut self) -> WRes {
w!(self.o; "#![allow(unused_imports)]");
Ok(())
Expand Down Expand Up @@ -176,6 +196,10 @@ impl Generator {
Some(i) => i,
None => return IE129.err()
};
// Jumps are a bit problematic: when we resume, we'd need full
// information about the current statement which is not available
// at runtime. Therefore we have to put the comefrom and the
// label of the statement on the next stack as well.
w!(self.o; "
if jumps.len() >= 80 {{
return err::IE123.err_with(None, {});
Expand Down Expand Up @@ -311,6 +335,7 @@ impl Generator {
Ok(())
}

/// Check for COME FROMs if the program uses computed COME FROM.
fn gen_comefrom_check(&mut self, cand1: &str, label: &str) -> WRes {
w!(self.o, 20; "let mut candidates = vec![];
if let Some(c) = {} {{ candidates.push(c); }}", cand1);
Expand All @@ -332,6 +357,7 @@ impl Generator {
Ok(())
}

/// Get the Rust name of the given variable reference.
fn get_varname(var: &Var) -> String {
match *var {
Var::I16(n) => format!("v{}", n),
Expand All @@ -341,7 +367,9 @@ impl Generator {
}
}

/// Generate an assignment of "val" to the given variable/array element.
fn gen_assign(&mut self, var: &Var) -> WRes {
// if the variable can't be IGNOREd, we can skip the check for it
let suffix = if match *var {
Var::I16(n) => self.program.var_info.0[n].can_ignore,
Var::I32(n) => self.program.var_info.1[n].can_ignore,
Expand All @@ -350,6 +378,9 @@ impl Generator {
} { "" } else { "_unchecked" };
match *var {
Var::I16(n) => {
// yes, we have to check this at this point - INTERCAL has no
// real types, so the magnitude of the value is the only
// reliable indicator whether we can put it into the variable
w!(self.o; "
if val > (std::u16::MAX as u32) {{
return err::IE275.err_with(None, {});
Expand Down Expand Up @@ -387,6 +418,7 @@ impl Generator {
Ok(())
}

/// Helper for ABSTAIN.
fn gen_abstain(&mut self, what: &Abstain, gen: &Fn(String) -> String) -> WRes {
if let &Abstain::Label(lbl) = what {
let idx = self.program.labels[&lbl];
Expand All @@ -401,13 +433,15 @@ impl Generator {
Ok(())
}

/// Evaluate an expression and assign it to "val".
fn gen_eval_expr(&mut self, expr: &Expr) -> WRes {
w!(self.o, 20; "let val = ");
try!(self.gen_eval(expr, ""));
w!(self.o; ";");
Ok(())
}

/// Evaluate a list of expressions and assign it to "subs". Used for array subscriptions.
fn gen_eval_subs(&mut self, exprs: &Vec<Expr>) -> WRes {
w!(self.o, 20; "let subs = vec![");
for (i, expr) in exprs.iter().enumerate() {
Expand All @@ -420,6 +454,7 @@ impl Generator {
Ok(())
}

/// Evaluate an expression (inline).
fn gen_eval(&mut self, expr: &Expr, astype: &str) -> WRes {
match *expr {
Expr::Num(_, v) => if v < 10 {
Expand Down Expand Up @@ -528,6 +563,7 @@ impl Generator {
Ok(())
}

/// Generate variable lookup inside an expression.
fn gen_lookup(&mut self, var: &Var, astype: &str) -> WRes {
match *var {
Var::I16(n) => w!(self.o; "(v{}.val{})", n,
Expand Down Expand Up @@ -571,14 +607,21 @@ impl Generator {
Ok(())
}

/// Generates local let-bindings for all the stuff we need to keep track of.
fn gen_program_vars(&mut self) -> WRes {
let vars = &self.program.var_info;
// program counter
w!(self.o, 4; "let mut pctr: usize = 0;");
// output stream
w!(self.o, 4; "let mut stdout = std::io::stdout();");
// NEXT stack (80 entries only)
w!(self.o, 4; "let mut jumps: Vec<(usize, Option<usize>, u16)> = Vec::with_capacity(80);");
// current input and output state
w!(self.o, 4; "let mut last_in: u8 = 0;");
w!(self.o, 4; "let mut last_out: u8 = 0;");
// random number generator state
w!(self.o, 4; "let mut rand_st: u32;");
// one binding for each variable used by the program
for i in 0..vars.0.len() {
w!(self.o, 4; "let mut v{}: Bind<u16> = Bind::new(0);", i);
}
Expand All @@ -591,6 +634,7 @@ impl Generator {
for i in 0..vars.3.len() {
w!(self.o, 4; "let mut b{}: Bind<Array<u32>> = Bind::new(Array::empty());", i);
}
// list of abstention state for each statement, can initially be 0 or 1
w!(self.o, 4; "let mut abstain = [");
for (i, stmt) in self.program.stmts.iter().enumerate() {
if i % 24 == 0 {
Expand Down
16 changes: 16 additions & 0 deletions src/err.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@

#![rick_embed_module_code]

/// Provides runtime errors.
///
/// The term "runtime error" is actually a bit misleading: the errors produced by the
/// compiler and by the compiled program actually behave the same. Certain error codes
/// can only be emitted by the compiler, and others only at program runtime.
///
/// Most errors are accompanied by "ON THE WAY TO ..." followed by the source line
/// number of the following statement. If the syslib or floatlib are automatically
/// appended to the program, their source line numbers will start where the original
/// program ended.
///
/// In the interpreter, errors are usually constructed with line number 0, and the
/// interpreter sets the correct line number before it hands the error up to its
/// caller. In compiled code, no such adjustment is done, so errors have to get the
/// correct line numbers when created.
use std::io;

/// Result of a statement.
Expand Down
5 changes: 5 additions & 0 deletions src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
// if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
// -------------------------------------------------------------------------------------------------

/// Interprets INTERCAL source.
///
/// The evaluator is used when rick is called with `-i`, or when the compiler generates
/// the output while compiling (in the constant-output case).
use std::fmt::{ Debug, Display };
use std::io::Write;
use std::u16;
Expand Down
5 changes: 5 additions & 0 deletions src/lex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
// if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
// -------------------------------------------------------------------------------------------------

/// A lexer for INTERCAL generated with RustLex.
///
/// The raw RustLex lexer is wrapped by a buffer iterator that adds a few
/// special methods, such as the pretty standard "peek" and "push back" features.
use std::io::Read;
use std::u32;

Expand Down
4 changes: 4 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
#![plugin(rustlex)]
#![plugin(rick_syntex)]

/// Main program for Rick.
///
/// Parses arguments, calls parser, optimizer, interpreter or code generator.
#[allow(plugin_as_library)]
extern crate rustlex;
extern crate getopts;
Expand Down
13 changes: 12 additions & 1 deletion src/mandel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@
// if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
// -------------------------------------------------------------------------------------------------

/// Mandelbrot fractal generator.
///
/// Used to display a nice "progress bar" like image on the console while you wait
/// for rustc to compile 50000 lines of generated Rust code. It could take a while...
///
/// This uses the 256-color ANSI codes, which might not be supported by all terminals.
/// If you see strange characters or just dots, get yourself an xterm.
///
/// Credits: most of this code is translated from PyPy's rpython/tool/ansi_mandelbrot.
use std::io::{ Write, stdout };
use std::cmp::min;
use std::ops::{ Add, Mul };
Expand Down Expand Up @@ -121,7 +131,8 @@ impl MandelPrinter {
println!("{} {}", color, self.max_color);
}
let idx = idxmax - (color + 1) * idxmax / self.max_color;
print!("\x1b[48;5;{}m \x1b[0m", COLORS[idx as usize]);
print!("\x1b[48;5;{}m\x1b[38;5;{}m.\x1b[0m",
COLORS[idx as usize], COLORS[idx as usize]);
if flush {
let _ = stdout.flush();
}
Expand Down
22 changes: 22 additions & 0 deletions src/opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,28 @@
// if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
// -------------------------------------------------------------------------------------------------

/// Optimizes INTERCAL code to look a little less like what your dog left on the carpet.
///
/// The optimizer gets the whole program and does several passes.
///
/// * constant folding: just reduces (sub)expressions involving no variables
/// * expressions: looks for common patterns of INTERCAL operator expressions
/// and replaces them by equivalent expressions involving native Rust operators
/// * constant output (can be disabled): if the program neither uses random numbers
/// nor takes any input, its output must be constant - the optimizer generates
/// this output using the Eval interpreter and replaces the program by a single
/// Print instruction (if your program does not terminate, you'd better disable
/// this pass with the -F option)
/// * abstain check: marks all statements that cannot be ABSTAINed from, so that
/// the code generator can skip emitting guards for them
/// * var check: marks all variables that cannot be IGNOREd, so that the code
/// generator can use unchecked assignments
///
/// The patterns recognized by the expression optimizer are pretty random. They
/// were selected to optimize performance of the `tpk.i` example program, and
/// could be expanded a lot. But at that point it's probably better to take the
/// route of C-INTERCAL and use a DSL for generic pattern matching.
use std::collections::BTreeMap;
use std::io::Cursor;
use std::u16;
Expand Down
12 changes: 12 additions & 0 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@
// if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
// -------------------------------------------------------------------------------------------------

/// Parses INTERCAL (yes, it's possible!) and generates an Abstract Sadism Tree.
///
/// Hand-written, since I didn't find any parser generators that looked nice enough.
/// The INTERCAL syntax is actually not that complicated (except where it is), but
/// the various extensions make it a bit iffy.
///
/// There are quite a few steps to do after parsing, which are done in the method
/// called `post_process`. It makes a list of statements into a "real" program.
use std::collections::{ BTreeMap, HashMap };
use std::io::{ Read, BufRead, BufReader, Cursor };
use std::u16;
Expand Down Expand Up @@ -47,6 +56,8 @@ pub struct Parser<'p> {
impl<'p> Parser<'p> {
pub fn new(code: &Vec<u8>, startline: usize, allow_bug: bool) -> Parser {
let cursor1 = Cursor::new(&code[..]);
// we have to keep a list of all physical source lines to generate
// E000 error messages, so duplicate the input stream
let lines = Parser::get_lines(BufReader::new(cursor1));
let cursor2 = Cursor::new(&code[..]);
Parser { lines: lines,
Expand Down Expand Up @@ -672,6 +683,7 @@ impl<'p> Parser<'p> {
});
}

/// Do whatever needs to be done after parsing is complete.
fn post_process(&self, stmts: Vec<Stmt>) -> Res<Program> {
let mut added_syslib = false;
let mut added_floatlib = false;
Expand Down
18 changes: 18 additions & 0 deletions src/stdops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@

#![rick_embed_module_code]

/// Runtime support for INTERCAL compiler and interpreter.
///
/// This file provides code that is useful in the interpreter and in the compiled
/// program. The syntax extension called above embeds the module source into the
/// module for use by the code generator.
///
/// The basic things implemented here are:
///
/// * Bind, the struct holding INTERCAL variables and their STASHes
/// * Array, the struct holding INTERCAL arrays
/// * RNG for execution chances
/// * jump handling for RESUME and FORGET
/// * Roman numeral and English spelled-out number conversion
/// * Basic read/write of numbers and bytes
/// * all the INTERCAL operators (mingle, select, unary and, unary or, unary xor)
use std::fmt::{ Debug, Display, Error, Formatter };
use std::fs::File;
use std::io::{ BufRead, Read, Write, stdin };
Expand Down Expand Up @@ -236,6 +252,8 @@ pub fn check_chance(chance: u8, state: u32) -> (bool, u32) {
if chance == 100 {
(true, state)
} else {
// this is the generator suggested as the default rand() by POSIX,
// I'm sure it is exceedingly random
let new_state = state.wrapping_mul(1103515245).wrapping_add(12345);
let random = (new_state / 65536) % 100;
(random < (chance as u32), new_state)
Expand Down
6 changes: 6 additions & 0 deletions src/syslib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
// if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
// -------------------------------------------------------------------------------------------------

/// Provides the syslib and floatlib as embedded code.
///
/// The syslib is appended to INTERCAL programs when they jump to a label
/// in the range 1000 to 1999 (but don't define any such label).
/// The same goes for floatlib with the range 5000 to 5999.
// This is the syslib.i from C-INTERCAL 0.30.

pub const SYSLIB_CODE: &'static [u8; 7156] = br##"
Expand Down

0 comments on commit 55d274e

Please sign in to comment.