1
0

Add java code and test cases

This commit is contained in:
2018-04-05 20:03:37 +02:00
parent d5f1b9a23a
commit c00decd11b
36 changed files with 2889 additions and 0 deletions

23
compiler/pom.xml Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>antlr-compiler</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>compiler</artifactId>
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>parser</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,15 @@
package de.letsbuildacompiler.compiler;
public enum DataType {
INT("I"), STRING("Ljava/lang/String;");
public final String jvmType;
DataType(String jvmType) {
this.jvmType = jvmType;
}
public String getJvmType() {
return jvmType;
}
}

View File

@ -0,0 +1,31 @@
package de.letsbuildacompiler.compiler;
import de.letsbuildacompiler.compiler.exceptions.FunctionAlreadyDefinedException;
import de.letsbuildacompiler.parser.DemoBaseVisitor;
import de.letsbuildacompiler.parser.DemoParser;
import org.antlr.v4.runtime.tree.ParseTree;
import java.util.HashSet;
import java.util.Set;
public class FunctionDefinitionFinder {
public static FunctionList findFunctions(ParseTree tree) {
final FunctionList definedFunctions = new FunctionList();
new DemoBaseVisitor<Void>() {
@Override
public Void visitFunctionDefinition(DemoParser.FunctionDefinitionContext ctx) {
String functionName = ctx.funcName.getText();
int numberOfArguments = ctx.params.declarations.size();
if (definedFunctions.contains(functionName, numberOfArguments)) {
throw new FunctionAlreadyDefinedException(ctx.funcName);
}
definedFunctions.add(functionName, numberOfArguments);
return null;
}
}.visit(tree);
return definedFunctions;
}
}

View File

@ -0,0 +1,36 @@
package de.letsbuildacompiler.compiler;
import java.util.ArrayList;
import java.util.Collection;
public class FunctionList {
private Collection<FunctionDefinition> definitions;
FunctionList() {
this.definitions = new ArrayList<>();
}
public boolean contains(String functionName, int parameterCount) {
for (FunctionDefinition definition : definitions) {
if (definition.functionName.equals(functionName) && definition.parameterCount == parameterCount) {
return true;
}
}
return false;
}
public void add (String functionName, int parameterCount) {
definitions.add(new FunctionDefinition(functionName, parameterCount));
}
private static final class FunctionDefinition {
private final String functionName;
private final int parameterCount;
private FunctionDefinition(String functionName, int parameterCount) {
this.functionName = functionName;
this.parameterCount = parameterCount;
}
}
}

View File

