Programming

Street Shooter

When I was browsing Google Maps’ Street View, a weird idea sudden popped out: Street Shooter. It’s a game where you shoot the people and cars in the Street View. To play the game, you must go to Google Maps and enter a Street View, then use a bookmarklet.

I don’t know if the implementation is possible. It seems the Maps Term of Use does not allow it and the Street View API doesn’t have capability to do that.

I also have ideas of Word and Excel macro game, where your Word document or Excel spreadsheet become a platformer action game.

Tags: ,

Sunday, October 11th, 2009 Programming, Web Design/Development Comments Off

PHP Flaws

You saw it, you learned it, you programmed it, it’s everywhere. Well, I’m not talking about a Microsoft product, it is PHP.

Are you annoyed by the error messages of PHP? When something wrong happened, an error message will show up, which may break the HTML being generated. To remove the error messages, you must add error_reporting(0) in front of the code, or it won’t be portable. Talking about portability… PHP is a configurable programming language. The configuration makes PHP code must detect the configurations and perform actions based on it.
Can you imagine of the php.ini setting default_php_mode exists? By turning on default_php_mode, you can start writing PHP code without , for example:

include 'header.php'
?>
<!DOCTYPE html>
<html>
<head>
......

You won’t be able to use PHP code to detect it. Of course, that configuration does exist, for good.

Magic quotes, originally used to help newbie programmers in security, made a hassle for experienced programmers and PHP5 newbies. You must add a boilerplate code like this

<?php
if (get_magic_quotes_gpc()) {
  $_REQUEST = array_map('stripslashes', $_REQUEST);
  $_GET = array_map('stripslashes', $_GET);
  $_POST = array_map('stripslashes', $_POST);
  $_COOKIE = array_map('stripslashes', $_COOKIE);
}
?>

to make sure the text that will be used in database queries will not be quad-slashed (like 'text here \\\\').

The flags are bad. I hate these things like file_put_contents('file.txt', 'Hello corrupted world', FILE_APPEND); preg_match($pattern, $subject, $matches, PREG_OFFSET_CAPTURE, 3);. Also, some functions use array references, for example, preg_match($pattern, $subject, $matches, PREG_OFFSET_CAPTURE, 3); will modify the variable $matches. It should be done like:

<?php
try {
  $matches = preg_match_offset_capture($pattern, $subject, 3);
} catch (PregException $e) {
  $matches = array();
}
?>

The variable scoping may confuse the newbie PHP programmers who had programming experience on other programming languages such as JavaScript. You can’t access global variables in the function scope. You must use $GLOBALS or global $g_variable. It’s a huge hassle.
Here is an example of PHP global variables:

<?php
$x = 0;

function f($n) {
  $x += $n; // wrong, not accessing global $x
}

function g($n) {
  global $x; // right
  $x += $n;
}

function h($n) {
  $GLOBALS['x'] += $n; // right
}

?>

Maybe you know, or maybe you don’t. There are alternative syntaxes for if and the for blocks.

<?php
if ($x):

else:

endif;

foreach ($a as $b):

endforeach;
?>

Some people will use the brace syntax, and some people will use that alternative syntax, which will cause inconsistency in large coding projects. The alternative syntaxes should be removed.

Most low-class Web developers who do not know PHP, copy and paste small scripts from large script repositories, like copying full of JavaScript from DynamicDrive for vanity proposes. Because PHP attracts low-class developers, the syntax of PHP became messy to meet the needs for the low-class developers, then, more low-class developers will be attracted.
PHP needs a revolution. The revolution will change the PHP syntax to something clean, ECMAScript syntax could be fair. Yes, do break the backward compatibility, too much backward compatibility cause problems. If PHP revolution won’t start… <%= "Hello Ruby on Rails world!" %>

Tags:

Wednesday, September 16th, 2009 Programming, Web Design/Development 1 Comment

Metaprogramming Examples and Applications

Metaprogramming is the writing of programs that can write/manipulate the program itself or other programs. If you are using PHP and MySQL, you are already metaprogramming.

<?php
$title = mysql_real_escape_string($_GET['search']);
$r = mysql_query("SELECT * FROM database1.table1 WHERE PageTitle = '" . $search . "'");
// ......
?>

By concatenating the user-submitted data, you generated a MySQL query.

