diff --git a/CHANGELOG.md b/CHANGELOG.md
index 98386526b582faaaa5f112d3fbc56ea102f31b7d..5b5dd824f84c7a78b1c2f03a347778be06a9ebc9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,32 @@
 # REAL changelog
 
+## Version [0.5](https://git.ecdf.ed.ac.uk/pguaglia/real/commits/0.5) (2021-09-28)
+
+#### New features
+
+- In-app help on internal commands. Issuing the command `.help` at REAL's prompt
+  shows the list of available commands, along with their usage and description.
+- Atomic selection conditions now support less-than comparisons using the newly
+  introduced `<` operator. This compares strings lexicographically, but behaves
+  differently on strings that represent integer values. More precisely, if the
+  two strings being compared can be *both* parsed as integers, then they are
+  compared as integers; otherwise, lexicographic comparison is used. As a side
+  effect, we lose the ability to compare lexicographically strings that consist
+  only of digits. This will be rectified once proper types are introduced.
+
+#### Changed functionality
+
+- Renaming is now defined as an operation that only accepts one replacement, in
+  place of a list thereof. That possibility is retained as a syntactic shortcut,
+  which is parsed as sequence of nested renamings. For example,
+  `<R>[A->B,B->C](table)` is short for `<R>[B->C](<R>[A->B](table))`.
+
+#### API improvements
+
+- Get list of replacements in the renaming operation.
+- Getters for conditions and terms.
+- New abstract superclass for unary operations.
+
 ## Version [0.4](https://git.ecdf.ed.ac.uk/pguaglia/real/commits/0.4) (2020-09-08)
 
 #### New features
diff --git a/data/sample-db/Account.csv b/data/sample-db/Account.csv
deleted file mode 100644
index 355a3065b2cf13945ab555a9316b5dd698e10cdf..0000000000000000000000000000000000000000
--- a/data/sample-db/Account.csv
+++ /dev/null
@@ -1,4 +0,0 @@
-Number,Branch,CustID,Balance
-04132,Edinburgh,cust1,0
-59687,London,cust2,1000
-19283,Edinburgh,cust2,500
diff --git a/data/sample-db/Customer.csv b/data/sample-db/Customer.csv
deleted file mode 100644
index cbecb0c8951a1d42b1e09df087216b90152cb6b4..0000000000000000000000000000000000000000
--- a/data/sample-db/Customer.csv
+++ /dev/null
@@ -1,4 +0,0 @@
-ID,Name,City,Age
-cust1,Renton,Edinburgh,24
-cust2,Watson,London,32
-cust3,Holmes,London,35
diff --git a/pom.xml b/pom.xml
index 104840470e08ab95c055c5c7f0f48248811c51a9..a3dd27dc528bf0069ff1bb8a049a897d58f17b5a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
 
 	<groupId>uk.ac.ed.pguaglia</groupId>
 	<artifactId>real</artifactId>
-	<version>0.4</version>
+	<version>0.5</version>
 	<name>real</name>
 	<url>https://git.ecdf.ed.ac.uk/pguaglia/real</url>
 
@@ -62,6 +62,19 @@
 	</dependencies>
 
 	<build>