@ -0,0 +1,304 @@
package de.letsbuildacompiler.compiler;
import de.letsbuildacompiler.compiler.exceptions.UndeclaredVariableException;
import de.letsbuildacompiler.compiler.exceptions.UndefinedFunctionException;
import de.letsbuildacompiler.compiler.exceptions.VariableAlreadyDefinedException;
import de.letsbuildacompiler.parser.DemoBaseVisitor;
import de.letsbuildacompiler.parser.DemoParser;
import de.letsbuildacompiler.parser.DemoParser.MainStatementContext;
import de.letsbuildacompiler.parser.DemoParser.NumberContext;
import de.letsbuildacompiler.parser.DemoParser.PlusContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import java.util.HashMap;
import java.util.Map;
public class JasminVisitor extends DemoBaseVisitor<String> {
// Symbol table
private Map<String, Integer> variables = new HashMap<>();
private JvmStack jvmStack = new JvmStack();
private final FunctionList definedFunctions;
private int branchCounter = 0;
private int compareCount = 0;
private int andCounter = 0;
private int orCounter = 0;
JasminVisitor(FunctionList definedFunctions) {
if (definedFunctions == null) {
throw new NullPointerException("definedFunctions");
}
this.definedFunctions = definedFunctions;
}
@Override
public String visitNumber(NumberContext ctx) {
jvmStack.push(DataType.INT);
return "ldc " + ctx.number.getText();
}
@Override
public String visitString(DemoParser.StringContext ctx) {
jvmStack.push(DataType.STRING);
return "ldc " + ctx.txt.getText();
}
private String visitBinaryOperation(String name, DemoParser.ExpressionContext ctx) {
String instructions = visitChildren(ctx) + "\n" + name;
jvmStack.pop();
jvmStack.pop();
jvmStack.push(DataType.INT);
return instructions;
}
@Override
public String visitPlus(PlusContext ctx) {
return visitBinaryOperation("iadd", ctx);
}
@Override
public String visitMinus(DemoParser.MinusContext ctx) {
return visitBinaryOperation("isub", ctx);
}
@Override
public String visitDiv(DemoParser.DivContext ctx) {
return visitBinaryOperation("idiv", ctx);
}
@Override
public String visitMult(DemoParser.MultContext ctx) {
return visitBinaryOperation("imul", ctx);
}
@Override
public String visitRelational(DemoParser.RelationalContext ctx) {
int compareNum = compareCount++;
String jumpInstruction;
switch (ctx.operator.getText()) {
case "<":
jumpInstruction = "if_icmplt";
break;
case "<=":
jumpInstruction = "if_icmple";
break;
case ">":
jumpInstruction = "if_icmpgt";
break;
case ">=":
jumpInstruction = "if_icmpge";
break;
default:
throw new IllegalArgumentException("Unknown operator : <" + ctx.operator.getText() + ">");
}
final String instructions = visitChildren(ctx) + "\n" +
jumpInstruction + " onTrue" + compareNum + "\n" +
"ldc 0\n" +
"goto onFalse" + compareNum + "\n" +
"onTrue" + compareNum + ":\n" +
"ldc 1\n" +
"onFalse" + compareNum + ":";
jvmStack.pop();
jvmStack.pop();
jvmStack.push(DataType.INT);
return instructions;
}
@Override
public String visitAnd(DemoParser.AndContext ctx) {
String left = visit(ctx.left);
String right = visit(ctx.right);
int andNum = andCounter++;
jvmStack.pop();
jvmStack.pop();
jvmStack.push(DataType.INT);
return left + "\n" +
"ifeq onAndFalse" + andNum + "\n" +
right + "\n" +
"ifeq onAndFalse" + andNum + "\n" +
"ldc 1\n" +
"goto andEnd" + andNum + "\n" +
"onAndFalse" + andNum + ":\n" +
"ldc 0\n" +
"andEnd" + andNum + ":";
}
@Override
public String visitOr(DemoParser.OrContext ctx) {
String left = visit(ctx.left);
String right = visit(ctx.right);
int orNum = orCounter++;
jvmStack.pop();
jvmStack.pop();
jvmStack.push(DataType.INT);
return left + "\n" +
"ifne onOrTrue" + orNum + "\n" +
right + "\n" +
"ifne onOrTrue" + orNum + "\n" +
"ldc 0\n" +
"goto orEnd" + orNum + "\n" +
"onOrTrue" + orNum + ":\n" +
"ldc 1\n" +
"orEnd" + orNum + ":";
}
@Override
public String visitPrintln(DemoParser.PrintlnContext ctx) {
String arg = visit(ctx.argument);
DataType type = jvmStack.pop();
return "getstatic java/lang/System/out Ljava/io/PrintStream;\n" +
arg + "\n" +
"invokevirtual java/io/PrintStream/println(" + type.getJvmType() + ")V\n";
}
@Override
public String visitPrint(DemoParser.PrintContext ctx) {
String arg = visit(ctx.argument);
DataType type = jvmStack.pop();
return "getstatic java/lang/System/out Ljava/io/PrintStream;\n" +
arg + "\n" +
"invokevirtual java/io/PrintStream/print(" + type.getJvmType() + ")V\n";
}
@Override
public String visitVariable(DemoParser.VariableContext ctx) {
jvmStack.push(DataType.INT);
return "iload " + requireVariableIndex(ctx.varName);
}
private int requireVariableIndex(Token varName) {
Integer varIndex = variables.get(varName.getText());
if (varIndex == null) {
throw new UndeclaredVariableException(varName);
}
return varIndex;
}
@Override
public String visitVarDeclaration(DemoParser.VarDeclarationContext ctx) {
String key = ctx.varName.getText();
if (variables.containsKey(key)) {
throw new VariableAlreadyDefinedException(ctx.varName);
}
variables.put(key, variables.size());
return "";
}
@Override
public String visitBranch(DemoParser.BranchContext ctx) {
String condition = visit(ctx.condition);
jvmStack.pop();
String onTrue = visit(ctx.onTrue);
String onFalse = visit(ctx.onFalse);
int branchNum = branchCounter++;
return condition + "\n" +
"ifne ifTrue" + branchNum + "\n" +
onFalse + "\n" +
"goto endIf" + branchNum + "\n" +
"ifTrue" + branchNum + ":\n" +
onTrue + "\n" +
"endIf" + branchNum + ":\n";
}
@Override
public String visitAssignment(DemoParser.AssignmentContext ctx) {
final String instructions = visit(ctx.expr) + "\n"
+ "istore " + requireVariableIndex(ctx.varName) + "\n";
jvmStack.pop();
return instructions;
}
@Override
public String visitFunctionDefinition(DemoParser.FunctionDefinitionContext ctx) {
Map<String, Integer> oldVars = this.variables;
JvmStack oldJvmStack = jvmStack;
jvmStack = new JvmStack();
variables = new HashMap<>();
visit(ctx.params);
String statements = visit(ctx.statements);
String instructions = ".method public static " + ctx.funcName.getText() + "(";
int numberOfParams = ctx.params.declarations.size();
instructions += stringRepeat("I", numberOfParams);
instructions += ")I\n" +
".limit locals 100\n" +
".limit stack 100\n" +
(statements == null ? "" : statements + "\n") +
visit(ctx.retValue) + "\n" +
"ireturn\n" +
".end method\n";
jvmStack.pop();
variables = oldVars;
jvmStack = oldJvmStack;
return instructions;
}
private String stringRepeat(String string, int numberOfParams) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < numberOfParams; i++) {
result.append(string);
}
return result.toString();
}
@Override
public String visitFunctionCall(DemoParser.FunctionCallContext ctx) {
if (!definedFunctions.contains(ctx.funcName.getText(), ctx.args.expressions.size() )) {
throw new UndefinedFunctionException(ctx.funcName);
}
String instructions = "";
String args = visit(ctx.args);
if (args != null) {
instructions += args + "\n";
}
instructions += "invokestatic HelloWorld/" + ctx.funcName.getText() + "(";
int numberOfParams = ctx.args.expressions.size();
instructions += stringRepeat("I", numberOfParams);
instructions +=")I\n";
for (int i = 0; i < numberOfParams; i++) {
jvmStack.pop();
}
jvmStack.push(DataType.INT);
return instructions;
}
@Override
public String visitProgram(DemoParser.ProgramContext ctx) {
StringBuilder mainCode = new StringBuilder();
StringBuilder functions = new StringBuilder();
for (ParseTree child : ctx.children) {
final String instructions = visit(child);
if (child instanceof MainStatementContext) {
mainCode.append(instructions);
} else {
functions.append(instructions);
}
}
final String instructions = ".method public static main([Ljava/lang/String;)V\n" +
".limit stack 100\n" +
".limit locals 100\n" +
mainCode + "\n" +
"return\n" +
".end method\n";
return functions.append(instructions).toString();
}
@Override
protected String aggregateResult(String aggregate, String nextResult) {
if (aggregate == null) {
return nextResult;
}
if (nextResult == null) {
return aggregate;
}
return aggregate + "\n" + nextResult;
}
}