In fact, PHP generates HTML, PHP is a meta-programming language. Don’t think PHP can only generate HTML. PHP can also generate CSS and JavaScipt. In some sites, you can customize the background color of your profile. The color you’ve chosen will be part of the CSS of your profile.

<style type='text/css'>
#profile {
  background-color: <?php echo $userperf['BackgroundColor'] ?>;
  <?php if ($userpref['BackgroundImage']) { echo 'background-image: ' . $userpref['BackgroundImage'] } ?>
  color: <?php echo $userpref['TextColor'] ?>
  /* ...... */
}
</style>

You can’t get the client’s IP address with JavaScript, but PHP can. The following code generates JavaScript code that alerts your IP address.

<script type='text/javascript'>alert("Your IP address is:<?php echo $_SERVER['REMOTE_ADDR'] ?></script>")

Metaprogramming allows you to change a general-propose to a domain-specific language, for example, changing Ruby to a domain-specific language by changing the way you program.

In Ruby, there are methods such as attr_reader and attr_writer, they are not keywords, instead, they made use of metaprogramming.

class Module # if the class Module exists, extend the class Module
   def attr_reader (*syms) # define the method attr_reader
     syms.each do |sym|
        class_eval %{def #{sym} # define the method dynamically based on the names given
                         @#{sym}
                       end}
     end
   end
end

There is no type checking in Ruby, writing same thing such as if arg1.kind_of? String is inconvenient, so you can use the Ruby metaprogramming capabilities to automate it: Use methods to generate methods.

First, you extend the class Module.

class Module

end

Then, add the method for the type-checking functionality,

class Module
  def type_checked(name, *types, &block)
    class_eval do
      define_method(name) do |*args|
        i = 0
        if args.length < types.length
          raise ArgumentError.new "Not enough number of arguments. Excepting #{types.length}. Got #{args.length}"
        end
        types.each do |type|
          if not args[i].kind_of? type
            raise TypeError.new "Excepting #{type} for paramter #{i}. Got #{args[i].class}."
          end
          i += 1
        end
        block.call(*args)
      end
    end
  end
end

Now you can use type_checked like a keyword.

class Test
  type_checked :add, Fixnum, Fixnum do |x, y|
    x + y
  end
end

It will make (Test.new)add("hello", "world") throw an exception because “hello” and “world” are Strings.

You can even implement unit calculation in Ruby.

class Module
  def unit(name, x)
    class_eval "def #{name}; self * #{x}; end;"
  end
end
class Numeric
  unit :mm, 0.001
  unit :cm, 0.01
  unit :dm, 0.1
  unit :m, 1
  unit :km, 1000
end

Because we extended the class Numeric, all numbers will have properties such as cm and mm. 10.cm will evaluate to 0.1, because 10cm = 0.1m.

In Python, you can use decorators to achieve metaprogramming. Decorators are like @deco, they are put before function definitions.

Here is a type-checking script in Python:

def accepts(*args):
  def de(func):
    def f(*fargs, **fkwargs):
      i = 0
      for a_type in args:
        if a_type != type(fargs[i]):
          raise TypeError("Expecting %s for argument %d. Got %s." % (a_type, i, type(fargs[i])))
      i += 1
      return func(*fargs, **fkwargs)
    return f
  return de

@accepts(int, int)
def add(a, b):
  return a + b

By adding @accepts(int, int) decorator before the function, you wrap the function with the type-checking routine, while you need to write them one-by-one normally.

Don’t think JavaScript does not have class inheritance. JavaScript is a functional and dynamic programming language.
You can use prototypes for class extending and class inheritance.

function Base() { }
function Derived() { } // class Derived extends Base { public Derived() : Base() { } }
Derived.prototype = new Base();

// add method alert to String s.
String.prototype.alert = function() { alert(this) }
"Hello, world!".alert();

Metaprogramming links:

Tags: , , , , , , ,

Monday, September 7th, 2009 Programming, Tutorial, Web Design/Development Comments Off

Processing.js Auto-Start with Apache mod_rewrite and PHP

Tired of keep adding boilerplate code for Processing.js scripts? Are you making full of Processing.js scripts and you are too lazy to share them? I wrote a script for that. You only need to upload the Processing.js scripts as .pjs file extension then you only need to type the URL to the script in your address bar. Your server will wrap it with initialization code. Very easy.

To see it in action:

Download the script here: http://www.mediafire.com/download.php?gfozzoymzew.

After you downloaded the script, create a new directory on your server, upload the files in the archive. To test if it works, type http://YOURSITE.com/PATH/sample.pjs (where YOURSITE.com is the domain of your site and PATH is the path to the Processing.js auto-loader you’ve uploaded), it will show you the sample file called sample.pjs.

Monday, August 3rd, 2009 Programming, Web Design/Development Comments Off

A Simple Processing.js Game

I was trying to code something. Then I made a simple game with Processing.js. It is a game where you use your mouse to move the blue circle and avoid all red circles.

You can see it at http://uniteduniversemainsite.freehostia.com/files/processingjs/avoider.html. It uses HTML 5, so it only work for latest, beta (and higher) browsers such as Firefox 3.5, Opera 10, Safari 4 and Chrome 2. The texts such as the score and level indicator only work on Firefox 3.5.
I’m going to make a shooter game next time.

Source code:

class Enemy {
  int x;
  int y;
  int speed;

  Enemy(int x, int speed) {
    this.x = x;
    this.y = 0;
    this.speed = constrain(speed, 0, 10);
  }

  void update() {
    fill(255, 0, 0);
    stroke(128, 0, 0);
    ellipseMode(CENTER);
    ellipse(x, y, 40, 40);
    this.y += speed;
  }
}

PFont font;
ArrayList enemies;
int level;
int score;
int timer;
int count;
int playerX;
int playerY;
byte state = 0;

void setup() {
  size(400, 400);
  font = loadFont("Courier New");
  reset();
}

void reset() {
  enemies = new ArrayList();
  level = 1;
  score = 0;
  timer = 150;
  count = 150;
  playerX = 200;
  playerY = 200;
}

void draw() {
  background(0);
  if (state == 2) {
    if (score >= level * level * 10) {
      level ++;
    }
    count = (1 / level) * 200;
    timer ++;
    if (timer > count) {
      timer = 0;
      enemies.add(new Enemy(random(400), sqrt(level) / 2));
    }

    for (int i = enemies.size() - 1; i > -1; i --) {
      Enemy enemy = (Enemy) enemies.get(i);
      if (enemy.y > height) {
        enemies.remove(i);
        score += level * 2;
        continue;
      }
      if (playerX < enemy.x + 30 && playerX > enemy.x - 30 &&
        playerY < enemy.y + 30 && playerY > enemy.y - 30) {
        state = 1;
        break;
      }
      enemy.update();
    }

    playerX += constrain(mouseX - playerX, -5, 5);
    playerY += constrain(mouseY - playerY, -5, 5);

    fill(0, 0, 255);
    stroke(0, 0, 128);
    ellipseMode(CENTER);
    ellipse(playerX, playerY, 30, 30);

    fill(255);
    textFont(font, 16);
    text("Score: " + score + "  |  Level: " + level, 1, 17);
  } else if (state == 1) {
    fill(255);
    textFont(font, 48);
    text("Game Over", 1, 49);
    textFont(font, 16);
    text("Score: " + score, 15, 93);
    text("Level: " + level, 15, 110);
  } else {
    fill(255);
    textFont(font, 18);
    text("Click to start", 1, 20);
  }
}

void mousePressed() {
  if (state == 1) {
    reset();
    state = 0;
  } else if (state == 0) {
    state = 2;
  }
}

Tags: , , ,

Friday, July 31st, 2009 Programming, Web Design/Development Comments Off

This morning

I can’t think for a better title for this post. It was 9:34 in my place when this post was made.

I watched a useful YouTube video: Introduction to Algorithms: http://www.youtube.com/watch?v=JPyuH4qXLZ0

It is about algorithms, and it is 1:20:35 long, because MIT is a sponsor on YouTube.

My mark in school was increasing, I got B in English, and +5% in Social Studies.
In last French class, I was about to get 0 in my test. That test was marked by students, so my friend feels guilty about I’m getting 0, so he filled some answers and let me get 7, I did not cheat, my friend did it.

Tags: , ,

Saturday, May 2nd, 2009 Programming 3 Comments

Canvas Chat