+		<resources>
+			<resource>
+				<directory>src/main/resources</directory>
+				<excludes>
+					<exclude>**/*.jar</exclude>
+				</excludes>
+				<includes>
+					<include>welcome.txt</include>
+					<include>help.txt</include>
+					<include>syntax.txt</include>
+				</includes>
+			</resource>
+		</resources>
 		<plugins>
 			<plugin>
 				<groupId>org.apache.maven.plugins</groupId>
diff --git a/src/main/antlr4/uk/ac/ed/pguaglia/real/parsing/RA.g4 b/src/main/antlr4/uk/ac/ed/pguaglia/real/parsing/RA.g4
index 884c56914d2012cc1dc482c53495cbc60c091a2f..ea2ced0804bb733646822d67c04e0b86f353a90c 100755
--- a/src/main/antlr4/uk/ac/ed/pguaglia/real/parsing/RA.g4
+++ b/src/main/antlr4/uk/ac/ed/pguaglia/real/parsing/RA.g4
@@ -22,11 +22,16 @@ NAME
 	)*
 ;
 
-CONSTANT
+STRING
 :
 	'\'' .+? '\''
 ;
 
+INTEGER
+:
+	DIGIT+
+;
+
 WHITESPACE
 :
 	[ \t\r\n]+ -> skip
@@ -102,7 +107,8 @@ attribute_list
 
 constant
 :
-	CONSTANT
+	STRING
+	| INTEGER
 ;
 
 term
@@ -131,6 +137,7 @@ condition
 	| condition AND condition # Conjunction
 	| condition OR condition # Disjunction
 	| term '=' term # Equality
+	| term '<' term # LessThan
 ;
 
 expression
diff --git a/src/main/java/uk/ac/ed/pguaglia/real/lang/BinaryCondition.java b/src/main/java/uk/ac/ed/pguaglia/real/lang/BinaryCondition.java
index 5b4a5a4eff255f6edd04fb37cf84b5ca7ea179c3..17406509105002cffb4be09985063e44f1d80825 100644
--- a/src/main/java/uk/ac/ed/pguaglia/real/lang/BinaryCondition.java
+++ b/src/main/java/uk/ac/ed/pguaglia/real/lang/BinaryCondition.java
@@ -13,6 +13,14 @@ public abstract class BinaryCondition extends Condition {
 		this.rightCondition = right;
 	}
 
+	public Condition getLeftCondition() {
+		return leftCondition;
+	}
+
+	public Condition getRightCondition() {
+		return rightCondition;
+	}
+
 	@Override
 	public String toString() {
 		return String.format( "%s %s %s",
diff --git a/src/main/java/uk/ac/ed/pguaglia/real/lang/BinaryOperation.java b/src/main/java/uk/ac/ed/pguaglia/real/lang/BinaryOperation.java
index 39fa5af7060972bbce8fd4a0f018c41ff85283bf..0729914186e4caa3c763b163bab040f15715856b 100644
--- a/src/main/java/uk/ac/ed/pguaglia/real/lang/BinaryOperation.java
+++ b/src/main/java/uk/ac/ed/pguaglia/real/lang/BinaryOperation.java
@@ -11,6 +11,13 @@ import uk.ac.ed.pguaglia.real.db.Table;
 public abstract class BinaryOperation extends Expression {
 
 	protected Expression leftOperand;
+	protected Expression rightOperand;
+
+	public BinaryOperation ( Expression left, Expression right, Expression.Type type ) {
+		super(type);
+		this.leftOperand = left;
+		this.rightOperand = right;
+	}
 
 	public Expression getLeftOperand() {
 		return leftOperand;
@@ -20,14 +27,6 @@ public abstract class BinaryOperation extends Expression {
 		return rightOperand;
 	}
 
-	protected Expression rightOperand;
-
-	public BinaryOperation ( Expression left, Expression right, Expression.Type type ) {
-		super(type);
-		this.leftOperand = left;
-		this.rightOperand = right;
-	}
-
 	@Override
 	public String toString() {
 		return String.format( "%s %s %s",
diff --git a/src/main/java/uk/ac/ed/pguaglia/real/lang/Distinct.java b/src/main/java/uk/ac/ed/pguaglia/real/lang/Distinct.java
index 2342cb43e2f3460f150933ac864791b00b47a06c..09437532ae42733eb704f6fa5f6971875beeef85 100644
--- a/src/main/java/uk/ac/ed/pguaglia/real/lang/Distinct.java
+++ b/src/main/java/uk/ac/ed/pguaglia/real/lang/Distinct.java
@@ -8,13 +8,10 @@ import uk.ac.ed.pguaglia.real.db.Schema;
 import uk.ac.ed.pguaglia.real.db.SchemaException;
 import uk.ac.ed.pguaglia.real.db.Table;
 
-public class Distinct extends Expression {
+public class Distinct extends UnaryOperation {
 
-	private Expression expr;
-
-	public Distinct(Expression expr) {
-		super(Type.DISTINCT);
-		this.expr = expr;
+	public Distinct(Expression operand) {
+		super(operand, Type.DISTINCT);
 	}
 
 	@Override
@@ -22,35 +19,35 @@ public class Distinct extends Expression {
 		String tree = "%1$s\n%2$s |\n%2$s +--- %3$s";
 		String conn = this.getType().getConnective();
 		String s;
-		if (expr.getType() == Expression.Type.BASE) {
-			s = expr.toSyntaxTreeString("", schema);
+		if (operand.getType() == Expression.Type.BASE) {
+			s = operand.toSyntaxTreeString("", schema);
 			s += "\n" + prefix;
 		} else {
-			s = expr.toSyntaxTreeString(prefix + "      ", schema);
+			s = operand.toSyntaxTreeString(prefix + "      ", schema);
 		}
 		return String.format(tree, conn, prefix, s);
 	}
 
 	@Override
 	public Set<String> signature(Schema schema) throws SchemaException {
-		return expr.signature(schema);
+		return operand.signature(schema);
 	}
 
 	@Override
 	public Table execute(Database db, boolean bags) throws DatabaseException {
 		signature(db.schema());
-		return bags ? expr.execute(db,true).distinct() : expr.execute(db,false);
+		return bags ? operand.execute(db,true).distinct() : operand.execute(db,false);
 	}
 
 	@Override
 	public String toString() {
 		return String.format( "%s( %s )",
 				this.getType().getConnective(),
-				expr.toString() );
+				operand.toString() );
 	}
 
 	@Override
 	public Set<String> getBaseNames() {
-		return expr.getBaseNames();
+		return operand.getBaseNames();
 	}
 }
diff --git a/src/main/java/uk/ac/ed/pguaglia/real/lang/Equality.java b/src/main/java/uk/ac/ed/pguaglia/real/lang/Equality.java
index 2e7214d1761255ddf587c2925e72070419e022ed..9e5049beb7349934971be3adcd4c6328b92cb256 100644
--- a/src/main/java/uk/ac/ed/pguaglia/real/lang/Equality.java
+++ b/src/main/java/uk/ac/ed/pguaglia/real/lang/Equality.java
@@ -15,6 +15,14 @@ public class Equality extends Condition {
 		this.right = right;
 	}
 
+	public Term getLeftTerm() {
+		return left;
+	}
+
+	public Term getRightTerm() {
+		return right;
+	}
+
 	@Override
 	public String toString() {
 		return String.format( "%s = %s",
diff --git a/src/main/java/uk/ac/ed/pguaglia/real/lang/LessThan.java b/src/main/java/uk/ac/ed/pguaglia/real/lang/LessThan.java
new file mode 100644
index 0000000000000000000000000000000000000000..3edbe0a32740f95b6954835fccd3f490df8e367b
--- /dev/null
+++ b/src/main/java/uk/ac/ed/pguaglia/real/lang/LessThan.java
@@ -0,0 +1,53 @@
+package uk.ac.ed.pguaglia.real.lang;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class LessThan extends Condition {
+
+	private Term left;
+	private Term right;
+
+	public LessThan(Term left, Term right) {
+		super(Condition.Type.COMPARISON);
+		this.left = left;
+		this.right = right;
+	}
+
+	public Term getLeftTerm() {
+		return left;
+	}
+
+	public Term getRightTerm() {
+		return right;
+	}
+
+	@Override
+	public String toString() {
+		return String.format("%s < %s", left.toString(), right.toString());
+	}
+
+	@Override
+	public Set<String> signature() {
+		var sign = new HashSet<String>();
+		if (left.isAttribute()) {
+			sign.add(left.getValue());
+		}
+		if (right.isAttribute()) {
+			sign.add(right.getValue());
+		}
+		return sign;
+	}
+
+	@Override
+	public boolean satisfied(String[] record, Map<String, Integer> attr) {
+		String v1 = left.getValue(record, attr);
+		String v2 = right.getValue(record, attr);
+		try {
+			return Integer.valueOf(v1) < Integer.valueOf(v2);
+		} catch (NumberFormatException e) {
+			return v1.compareTo(v2) < 0;			
+		}
+	}
+}
diff --git a/src/main/java/uk/ac/ed/pguaglia/real/lang/Negation.java b/src/main/java/uk/ac/ed/pguaglia/real/lang/Negation.java
index 2e0965abb66af6705e9aaf52a0cd0bc955ad7e92..0a2bc2c8b536174546e5cd4e50140131a1558e60 100644
--- a/src/main/java/uk/ac/ed/pguaglia/real/lang/Negation.java
+++ b/src/main/java/uk/ac/ed/pguaglia/real/lang/Negation.java
@@ -12,6 +12,10 @@ public class Negation extends Condition {
 		this.cond = c;
 	}
 
+	public Condition getCondition() {
+		return cond;
+	}
+
 	@Override
 	public String toString() {
 		return String.format("%s( %s )", getType().getConnective(), cond.toString());
diff --git a/src/main/java/uk/ac/ed/pguaglia/real/lang/Projection.java b/src/main/java/uk/ac/ed/pguaglia/real/lang/Projection.java
index e4b1b5113c296a45771ac4e210a8caa418146c59..ac1990e7d043d3bb65965d449cd5c3b5beeac732 100644
--- a/src/main/java/uk/ac/ed/pguaglia/real/lang/Projection.java
+++ b/src/main/java/uk/ac/ed/pguaglia/real/lang/Projection.java
@@ -11,14 +11,12 @@ import uk.ac.ed.pguaglia.real.db.SchemaException;
 import uk.ac.ed.pguaglia.real.db.Table;
 
 
-public class Projection extends Expression {
+public class Projection extends UnaryOperation {
 
-	private Expression operand;
 	private List<String> attributes;
 
 	public Projection ( List<String> attributes, Expression operand ) {
-		super(Expression.Type.PROJECTION);
-		this.operand = operand;
+		super(operand, Expression.Type.PROJECTION);
 		this.attributes = attributes;
 	}
 
diff --git a/src/main/java/uk/ac/ed/pguaglia/real/lang/Renaming.java b/src/main/java/uk/ac/ed/pguaglia/real/lang/Renaming.java
index 18e0e713bb8d2244eb41fe395eb2466cff187e1f..d95cb352d7126d58e05cc274a1057eb9346d7787 100644
--- a/src/main/java/uk/ac/ed/pguaglia/real/lang/Renaming.java
+++ b/src/main/java/uk/ac/ed/pguaglia/real/lang/Renaming.java
@@ -1,6 +1,7 @@
 package uk.ac.ed.pguaglia.real.lang;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -15,14 +16,12 @@ import uk.ac.ed.pguaglia.real.db.SchemaException;
 import uk.ac.ed.pguaglia.real.db.Table;
 
 
-public class Renaming extends Expression {
+public class Renaming extends UnaryOperation {
 
-	private Expression operand;
 	private Map<String,String> replacements;
 
 	public Renaming (Map<String,String> replacements, Expression operand) throws ReplacementException {
-		super(Expression.Type.RENAMING);
-		this.operand = operand;
+		super(operand,Expression.Type.RENAMING);
 		if (Utils.isInjective(replacements)) {
 			this.replacements = replacements;
 		} else {
@@ -87,4 +86,8 @@ public class Renaming extends Expression {
 	public Set<String> getBaseNames() {
 		return operand.getBaseNames();
 	}
+
+	public Map<String,String> getReplacements() {
+		return new HashMap<String, String>(replacements);
+	}
 }
diff --git a/src/main/java/uk/ac/ed/pguaglia/real/lang/Selection.java b/src/main/java/uk/ac/ed/pguaglia/real/lang/Selection.java
index 8954542d555f7e8d4facc26a17f56b4099f795e9..08a8f44b36ec4705cf38723e10da57131aa3404e 100644
--- a/src/main/java/uk/ac/ed/pguaglia/real/lang/Selection.java
+++ b/src/main/java/uk/ac/ed/pguaglia/real/lang/Selection.java
@@ -9,23 +9,25 @@ import uk.ac.ed.pguaglia.real.db.Schema;
 import uk.ac.ed.pguaglia.real.db.SchemaException;
 import uk.ac.ed.pguaglia.real.db.Table;
 
-public class Selection extends Expression {
+public class Selection extends UnaryOperation {
 
-	private Expression expr;
 	private Condition cond;
 
-	public Selection ( Condition cond, Expression expr ) {
-		super(Expression.Type.SELECTION);
-		this.expr = expr;
+	public Selection ( Condition cond, Expression operand ) {
+		super(operand,Expression.Type.SELECTION);
 		this.cond = cond;
 	}
 
+	public Condition getCondition() {
+		return cond;
+	}
+
 	@Override
 	public String toString() {
 		return String.format( "%s[%s]( %s )",
 				this.getType().getConnective(),
 				cond.toString(),
-				expr.toString() );
+				operand.toString() );
 	}
 
 	@Override
@@ -36,14 +38,14 @@ public class Selection extends Expression {
 				.replace("( ", "(")
 				.replace(" )", ")")
 				.replace(" = ", "=");
-		String s = Utils.toSyntaxTreeString(expr, schema, prefix, "      ");
+		String s = Utils.toSyntaxTreeString(operand, schema, prefix, "      ");
 		return String.format(tree, conn, cond, prefix, s);
 	}
 
 	@Override
 	public Set<String> signature(Schema schema) throws SchemaException {
 		Set<String> condAttrs = cond.signature();
-		Set<String> exprAttrs = Utils.clone(expr.signature(schema));
+		Set<String> exprAttrs = Utils.clone(operand.signature(schema));
 		if (exprAttrs.containsAll(condAttrs)) {
 			return exprAttrs;
 		} else {
@@ -56,11 +58,11 @@ public class Selection extends Expression {
 	@Override
 	public Table execute(Database db, boolean bags) throws DatabaseException {
 		signature(db.schema());
-		return expr.execute(db,bags).select(cond);
+		return operand.execute(db,bags).select(cond);
 	}
 
 	@Override
 	public Set<String> getBaseNames() {
-		return expr.getBaseNames();
+		return operand.getBaseNames();
 	}
 }
diff --git a/src/main/java/uk/ac/ed/pguaglia/real/lang/Term.java b/src/main/java/uk/ac/ed/pguaglia/real/lang/Term.java
index 1d82d544eb6fbec58eba2495be7558a7bd204441..358444b9c494a464fd068155db4d2d19259ea4ed 100644
--- a/src/main/java/uk/ac/ed/pguaglia/real/lang/Term.java
+++ b/src/main/java/uk/ac/ed/pguaglia/real/lang/Term.java
@@ -26,7 +26,8 @@ public class Term {
 
 	@Override
 	public String toString() {
-		return constant ? "'" + value + "'" : value;
+		// TODO: create proper type for integers and remove the regex
+		return constant && !value.matches("[0-9]+") ? "'" + value + "'" : value;
 	}
 
 	public String getValue(String[] record, Map<String,Integer> attr) {
diff --git a/src/main/java/uk/ac/ed/pguaglia/real/lang/UnaryOperation.java b/src/main/java/uk/ac/ed/pguaglia/real/lang/UnaryOperation.java
new file mode 100644
index 0000000000000000000000000000000000000000..53527baf651a48655adc7e6bb301a0f15777b766
--- /dev/null
+++ b/src/main/java/uk/ac/ed/pguaglia/real/lang/UnaryOperation.java
@@ -0,0 +1,15 @@
+package uk.ac.ed.pguaglia.real.lang;
+
+public abstract class UnaryOperation extends Expression {
+
+	protected Expression operand;
+
+	public UnaryOperation ( Expression operand, Expression.Type type ) {
+		super(type);
+		this.operand = operand;
+	}
+
+	public Expression getOperand() {
+		return this.operand;
+	}
+}
diff --git a/src/main/java/uk/ac/ed/pguaglia/real/parsing/RealListener.java b/src/main/java/uk/ac/ed/pguaglia/real/parsing/RealListener.java
index c6d0f68463e195ea171fada8c847e94e0a5a3e8b..a4347fc4b848bbd94266935bdedd54ed311834e5 100644
--- a/src/main/java/uk/ac/ed/pguaglia/real/parsing/RealListener.java
+++ b/src/main/java/uk/ac/ed/pguaglia/real/parsing/RealListener.java
@@ -3,7 +3,6 @@ package uk.ac.ed.pguaglia.real.parsing;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Stack;
 
 import uk.ac.ed.pguaglia.real.lang.BaseExpression;
@@ -15,6 +14,7 @@ import uk.ac.ed.pguaglia.real.lang.Distinct;
 import uk.ac.ed.pguaglia.real.lang.Equality;
 import uk.ac.ed.pguaglia.real.lang.Expression;
 import uk.ac.ed.pguaglia.real.lang.Intersection;
+import uk.ac.ed.pguaglia.real.lang.LessThan;
 import uk.ac.ed.pguaglia.real.lang.Negation;
 import uk.ac.ed.pguaglia.real.lang.Product;
 import uk.ac.ed.pguaglia.real.lang.Projection;
@@ -26,14 +26,24 @@ import uk.ac.ed.pguaglia.real.lang.Union;
 
 public class RealListener extends RABaseListener {
 
+	private class MapEntry<K,V> {
+		K key;
+		V val;
+
+		public MapEntry(K key, V val) {
+			this.key = key;
+			this.val = val;
+		}
+	}
+
 	private Stack<Expression> exprStack = new Stack<Expression>();
 	private List<String> attrList = null;
 	private Stack<List<String>> attrListStack = new Stack<List<String>>();
 	private Stack<Term> termStack = new Stack<Term>();
 	private Stack<Condition> condStack = new Stack<Condition>();
 	private Stack<String> attrStack = new Stack<String>();
-	private Map<String,String> replMap = null;
-	private Stack<Map<String,String>> replMapStack = new Stack<Map<String,String>>();
+	private List<MapEntry<String,String>> replMap = null;
+	private Stack<List<MapEntry<String,String>>> replMapStack = new Stack<>();
 	private Expression parsedExpression = null;
 
 	public Expression parsedExpression() {
@@ -97,7 +107,7 @@ public class RealListener extends RABaseListener {
 
 	@Override
 	public void enterReplacementList(RAParser.ReplacementListContext ctx) {
-		replMap = new HashMap<String,String>();
+		replMap = new ArrayList<MapEntry<String,String>>();
 	}
 
 	@Override
@@ -136,6 +146,13 @@ public class RealListener extends RABaseListener {
 		condStack.push(new Equality(left, right));
 	}
 
+	@Override
+	public void exitLessThan(RAParser.LessThanContext ctx) {
+		Term right = termStack.pop();
+		Term left = termStack.pop();
+		condStack.push(new LessThan(left, right));
+	}
+
 	@Override
 	public void exitConjunction(RAParser.ConjunctionContext ctx) {
 		Condition right = condStack.pop();
@@ -167,20 +184,22 @@ public class RealListener extends RABaseListener {
 	public void exitReplacement(RAParser.ReplacementContext ctx) {
 		String v = attrStack.pop();
 		String k = attrStack.pop();
-		if (replMap.containsKey(k)) {
-			throw new RuntimeException(ctx.getText(), ReplacementException.renamedMoreThanOnce(k));
-		}
-		replMap.put(k, v);
+		replMap.add(new MapEntry<>(k,v));
 	}
 
 	@Override
 	public void exitRenaming(RAParser.RenamingContext ctx) {
-		Map<String,String> repl = replMapStack.pop();
-		Expression expr = exprStack.pop();
-		try {
-			exprStack.push(new Renaming(repl, expr));
-		} catch (ReplacementException e) {
-			throw new RuntimeException(ctx.getText(), e);
+		var repl = replMapStack.pop();
+		var expr = exprStack.pop();
+		for (var entry : repl) {
+			try {
+				HashMap<String, String> map = new HashMap<>();
+				map.put(entry.key, entry.val);
+				expr = new Renaming(map, expr);
+			} catch (ReplacementException e) {
+				throw new RuntimeException(ctx.getText(), e);
+			}
 		}
+		exprStack.push(expr);
 	}
 }
diff --git a/src/main/java/uk/ac/ed/pguaglia/real/runtime/CommandLineApp.java b/src/main/java/uk/ac/ed/pguaglia/real/runtime/CommandLineApp.java
index a7882dd16e0e61c79bef7baf7607af1db2bb38e0..92d5d2c9154833986df176f8f7d18173e7e732d6 100644
--- a/src/main/java/uk/ac/ed/pguaglia/real/runtime/CommandLineApp.java
+++ b/src/main/java/uk/ac/ed/pguaglia/real/runtime/CommandLineApp.java
@@ -2,6 +2,7 @@ package uk.ac.ed.pguaglia.real.runtime;
 
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
@@ -36,7 +37,18 @@ public final class CommandLineApp {
 	private enum Eval { OFF, SET, BAG }
 
 	private enum Commands {
-		QUIT, SCHEMA, TREE, EVAL, DROP, SAVE, LOAD, ADD, TABLES, VIEWS;
+		HELP,   // show help for commands
+		QUIT,   // exit the application
+		TREE,   // show the parse tree [on/off]
+		EVAL,   // set evaluation semantics [set/bag/off]
+		DROP,   // delete table or view
+		SAVE,   // save current database
+		LOAD,   // load database from file
+		ADD,    // create new base table
+		TABLES, // list tables
+		VIEWS,  // list views
+		SCHEMA, // print the JSON schema file (private)
+		SYNTAX, // show accepted RA syntax
 	}
 
 	private static final String APP_COMMAND = "java -jar <path/to/real-X.Y.Z.jar>";
@@ -84,6 +96,23 @@ public final class CommandLineApp {
 			System.exit(-1);
 		}
 
+		// load welcome, help and syntax messages from file
+		String welcome = null;
+		String help = null;
+		String syntax = null;
+		try {
+			InputStream input = CommandLineApp.class.getResourceAsStream("/welcome.txt");
+			welcome = new String(input.readAllBytes());
+			input = CommandLineApp.class.getResourceAsStream("/help.txt");
+			help = new String(input.readAllBytes());
+			input = CommandLineApp.class.getResourceAsStream("/syntax.txt");
+			syntax = new String(input.readAllBytes());
+			input.close();
+		} catch (IOException e3) {
+			// TODO Auto-generated catch block
+			e3.printStackTrace();
+		}
+
 		Property<Tree> parseTree = new Property<>(
 				Commands.TREE.toString().toLowerCase(),
 				Tree.class,
@@ -129,6 +158,7 @@ public final class CommandLineApp {
 				.build();
 		String prompt = "$> ";
 
+		System.out.println(welcome);
 		String viewName = null;
 
 		mainLoop: do {
@@ -149,7 +179,8 @@ public final class CommandLineApp {
 					try {
 						cmd = Commands.valueOf(cmdName.toUpperCase());
 					} catch (IllegalArgumentException e) {
-						System.err.println(String.format("Unrecognized command \".%s\"", cmdName));
+						System.err.println(String.format("Unrecognized command \".%s\"\n"
+								+ "Type \".help\" to print the list of available commands.", cmdName));
 						continue mainLoop;
 					}
 					cmdSwitch: switch (cmd) {
@@ -158,6 +189,19 @@ public final class CommandLineApp {
 							System.err.println(String.format("WARNING: ignoring \"%s\"", line));
 						}
 						break mainLoop;
+					case HELP:
+						if (line.isBlank() == false) {
+							System.err.println(String.format("WARNING: ignoring \"%s\"", line));
+						}
+						System.out.println(); // print empty line
+						System.out.println(help);
+						continue mainLoop;
+					case SYNTAX:
+						if (line.isBlank() == false) {
+							System.err.println(String.format("WARNING: ignoring \"%s\"", line));
+						}
+						System.out.println(syntax);
+						continue mainLoop;
 					case TABLES:
 						if (line.isBlank() == false) {
 							System.err.println(String.format("WARNING: ignoring \"%s\"", line));
diff --git a/src/main/resources/help.txt b/src/main/resources/help.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d4bf98e781e78971e62a4c5aedf2af58ccf54e5c
--- /dev/null
+++ b/src/main/resources/help.txt
@@ -0,0 +1,38 @@
+  .syntax                Show the Relational Algebra grammar accepted by REAL.
+
+  .tables                List names and attributes of the base tables in the
+                         current database.
+
+  .views                 List names and definitions of the views in the current
+                         database.
+
+  .help                  Show this help message.
+
+  .quit                  Exit REAL's command prompt and go back to the shell
+  			 (unsaved changes are discarded).
+
+  .tree [on|off]         Turn parse tree on/off [default: off].
+                         With no arguments, the current setting is shown.
+
+  .eval [set|bag|off]    choose query evaluation semantics:
+                         - 'set': set semantics (default)
+                         - 'bag': bag semantics
+                         - 'off': no evaluation
+                         With no arguments, the current setting is shown.
+
+  .add TABLE_NAME(ATTR_LIST) : DATA_FILE
+                         Create a new base table:
+                         - TABLE_NAME is the name of the table;
+                         - ATTR_LIST is a comma-separated list of attribute names;
+                         - DATA_FILE is the path to a CSV file containing the
+                           instance (rows) of the table.
+
+  .drop TABLE_NAME       Delete table or view TABLE_NAME from the schema.
+
+  .load PATH             Load the database from the JSON file specified by PATH.
+
+  .save [PATH]           Save the current database schema to the JSON file
+                         specified by PATH. Without PATH, the current schema
+			 file is used, which is the latest file explicitly
+			 loaded with .load, or the temporary file automatically
+			 created when starting the application.
diff --git a/src/main/resources/syntax.txt b/src/main/resources/syntax.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b01e85fdb628430cb6ea3367978e990c44c08ab3
--- /dev/null
+++ b/src/main/resources/syntax.txt
@@ -0,0 +1,70 @@
+┌──────────────────────────────────────────────────────────────────────────────┐
+│ RELATIONAL ALGEBRA GRAMMAR                                                   │
+└──────────────────────────────────────────────────────────────────────────────┘
+
+  TABLE_NAME  →  case-sensitive alphanumeric string that:
+                   ‣ starts with a letter
+                   ‣ may contain the underscore character "_"
+                   ‣ does not contain spaces of any kind
+              
+  EXPRESSION  →  TABLE_NAME                         # base table (or view)
+              →  <S>[ CONDITION ]( EXPRESSION )     # selection
+              →  <P>[ ATTR_LIST ]( EXPRESSION )     # projection
+              →  <R>[ REPL_LIST ]( EXPRESSION )     # renaming
+              →  EXPRESSION BINARY_OP EXPRESSION    # binary operation
+              →  ( EXPRESSION )                     # parenthesized expression
+              
+  BINARY_OP   →  <X>    # cross product
+              →  <U>    # union
+              →  <D>    # difference
+              →  <I>    # intersection
+              
+  ATTR_NAME   →  same rules as for table names
+              
+  STRING      →  anything enclosed in single quotes
+
+  INTEGER     →  non-empty sequence of digits
+
+  CONSTANT    →  STRING
+              →  INTEGER
+
+  TERM        →  ATTR_NAME
+              →  CONSTANT
+
+  ATTR_LIST   →  comma-separated list of ATTR_NAMEs
+
+  REPLACEMENT →  ATTR_NAME -> ATTR_NAME
+
+  REPL_LIST   →  comma-separated list of REPLACEMENTs
+
+  CONDITION   →  TERM = TERM              # equality comparison
+              →  CONDITION & CONDITION    # conjunction
+              →  CONDITION | CONDITION    # disjunction
+              →  ~ CONDITION              # negation
+              →  ( CONDITION )            # parenthesized condition
+
+┌──────────────────────────────────────────────────────────────────────────────┐
+│ UNARY OPERATIONS: USAGE OF PROJECTION, SELECTION, RENAMING                   │
+└──────────────────────────────────────────────────────────────────────────────┘
+
+  <S>    selection
+
+         Usage: <S>[selection-condition](EXPRESSION)
+
+         Example: <S>[A=B](R)
+           ‣ selects the rows of R where A=B
+
+  <P>    projection
+
+         Usage: <P>[comma-separated-list-of-attributes](EXPRESSION)
+
+         Example: <P>[A,B](R)
+           ‣ projects columns A and B from R
+
+  <R>    renaming
+
+         Usage: <R>[comma-separated-list-of-replacements](EXPRESSION)
+
+         Example: <R>[A->B,B->C](R<U>S)
+           ‣ first renames A to B, then B to C, in the union of R and S
+           ‣ equivalent to <R>[B->C](<R>[A->B](R<U>S))
diff --git a/src/main/resources/welcome.txt b/src/main/resources/welcome.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9f905e3fb2a11640d641a9d8dae57689f8ad4fa9
--- /dev/null
+++ b/src/main/resources/welcome.txt
@@ -0,0 +1,5 @@
+┌──────────────────────────────────────────────────────────────────────────────┐
+│             REAL: an interpreter for Relational Algebra (v0.5)               │
+│                        Released under the MIT License                        │
+│                    Copyright © 2019-2021 Paolo Guagliardo                    │
+└──────────────────────────────────────────────────────────────────────────────┘