View File

@ -0,0 +1,18 @@
package de.letsbuildacompiler.compiler;
import java.util.Deque;
import java.util.LinkedList;
public class JvmStack {
private Deque<DataType> typesOnStack = new LinkedList<>();
public void push(DataType type) {
typesOnStack.push(type);
}
public DataType pop() {
return typesOnStack.pop();
}
}

View File

@ -0,0 +1,33 @@
package de.letsbuildacompiler.compiler;
import de.letsbuildacompiler.parser.DemoLexer;
import de.letsbuildacompiler.parser.DemoParser;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
public class Main {
public static void main(String[] args) {
CodePointCharStream input = CharStreams.fromString("print(2 < 2);");
System.out.println(compile(input));
}
public static String compile(CharStream input) {
DemoLexer lexer = new DemoLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
DemoParser parser = new DemoParser(tokens);
ParseTree tree = parser.program();
FunctionList definedFunctions = FunctionDefinitionFinder.findFunctions(tree);
return createJasminFile(new JasminVisitor(definedFunctions).visit(tree));
}
private static String createJasminFile(String instructions) {
return ".class public HelloWorld\n" +
".super java/lang/Object\n" +
"\n" +
instructions;
}
}

View File

@ -0,0 +1,13 @@
package de.letsbuildacompiler.compiler.exceptions;
import org.antlr.v4.runtime.Token;
class CompileException extends RuntimeException {
int line;
int col;
CompileException(Token token) {
line = token.getLine();
col = token.getCharPositionInLine();
}
}