Recently, I created a new PHP application, it is like a chat room, but you can send images instead of text.
To download the source code: http://www.mediafire.com/download.php?jgm2yn2qt5z.
To try it online, go to http://www.eeveeshq.com/canvaschat/, Kat uploaded it to her site, but that one is a custom build with integration to her website, so you need to register Eevee’s HQ to use it.
I originally posted in on Eevee’s HQ, and someone spammed it, at last, Nick said he did it.
asked Kat about it, In the next day, Kat said she was joking around.

YouTube is getting worse, big companies such as Disney were trying to make advertisement videos more visible on YouTube, YouTube is not for you, it is for THEM.

Let’s use Veoh.com if YouTube is getting bad.

Tags: ,

Friday, April 17th, 2009 Programming, Web Design/Development Comments Off

PHP Math Expression Parser

I created a math expression parser that evaluates math expressions and functions such as “variable = 42 * (34 + 52 – sqrt(variable + 3))”.

<?php
/**
 * A math expression parser, supports:
 * <ol>
 *  <li>add, subtract, multiply and division</li>
 *  <li>brackets grouping</li>
 *  <li>functions, custom functions' prefix can be customized.</li>
 *  <li>variables</li>
 * </ol>
 * Does not support:
 * <ol>
 *  <li>bitwise/logical operators.</li>
 *  <li>if/else/for statements</li>
 *  <li>arrays</li>
 * </ol>
 * Depends on BCMath library to gain arbitrary precision features.<br />
 * BNF Grammar (quotes strings and ALL CAPs are terminals):
 * <pre>
 * expression ::= ( IDENT '=' expression | addexpr )
 * addexpr ::= term ( ( '+' | '-' ) term )*
 * term ::= primary ( ( '*'|'/' ) primary )*
 * primary ::= ( NUMBER | IDENT | '(' expression ')' | IDENT ( '(' expression (',' expression)* ')' | '(' ')' ) )
 * </pre>
 * This class implements ArrayAccess, so <code>$expr['name']</code> will access variable <code>name</code>.
 * To evaluate an expression, use the <code>evaluate(string $expr)</code> method.
 * To set percision, use <code>bcscale(int $scale)</code> function.
 * Example usage: <code>$expr = new Expression(); $expr->evaluate('a = 12 + 5'); echo $expr['a']; //prints 17</code>
 * @author SpaceMan
 * @version 1.0
 */
class Expression implements ArrayAccess {
	private $tokens = array();
	private $expr = '';
	/**
	 * @var array List of all variables.
	 */
	public $variables = array();
	/**
	 * @var string Prefix of all custom functions, for example, <code>sqrt(10)</code> will map
	 * to <code>bcsqrt(10)</code> if the prefix is <code>bc</code>.
	 */
	public $prefix = 'bc';
	/**
	 * Constructs a new Expression object.
	 * @param string $expr The initial expression to be evaluated.
	 */
	public function __construct($expr = '') {
		$this->expr = $expr;
	}
	/**
	 * Evaluates an expression.
	 * @param string $expr The expression to be evaluated.
	 * @return string The result in string of numbers.
	 */
	public function evaluate($expr) {
		$this->expr = $expr;
		$this->tokenize();
		return $this->expression();
	}
	public function offsetSet($offset, $value) {
		$this->variables[$offset] = $value;
	}
	public function offsetExists($offset) {
		return isset($this->variables[$offset]);
	}
	public function offsetUnset($offset) {
		unset($this->variables[$offset]);
	}
	public function offsetGet($offset) {
		return isset($this->variables[$offset]) ? $this->variables[$offset] : null;
	}
	/**
	 * Tokenize the expression.
	 * @internal
	 */
	private function tokenize() {
		$expr = $this->expr;
		$i = 0;
		$c = " ";
		//while there are more string to be tokenized
		while ($c) {
			//exit if there are no more string.
			if ($i >= strlen($expr)) {
				return;
			}
			//the code to be scanned
			$c = substr($expr, $i);
			if (preg_match('/^\d+(\.\d+)?/', $c, $matches, PREG_OFFSET_CAPTURE)) {
				//numbers
				$i += strlen($matches[0][0]);
				array_push($this->tokens, array('NUMBER', $matches[0][0]));
			} else if (preg_match('/^[A-Za-z0-9_]+/', $c, $matches, PREG_OFFSET_CAPTURE)) {
				//variables
				$i += strlen($matches[0][0]);
				array_push($this->tokens, array('IDENT', $matches[0][0]));
			} else if ($c[0] == ' ' or $c[0] == '\t' or $c[0] == '\r' or $c[0] == '\n') {
				//whitespaces
				$i ++;
			} else {
				//operators
				array_push($this->tokens, array($expr[$i], $expr[$i]));
				$i ++;
			}
		}
	}
	/**
	 * Determines if the next token exists.
	 * This function takes multiple arguments.
	 * @internal
	 */
	private function has() {
		$t = $this->tokens[0];
		foreach (func_get_args() as $name) {
			if ($t[0] == $name) {
				return true;
			}
		}
		return false;
	}
	/**
	 * Consume the next token.
	 */
	private function token() {
		return array_shift($this->tokens);
	}
	/**
	 * Calls a function.
	 * @internal
	 */
	private function call($name, $values = array()) {
		return eval('return ' . $this->prefix . $name . '(' . implode(',', $values) . ');');
	}
	private function primary() {
		if ($this->has('NUMBER')) {
			//numbers
			$t = $this->token();
			return $t[1];
		} else if ($this->has('-')) {
			//negative numbers
			$this->token();
			return -$this->primary();
		} else if ($this->has('(')) {
			//brackets
			$this->token();
			$v = $this->expression();
			$this->token();
			return $v;
		} else if ($this->has('IDENT') && $this->tokens[1][0] == '(') {
			//function call
			$name = $this->token();
			$name = $name[1];
			$this->token();
			$args = array();
			if ($this->has(')')) {
				//zero arguments
				return $this->call($name);
			} else {
				//one or more arguments
				$args[] = $this->expression();
				while ($this->has(',')) {
					$this->token();
					$args[] = $args[] = $this->expression();
				}
				$v = $this->call($name, $args);
				$this->token();
				return $v;
			}
		} else if ($this->has('IDENT')) {
			//get variable
			$t = $this->token();
			return $this->variables[$t[1]] or '0';
		} else {
			throw new RuntimeException('Syntax error.');
		}
	}
	private function term() {
		$v = $this->primary();
		while ($this->has('*', '/')) {
			$op = $this->token();
			$right = $this->primary();
			switch ($op[0]) {
				case '*':
					$v = bcmul($v, $right);
					break;
				case '/':
					$v = bcdiv($v, $right);
					break;
				default:
					throw new RuntimeException('Invalid operator, expection "*" or "/".');
			}
		}
		return $v;
	}
	private function addexpr() {
		$v = $this->term();
		while ($this->has('+', '-')) {
			$op = $this->token();
			$right = $this->term();
			switch ($op[0]) {
				case '+':
					$v = bcadd($v, $right);
					break;
				case '-':
					$v = bcsub($v, $right);
					break;
				default:
					throw new RuntimeException('Invalid operator, expection "+" or "-".');
			}
		}
		return $v;
	}
	private function expression() {
		if ($this->has('IDENT') and $this->tokens[1][0] == '=') {
			$left = $this->token();
			$left = $left[1];
			$this->token();
			$right = $this->expression();
			$this->variables[$left] = $right;
			return $right;
		} else {
			return $this->addexpr();
		}
	}
}
?>

To use it, include that file and create a new Expression object, then call the evaluate($expr) to evaluate expressions.
An example usage:

<?php
include ('expression.php');
$expr = new Expression();
$expr['variablename'] = 12; //pre-define a variable inside the object
echo $expr->evaluate($_GET['expression']);
echo 'The variable variablename is: ' . $expr['variablename'];
//to set a prefix for custom functions
$expr->prefix = 'myfuncs_';
?>

Tags:

Sunday, April 12th, 2009 Programming, Web Design/Development Comments Off

Flex RSS Reader

I can’t afford Flash, so I used Adobe Flex.
My first working Flex program is an RSS reader, I hosted it on deviantART.
http://spacemanplusplus.deviantart.com/art/Adobe-Flex-RSS-Reader-118809423
Please don’t complain about hosting and sites, I chose to host on dA because my site has stupid file size limit.

I’m going to make something useful that can be added to your site next time.

Tags: ,

Friday, April 10th, 2009 Programming Comments Off

Basic Linux BASH Scripting Tutorial

If you are using Linux, are you frustrated about the terminal? You copied and pasted commands from other forums, and it may or may not work, however, it is important to learn how to script.

I’m not teaching you how to fix your computer, I’m teaching you the syntax of BASH.