View File

@ -0,0 +1,18 @@
package de.letsbuildacompiler.compiler.exceptions;
import org.antlr.v4.runtime.Token;
public class FunctionAlreadyDefinedException extends CompileException{
private String funcName;
public FunctionAlreadyDefinedException(Token token) {
super(token);
funcName = token.getText();
}
@Override
public String getMessage() {
return line + ":" + col + " function already defined : <" + funcName + ">";
}
}

View File

@ -0,0 +1,20 @@
package de.letsbuildacompiler.compiler.exceptions;
import org.antlr.v4.runtime.Token;
public class UndeclaredVariableException extends CompileException {
private final String varName;
public UndeclaredVariableException(Token token) {
super(token);
varName = token.getText();
}
@Override
public String getMessage() {
return line + ":" + col + " undeclared variable <" + varName + ">";
}
}

View File

@ -0,0 +1,18 @@
package de.letsbuildacompiler.compiler.exceptions;
import org.antlr.v4.runtime.Token;
public class UndefinedFunctionException extends CompileException {
private String funcName;
public UndefinedFunctionException(Token token) {
super(token);
funcName = token.getText();
}
@Override
public String getMessage() {
return line + ":" + col + " call to undefined function : <" + funcName + ">";
}
}

View File

@ -0,0 +1,18 @@
package de.letsbuildacompiler.compiler.exceptions;
import org.antlr.v4.runtime.Token;
public class VariableAlreadyDefinedException extends CompileException {
private String varName;
public VariableAlreadyDefinedException(Token token) {
super(token);
varName = token.getText();
}
@Override
public String getMessage() {
return line + ":" + col + " variable <" + varName + "> already defined";
}
}

View File

@ -0,0 +1,173 @@
package de.letsbuildacompiler.compiler;
import de.letsbuildacompiler.compiler.exceptions.FunctionAlreadyDefinedException;
import de.letsbuildacompiler.compiler.exceptions.UndeclaredVariableException;
import de.letsbuildacompiler.compiler.exceptions.UndefinedFunctionException;
import de.letsbuildacompiler.compiler.exceptions.VariableAlreadyDefinedException;
import jasmin.ClassFile;
import org.antlr.v4.runtime.CharStreams;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Scanner;
import static org.testng.AssertJUnit.assertEquals;
public class CompilerTest {
private Path tempDir;
@BeforeMethod
public void createTempDir() throws IOException {
tempDir = Files.createTempDirectory("compilerTest");
}
@AfterMethod
public void deleteTempDir() {
deleteRecursive(tempDir.toFile());
}
private void deleteRecursive(File file) {
if (file.isDirectory()) {
for (File f : Objects.requireNonNull(file.listFiles())) {
deleteRecursive(f);
}
}
if (!file.delete()) {
throw new Error("Could not delete file <" + file + ">");
}
}
@Test(dataProvider = "provider")
@SuppressWarnings("unused")
public void runningCode_outputsExpectedText(String description, String code, String expectedText) throws Exception {
String actual = compileAndRun(code);
assertEquals(expectedText, actual);
}
@DataProvider(name = "provider")
public Object[][] provide_code_expectedText() {
return new Object[][] {
{"plus", "println(1+2);", "3" + System.lineSeparator()},
{"chained plus", "println(1+4+42);", "47" + System.lineSeparator()},
{"multiple statements", "println(1); println(2);", "1" + System.lineSeparator() + "2" + System.lineSeparator()},
{"minus", "println(3-2);", "1" + System.lineSeparator()},
{"times", "println(3*3);", "9" + System.lineSeparator()},
{"division", "println(6/3);", "2" + System.lineSeparator()},
{"truncated division", "println(7/2);", "3" + System.lineSeparator()},
{"operator precedence times and divide", "println(8/2*4);", "16" + System.lineSeparator()},
{"operator precedence times and plus", "println(2+3*3);", "11" + System.lineSeparator()},
{"operator precedence times and minus", "println(9-2*3);", "3" + System.lineSeparator()},
{"operator precedence minus and plus", "println(8-2+5);", "11" + System.lineSeparator()},
{"int variable", "int foo; foo = 42; println(foo);", "42" + System.lineSeparator()},
{"add variable and constant parameters",
"int foo; foo = 42; println(foo+2);", "44" + System.lineSeparator()},
{"add two variables parameters",
"int foo; int bar; foo = 42; bar = 43; println(foo+bar);", "85" + System.lineSeparator()},
example("functions/simple_function", "4" + System.lineSeparator()),
example("functions/scopes", "4" + System.lineSeparator() + "42" + System.lineSeparator()),
example("functions/int_parameters", "13" + System.lineSeparator()),
example("branch/if_int_false", "42" + System.lineSeparator()),
example("branch/if_int_true", "81" + System.lineSeparator()),
{"lower than true", "println(1 < 2);", "1" + System.lineSeparator()},
{"lower than false", "println(2 < 2);", "0" + System.lineSeparator()},
{"lower or equal true", "println(1 <= 1);", "1" + System.lineSeparator()},
{"lower or equal false", "println(3 <= 2);", "0" + System.lineSeparator()},
{"greater than true", "println(3 > 2);", "1" + System.lineSeparator()},
{"greater than false", "println(3 > 3);", "0" + System.lineSeparator()},
{"greater or equal true", "println(3 >= 3);", "1" + System.lineSeparator()},
{"greater or equal false", "println(2 >= 3);", "0" + System.lineSeparator()},
{"and true", "println(1 && 1);", "1" + System.lineSeparator()},
{"and left false", "println(0 && 1);", "0" + System.lineSeparator()},
{"and right false", "println(1 && 0);", "0" + System.lineSeparator()},
example("operators/and-skip-right", "0" + System.lineSeparator() + "0" + System.lineSeparator()),
{"or false", "println(0 || 0);", "0" + System.lineSeparator()},
{"or left true", "println(1 || 0);", "1" + System.lineSeparator()},
{"or right true", "println(0 || 1);", "1" + System.lineSeparator()},
example("operators/or-skip-right", "1" + System.lineSeparator() + "1" + System.lineSeparator()),
{"print", "print(42);", "42"},
{"print string litteral", "print(\"Hello world\");", "Hello world"},
};
}
@Test(expectedExceptions = UndeclaredVariableException.class,
expectedExceptionsMessageRegExp = "1:8 undeclared variable <x>")
public void compilingCode_throwsUndeclaredVariableException_ifReadingUndefinedVariable() throws Exception {
compileAndRun("println(x);");
// evaluation performed by expected exception.
}
@Test(expectedExceptions = UndeclaredVariableException.class,
expectedExceptionsMessageRegExp = "1:0 undeclared variable <x>")
public void compilingCode_throwsUndeclaredVariableException_ifWritingUndefinedVariable() throws Exception {
compileAndRun("x = 5;");
// evaluation performed by expected exception.
}
@Test(expectedExceptions = VariableAlreadyDefinedException.class,
expectedExceptionsMessageRegExp = "2:4 variable <x> already defined")
public void compilingCode_throwsAlreadyDefinedException_whenDefiningAlreadyDefinedVariable() throws Exception {
compileAndRun("int x;" + System.lineSeparator() +
"int x;");
// evaluation performed by expected exception.
}
@Test(expectedExceptions = UndefinedFunctionException.class,
expectedExceptionsMessageRegExp = "1:8 call to undefined function : <undefinedFunction>")
public void compilingCode_throwsUndefinedFunctionException_whenCallingUndefinedFunction() throws Exception {
compileAndRun("println(undefinedFunction());");
// evaluation performed by expected exception.
}
@Test(expectedExceptions = FunctionAlreadyDefinedException.class,
expectedExceptionsMessageRegExp = "2:4 function already defined : <x>")
public void compilingCode_throwsAlreadyDefinedFunctionException_whenDefiningFunctionTwice() throws Exception {
compileAndRun("int x() { return 42; }\n" +
"int x() { return 45; }");
// evaluation performed by expected exception.
}
private String compileAndRun(String code) throws Exception {
code = Main.compile(CharStreams.fromString(code));
ClassFile classFile = new ClassFile();
classFile.readJasmin(new StringReader(code), "", false);
Path outputPath = tempDir.resolve(classFile.getClassName() + ".class");
try (OutputStream outputStream = Files.newOutputStream(outputPath)) {
classFile.write(outputStream);
}
return runJavaClass(tempDir, classFile.getClassName());
}
private String runJavaClass(Path dir, String className) throws IOException {
Process process = Runtime.getRuntime().exec(new String[]{"java", "-cp", dir.toString(), className});
try (InputStream in = process.getInputStream()) {
return new Scanner(in).useDelimiter("\\A").next();
}
}
private String[] example(String name, String expectedResult) throws IllegalArgumentException {
try (InputStream in = CompilerTest.class.getClassLoader().getResourceAsStream("examples/" + name)) {
if (in == null) {
throw new IllegalArgumentException("No such example " + name);
}
String code = new Scanner(in, "UTF-8").useDelimiter("\\A").next();
return new String[] {name, code, expectedResult};
} catch (IOException e) {
throw new IllegalArgumentException("No such example " + name);
}
}
}