A command will be some strings seperated by spaces such as this:

$ echo "Hello, world!"

(please note, don’t type the $ before the command, it just the input prompt sign like [user@host]$)

This will run the program echo with string Hello, world! (without the quotes) as the first command line argument. The quotes will not be part of the command line argument, because it is a token.

What is a command line argument? It is a string that passed to a program to tell the program what you are doing. The command echo takes infinity amount of arguments and print them separated by spaces.
You don’t need quotes to enclose command line arguments if they are words such as hello, but you may need to if it contains operators such as brackets, semicolons, or other signs.

You can define variables in BASH, the variable will disappear after the end of the session unless you export it. To define a variable, put an identifier on the left side and the value on the right side.

$ A="A string"

There should be no whitespaces between the equal sign.
After you defined a variable, you can reference it by using the dollar sign($).

$ echo $A

It outputs “A string”.
Double quotes can interpolate variables, it means you can use dollar sign in double-quoted strings like this:

$ echo "The value is $A"

If you want to display a literal dollar sign, use single quotes or escape sequence. The character for escape sequence is backslash().

$ echo "This is a dollar sign: $"

or

echo 'This is a dollar sign: $'

If you want to display backslash, simply use double backslash like “\” in double-quoted string or you don’t need it at all for “=” or other invalid escapes. You can use backslashes to escape unquoted strings such as spaces.

$ cd /media/AAA/Program Files/

There is a backslash before the space, so it will not be separated into 2 arguments.
By the way, the cd command changes the working directory.

Most commands has help feature, add a –help after command to see help message.
Some useful commands includes ls, grep and cat. ls and dir lists all entries in the working directory, cat prints content of a file(or from standard input), grep takes a regular expression and an optional filename, and it outputs lines that matches the regular expression.
An example use of grep will be like this:

$ grep 03-01 log.txt
2009-03-01 5:00: .............
2009-03-01 6:13: ...........
2009-03-01 9:12: ........

The first argument of grep is the regular expression of be matches, the second one is the filename of input file, it is optional, if there is no input file, the program will ask you to input and repeats your input if that string has a match. An example of grep session is like this:

$ grep file
ABCDEFG
filename
filename
test
a

To end the program, type Ctrl+D to insert End-of-File(EOF) or Ctrl+Z to terminate.
However, you can input and output from a different stream, that is called redirection.

$ php test.php | grep "1 + 1"
1 + 1 = 2
15 - 1 + 1 = 15
35 / 1 + 1 = 36 

That command may be hard to understand, that is where redirection is used.
To understand redirection, you must understand the input and output streams.
A program opens three streams, standard input (stdin), standard output (stdout), and standard error (stderr).
When standard input is being read, the terminal will pause and ask you to input, you can type something with keyboard and press Enter. Standard input is normally be read, not written.

Standard output and standard error are two output streams, when they are written, new text will be shown to the terminal. stderr is used for error messages, and some programs highlights stderr red or they ignore stderr completely.
Program can open/close other streams such as file streams.

You can see the diagram in http://en.wikipedia.org/wiki/File:Stdstreams-notitle.svg .

Let’s go back to that command:

$ php test.php | grep "1 + 1"

The output of command php will be redirected as input of command grep.

Redirection diagram

The < and > operator controls input and output with files. > operator redirects and appends output to a file. For example, the following command line appends output of command ls to filename output.txt.

$ ls > output.txt

The >> operator will overwrite the file.

$ ls >> output.txt

The < operator puts a file’s content as input of a file.
The following command finds all TODO from file test.c

$ grep TODO < test.c

Of course, you can use cat and vertical pipe.

$ cat test.c | grep TODO

Many C/C++ programmers do that to find TODO, FIXME, XXX markers.

I will put some more complex and useful command here.

$ cat file.js | js | grep 180 | less

First, it puts contents of file.js as input to command js (a JavaScript interpreter), then put the output of that command to grep 180, and put output of grep to command less (shows scrollable text viewer).

$ ls | grep .php

Find all .php files in the working directory.

cat file.txt | grep ERROR

Finds string ERROR in file file.txt and output it.

That’s all for today. For more info about redirection, visit http://en.wikipedia.org/wiki/Redirection_(computing).

Tags: , ,

Friday, April 10th, 2009 Programming, Tutorial Comments Off