View File

@ -0,0 +1,5 @@
if (0) {
println(81);
} else {
println(42);
}

View File

@ -0,0 +1,5 @@
if(1){
println(81);
} else{
println(42);
}

View File

@ -0,0 +1,5 @@
int add(int a, int b) {
return a + b;
}
println(add(5, 8));

View File

@ -0,0 +1,9 @@
int i;
i = 42;
int randomNumber() {
int i;
i = 4;
return i;
}
println(randomNumber());
println(i);

View File

@ -0,0 +1,5 @@
int number() {
return 4;
}
println(number());

View File

@ -0,0 +1,6 @@
int x(int i) {
println(i);
return i;
}
println(x(0) && x(1));

View File

@ -0,0 +1,6 @@
int x(int i) {
println(i);
return i;
}
println(x(1) || x(0));

View File

@ -0,0 +1,5 @@
if (0) {
println(81);
} else {
println(42);
}

View File

@ -0,0 +1,5 @@
if(1){
println(81);
} else{
println(42);
}

View File

@ -0,0 +1,5 @@
int add(int a, int b) {
return a + b;
}
println(add(5, 8));

View File

@ -0,0 +1,9 @@
int i;
i = 42;
int randomNumber() {
int i;
i = 4;
return i;
}
println(randomNumber());
println(i);

View File

@ -0,0 +1,5 @@
int number() {
return 4;
}
println(number());

View File

@ -0,0 +1,6 @@
int x(int i) {
println(i);
return i;
}
println(x(0) && x(1));

View File

@ -0,0 +1,6 @@
int x(int i) {
println(i);
return i;
}
println(x(1) || x(0));