Initial
This commit is contained in:
commit
664c9a4f31
25
pom.xml
Normal file
25
pom.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.annimon</groupId>
|
||||
<artifactId>ParboiledExample</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.parboiled</groupId>
|
||||
<artifactId>parboiled-core</artifactId>
|
||||
<version>1.1.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.parboiled</groupId>
|
||||
<artifactId>parboiled-java</artifactId>
|
||||
<version>1.1.7</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
10
src/main/java/com/annimon/parboiled/calc/AstNode.java
Normal file
10
src/main/java/com/annimon/parboiled/calc/AstNode.java
Normal file
@ -0,0 +1,10 @@
|
||||
package com.annimon.parboiled.calc;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author aNNiMON
|
||||
*/
|
||||
public interface AstNode {
|
||||
|
||||
int eval();
|
||||
}
|
36
src/main/java/com/annimon/parboiled/calc/BlockNode.java
Normal file
36
src/main/java/com/annimon/parboiled/calc/BlockNode.java
Normal file
@ -0,0 +1,36 @@
|
||||
package com.annimon.parboiled.calc;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import org.parboiled.trees.ImmutableTreeNode;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author aNNiMON
|
||||
*/
|
||||
public final class BlockNode extends ImmutableTreeNode implements AstNode {
|
||||
|
||||
private final List<AstNode> nodes;
|
||||
|
||||
public BlockNode(AstNode node) {
|
||||
this(Arrays.asList(node));
|
||||
System.out.println(node.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
public BlockNode(AstNode... nodes) {
|
||||
this(Arrays.asList(nodes));
|
||||
}
|
||||
|
||||
public BlockNode(List<AstNode> nodes) {
|
||||
super(nodes);
|
||||
this.nodes = nodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int eval() {
|
||||
nodes.stream()
|
||||
.filter(n -> n != null)
|
||||
.forEach(AstNode::eval);
|
||||
return 0;
|
||||
}
|
||||
}
|
25
src/main/java/com/annimon/parboiled/calc/NumberNode.java
Normal file
25
src/main/java/com/annimon/parboiled/calc/NumberNode.java
Normal file
@ -0,0 +1,25 @@
|
||||
package com.annimon.parboiled.calc;
|
||||
|
||||
import org.parboiled.trees.ImmutableTreeNode;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author aNNiMON
|
||||
*/
|
||||
public final class NumberNode extends ImmutableTreeNode implements AstNode {
|
||||
|
||||
private final int value;
|
||||
|
||||
public NumberNode(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int eval() {
|
||||
return value;
|
||||
}
|
||||
}
|
19
src/main/java/com/annimon/parboiled/calc/OkaNode.java
Normal file
19
src/main/java/com/annimon/parboiled/calc/OkaNode.java
Normal file
@ -0,0 +1,19 @@
|
||||
package com.annimon.parboiled.calc;
|
||||
|
||||
import org.parboiled.trees.ImmutableTreeNode;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author aNNiMON
|
||||
*/
|
||||
public final class OkaNode extends ImmutableTreeNode implements AstNode {
|
||||
|
||||
public OkaNode() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int eval() {
|
||||
System.out.println("Я Ока");
|
||||
return 0;
|
||||
}
|
||||
}
|
73
src/main/java/com/annimon/parboiled/calc/OkaParser.java
Normal file
73
src/main/java/com/annimon/parboiled/calc/OkaParser.java
Normal file
@ -0,0 +1,73 @@
|
||||
package com.annimon.parboiled.calc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.parboiled.BaseParser;
|
||||
import static org.parboiled.BaseParser.EOI;
|
||||
import org.parboiled.Parboiled;
|
||||
import org.parboiled.Rule;
|
||||
import static org.parboiled.errors.ErrorUtils.printParseErrors;
|
||||
import org.parboiled.parserunners.RecoveringParseRunner;
|
||||
import org.parboiled.support.ParsingResult;
|
||||
|
||||
public class OkaParser extends BaseParser<AstNode> {
|
||||
|
||||
public static void main(String[] args) {
|
||||
final OkaParser parser = Parboiled.createParser(OkaParser.class);
|
||||
ParsingResult<?> result = new RecoveringParseRunner(parser.Program())
|
||||
.run("ок ори 3 ори 2 ока ока ори 2");
|
||||
if (result.hasErrors()) {
|
||||
System.out.println("\nParse Errors:\n" + printParseErrors(result));
|
||||
}
|
||||
|
||||
AstNode node = (AstNode) result.resultValue;
|
||||
node.eval();
|
||||
}
|
||||
|
||||
public Rule Program() {
|
||||
return Sequence(Block(), EOI);
|
||||
}
|
||||
|
||||
Rule Block() {
|
||||
final List<AstNode> nodes = new ArrayList<>();
|
||||
return Sequence(
|
||||
Statement(),
|
||||
ZeroOrMore(nodes.add(pop()), Statement()),
|
||||
push(new BlockNode(nodes))
|
||||
);
|
||||
}
|
||||
|
||||
Rule Statement() {
|
||||
return FirstOf(Oka(), Ori());
|
||||
}
|
||||
|
||||
Rule Oka() {
|
||||
return Sequence(
|
||||
IgnoreCase("ока "),
|
||||
push(new OkaNode())
|
||||
);
|
||||
}
|
||||
|
||||
Rule Ori() {
|
||||
return Sequence(
|
||||
Sequence(IgnoreCase("ори "), Number()),
|
||||
push(new OriNode(pop()))
|
||||
);
|
||||
}
|
||||
|
||||
Rule Number() {
|
||||
return Sequence(
|
||||
OneOrMore(Digit()),
|
||||
push(new NumberNode(Integer.parseInt(matchOrDefault("0")))),
|
||||
WhiteSpace()
|
||||
);
|
||||
}
|
||||
|
||||
Rule Digit() {
|
||||
return CharRange('0', '9');
|
||||
}
|
||||
|
||||
Rule WhiteSpace() {
|
||||
return ZeroOrMore(AnyOf(" \t\f"));
|
||||
}
|
||||
}
|
28
src/main/java/com/annimon/parboiled/calc/OriNode.java
Normal file
28
src/main/java/com/annimon/parboiled/calc/OriNode.java
Normal file
@ -0,0 +1,28 @@
|
||||
package com.annimon.parboiled.calc;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.IntStream;
|
||||
import org.parboiled.trees.ImmutableTreeNode;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author aNNiMON
|
||||
*/
|
||||
public final class OriNode extends ImmutableTreeNode implements AstNode {
|
||||
|
||||
private final AstNode node;
|
||||
|
||||
public OriNode(AstNode node) {
|
||||
super(Arrays.asList(node));
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int eval() {
|
||||
if (node == null) return 0;
|
||||
|
||||
IntStream.range(0, node.eval())
|
||||
.forEach(i -> System.out.println("ору"));
|
||||
return 0;
|
||||
}
|
||||
}
|
50
src/main/java/org/parboiled/examples/abc/AbcParser.java
Normal file
50
src/main/java/org/parboiled/examples/abc/AbcParser.java
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2011 Mathias Doenitz
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.parboiled.examples.abc;
|
||||
|
||||
import org.parboiled.BaseParser;
|
||||
import org.parboiled.Rule;
|
||||
import org.parboiled.annotations.BuildParseTree;
|
||||
|
||||
/**
|
||||
* A parser for the classic non-context free language example { a^n b^n c^n : n >= 1 }
|
||||
* S <- &(A c) a+ B !(a|b|c)
|
||||
* A <- a A? b
|
||||
* B <- b B? c
|
||||
*/
|
||||
@SuppressWarnings({"InfiniteRecursion"})
|
||||
@BuildParseTree
|
||||
public class AbcParser extends BaseParser<Object> {
|
||||
|
||||
public Rule S() {
|
||||
return Sequence(
|
||||
Test(A(), 'c'),
|
||||
OneOrMore('a'),
|
||||
B(),
|
||||
TestNot(AnyOf("abc"))
|
||||
);
|
||||
}
|
||||
|
||||
public Rule A() {
|
||||
return Sequence('a', Optional(A()), 'b');
|
||||
}
|
||||
|
||||
public Rule B() {
|
||||
return Sequence('b', Optional(B()), 'c');
|
||||
}
|
||||
|
||||
}
|
49
src/main/java/org/parboiled/examples/abc/Main.java
Normal file
49
src/main/java/org/parboiled/examples/abc/Main.java
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2011 Mathias Doenitz
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.parboiled.examples.abc;
|
||||
|
||||
import org.parboiled.Parboiled;
|
||||
import org.parboiled.common.StringUtils;
|
||||
import org.parboiled.errors.ErrorUtils;
|
||||
import org.parboiled.parserunners.RecoveringParseRunner;
|
||||
import static org.parboiled.support.ParseTreeUtils.printNodeTree;
|
||||
|
||||
import org.parboiled.parserunners.ReportingParseRunner;
|
||||
import org.parboiled.support.ParsingResult;
|
||||
|
||||
import java.util.Scanner;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
AbcParser parser = Parboiled.createParser(AbcParser.class);
|
||||
|
||||
while (true) {
|
||||
System.out.print("Enter an a^n b^n c^n expression (single RETURN to exit)!\n");
|
||||
String input = new Scanner(System.in).nextLine();
|
||||
if (StringUtils.isEmpty(input)) break;
|
||||
|
||||
ParsingResult<?> result = new ReportingParseRunner(parser.S()).run(input);
|
||||
|
||||
if (!result.parseErrors.isEmpty())
|
||||
System.out.println(ErrorUtils.printParseError(result.parseErrors.get(0)));
|
||||
else
|
||||
System.out.println(printNodeTree(result) + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2011 Mathias Doenitz
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.parboiled.examples.calculators;
|
||||
|
||||
import org.parboiled.BaseParser;
|
||||
import org.parboiled.Parboiled;
|
||||
import org.parboiled.parserunners.RecoveringParseRunner;
|
||||
import org.parboiled.Rule;
|
||||
import org.parboiled.common.StringUtils;
|
||||
import org.parboiled.support.ParsingResult;
|
||||
import org.parboiled.support.ToStringFormatter;
|
||||
import org.parboiled.trees.GraphNode;
|
||||
|
||||
import java.util.Scanner;
|
||||
|
||||
import static org.parboiled.errors.ErrorUtils.printParseErrors;
|
||||
import static org.parboiled.support.ParseTreeUtils.printNodeTree;
|
||||
import static org.parboiled.trees.GraphUtils.printTree;
|
||||
|
||||
/**
|
||||
* Base class of all calculator parsers in the org.parboiled.examples.calculators package.
|
||||
* Simply adds the public static main entry point.
|
||||
*
|
||||
* @param <V> the type of the main value object created by the parser
|
||||
*/
|
||||
public abstract class CalculatorParser<V> extends BaseParser<V> {
|
||||
|
||||
public abstract Rule InputLine();
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public static <V, P extends CalculatorParser<V>> void main(Class<P> parserClass) {
|
||||
CalculatorParser<V> parser = Parboiled.createParser(parserClass);
|
||||
|
||||
while (true) {
|
||||
System.out.print("Enter a calculators expression (single RETURN to exit)!\n");
|
||||
String input = new Scanner(System.in).nextLine();
|
||||
if (StringUtils.isEmpty(input)) break;
|
||||
|
||||
ParsingResult<?> result = new RecoveringParseRunner(parser.InputLine()).run(input);
|
||||
|
||||
if (result.hasErrors()) {
|
||||
System.out.println("\nParse Errors:\n" + printParseErrors(result));
|
||||
}
|
||||
|
||||
Object value = result.parseTreeRoot.getValue();
|
||||
if (value != null) {
|
||||
String str = value.toString();
|
||||
int ix = str.indexOf('|');
|
||||
if (ix >= 0) str = str.substring(ix + 2); // extract value part of AST node toString()
|
||||
System.out.println(input + " = " + str + '\n');
|
||||
}
|
||||
if (value instanceof GraphNode) {
|
||||
System.out.println("\nAbstract Syntax Tree:\n" +
|
||||
printTree((GraphNode) value, new ToStringFormatter(null)) + '\n');
|
||||
} else {
|
||||
System.out.println("\nParse Tree:\n" + printNodeTree(result) + '\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2011 Mathias Doenitz
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.parboiled.examples.calculators;
|
||||
|
||||
import org.parboiled.Rule;
|
||||
import org.parboiled.annotations.BuildParseTree;
|
||||
|
||||
/**
|
||||
* A basic calculator parser without any actions.
|
||||
*/
|
||||
@BuildParseTree
|
||||
public class CalculatorParser0 extends CalculatorParser<Integer> {
|
||||
|
||||
@Override
|
||||
public Rule InputLine() {
|
||||
return Sequence(Expression(), EOI);
|
||||
}
|
||||
|
||||
Rule Expression() {
|
||||
return Sequence(Term(), ZeroOrMore(AnyOf("+-"), Term()));
|
||||
}
|
||||
|
||||
Rule Term() {
|
||||
return Sequence(Factor(), ZeroOrMore(AnyOf("*/"), Factor()));
|
||||
}
|
||||
|
||||
Rule Factor() {
|
||||
return FirstOf(Number(), Parens());
|
||||
}
|
||||
|
||||
Rule Parens() {
|
||||
return Sequence('(', Expression(), ')');
|
||||
}
|
||||
|
||||
Rule Number() {
|
||||
return OneOrMore(Digit());
|
||||
}
|
||||
|
||||
Rule Digit() {
|
||||
return CharRange('0', '9');
|
||||
}
|
||||
|
||||
//**************** MAIN ****************
|
||||
|
||||
public static void main(String[] args) {
|
||||
main(CalculatorParser0.class);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2011 Mathias Doenitz
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.parboiled.examples.calculators;
|
||||
|
||||
import org.parboiled.Rule;
|
||||
import org.parboiled.annotations.BuildParseTree;
|
||||
import org.parboiled.annotations.SuppressSubnodes;
|
||||
|
||||
/**
|
||||
* A calculator parser building calculation results directly in the parsers value stack.
|
||||
* All calculations are implemented directly in action expressions.
|
||||
*/
|
||||
@BuildParseTree
|
||||
public class CalculatorParser1 extends CalculatorParser<Integer> {
|
||||
|
||||
@Override
|
||||
public Rule InputLine() {
|
||||
return Sequence(Expression(), EOI);
|
||||
}
|
||||
|
||||
public Rule Expression() {
|
||||
return Sequence(
|
||||
Term(), // a successful match of a Term pushes one Integer value onto the value stack
|
||||
ZeroOrMore(
|
||||
FirstOf(
|
||||
// the action that is run after the '+' and the Term have been matched consumes the
|
||||
// two top value stack elements and replaces them with the calculation result
|
||||
Sequence('+', Term(), push(pop() + pop())),
|
||||
|
||||
// same for the '-' operator, however, here the order of the "pop"s matters, we need to
|
||||
// retrieve the second to last value first, which is what the pop(1) call does
|
||||
Sequence('-', Term(), push(pop(1) - pop()))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public Rule Term() {
|
||||
return Sequence(
|
||||
Factor(), // a successful match of a Factor pushes one Integer value onto the value stack
|
||||
ZeroOrMore(
|
||||
FirstOf(
|
||||
// the action that is run after the '*' and the Factor have been matched consumes the
|
||||
// two top value stack elements and replaces them with the calculation result
|
||||
Sequence('*', Factor(), push(pop() * pop())),
|
||||
|
||||
// same for the '/' operator, however, here the order of the "pop"s matters, we need to
|
||||
// retrieve the second to last value first, which is what the pop(1) call does
|
||||
Sequence('/', Factor(), push(pop(1) / pop()))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public Rule Factor() {
|
||||
return FirstOf(Number(), Parens()); // a factor "produces" exactly one Integer value on the value stack
|
||||
}
|
||||
|
||||
public Rule Parens() {
|
||||
return Sequence('(', Expression(), ')');
|
||||
}
|
||||
|
||||
public Rule Number() {
|
||||
return Sequence(
|
||||
Digits(),
|
||||
|
||||
// parse the input text matched by the preceding "Digits" rule,
|
||||
// convert it into an Integer and push it onto the value stack
|
||||
// the action uses a default string in case it is run during error recovery (resynchronization)
|
||||
push(Integer.parseInt(matchOrDefault("0")))
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressSubnodes
|
||||
public Rule Digits() {
|
||||
return OneOrMore(Digit());
|
||||
}
|
||||
|
||||
public Rule Digit() {
|
||||
return CharRange('0', '9');
|
||||
}
|
||||
|
||||
//**************** MAIN ****************
|
||||
|
||||
public static void main(String[] args) {
|
||||
main(CalculatorParser1.class);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2011 Mathias Doenitz
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.parboiled.examples.calculators;
|
||||
|
||||
import org.parboiled.Rule;
|
||||
import org.parboiled.annotations.BuildParseTree;
|
||||
import org.parboiled.annotations.SuppressSubnodes;
|
||||
import org.parboiled.examples.calculators.CalculatorParser2.CalcNode;
|
||||
import org.parboiled.support.Var;
|
||||
import org.parboiled.trees.ImmutableBinaryTreeNode;
|
||||
|
||||
/**
|
||||
* A calculator parser building an AST representing the expression structure before performing the actual calculation.
|
||||
* The parser value stack is used to build the AST nodes of type CalcNode.
|
||||
*/
|
||||
@BuildParseTree
|
||||
public class CalculatorParser2 extends CalculatorParser<CalcNode> {
|
||||
|
||||
@Override
|
||||
public Rule InputLine() {
|
||||
return Sequence(Expression(), EOI);
|
||||
}
|
||||
|
||||
public Rule Expression() {
|
||||
Var<Character> op = new Var<Character>(); // we use an action variable to hold the operator character
|
||||
return Sequence(
|
||||
Term(),
|
||||
ZeroOrMore(
|
||||
AnyOf("+-"),
|
||||
op.set(matchedChar()), // set the action variable to the matched operator char
|
||||
Term(),
|
||||
|
||||
// create an AST node for the operation that was just matched
|
||||
// we consume the two top stack elements and replace them with a new AST node
|
||||
// we use an alternative technique to the one shown in CalculatorParser1 to reverse
|
||||
// the order of the two top value stack elements
|
||||
swap() && push(new CalcNode(op.get(), pop(), pop()))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public Rule Term() {
|
||||
Var<Character> op = new Var<Character>(); // we use an action variable to hold the operator character
|
||||
return Sequence(
|
||||
Factor(),
|
||||
ZeroOrMore(
|
||||
AnyOf("*/"),
|
||||
op.set(matchedChar()), // set the action variable to the matched operator char
|
||||
Factor(),
|
||||
|
||||
// create an AST node for the operation that was just matched
|
||||
// we consume the two top stack elements and replace them with a new AST node
|
||||
// we use an alternative technique to the one shown in CalculatorParser1 to reverse
|
||||
// the order of the two top value stack elements
|
||||
swap() && push(new CalcNode(op.get(), pop(), pop()))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public Rule Factor() {
|
||||
return FirstOf(Number(), Parens());
|
||||
}
|
||||
|
||||
public Rule Parens() {
|
||||
return Sequence('(', Expression(), ')');
|
||||
}
|
||||
|
||||
public Rule Number() {
|
||||
return Sequence(
|
||||
Digits(),
|
||||
|
||||
// parse the input text matched by the preceding "Digits" rule,
|
||||
// convert it into an Integer and push a new AST node for it onto the value stack
|
||||
// the action uses a default string in case it is run during error recovery (resynchronization)
|
||||
push(new CalcNode(Integer.parseInt(matchOrDefault("0"))))
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressSubnodes
|
||||
public Rule Digits() {
|
||||
return OneOrMore(Digit());
|
||||
}
|
||||
|
||||
public Rule Digit() {
|
||||
return CharRange('0', '9');
|
||||
}
|
||||
|
||||
//****************************************************************
|
||||
|
||||
/**
|
||||
* The AST node for the calculators. The type of the node is carried as a Character that can either contain
|
||||
* an operator char or be null. In the latter case the AST node is a leaf directly containing a value.
|
||||
*/
|
||||
public static class CalcNode extends ImmutableBinaryTreeNode<CalcNode> {
|
||||
private int value;
|
||||
private Character operator;
|
||||
|
||||
public CalcNode(int value) {
|
||||
super(null, null);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public CalcNode(Character operator, CalcNode left, CalcNode right) {
|
||||
super(left, right);
|
||||
this.operator = operator;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
if (operator == null) return value;
|
||||
switch (operator) {
|
||||
case '+':
|
||||
return left().getValue() + right().getValue();
|
||||
case '-':
|
||||
return left().getValue() - right().getValue();
|
||||
case '*':
|
||||
return left().getValue() * right().getValue();
|
||||
case '/':
|
||||
return left().getValue() / right().getValue();
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (operator == null ? "Value " + value : "Operator '" + operator + '\'') + " | " + getValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//**************** MAIN ****************
|
||||
|
||||
public static void main(String[] args) {
|
||||
main(CalculatorParser2.class);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2011 Mathias Doenitz
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.parboiled.examples.calculators;
|
||||
|
||||
import org.parboiled.Rule;
|
||||
import org.parboiled.annotations.BuildParseTree;
|
||||
import org.parboiled.examples.calculators.CalculatorParser3.CalcNode;
|
||||
import org.parboiled.support.Var;
|
||||
import org.parboiled.trees.ImmutableBinaryTreeNode;
|
||||
|
||||
/**
|
||||
* A calculator parser building an AST representing the expression structure before performing the actual calculation.
|
||||
* The value field of the parse tree nodes is used for AST nodes.
|
||||
* As opposed to the CalculatorParser2 this parser also supports floating point operations, negative numbers, a "power"
|
||||
* and a "SQRT" operation as well as optional whitespace between the various expressions components.
|
||||
*/
|
||||
@BuildParseTree
|
||||
public class CalculatorParser3 extends CalculatorParser<CalcNode> {
|
||||
|
||||
@Override
|
||||
public Rule InputLine() {
|
||||
return Sequence(Expression(), EOI);
|
||||
}
|
||||
|
||||
Rule Expression() {
|
||||
Var<Character> op = new Var<Character>();
|
||||
return Sequence(
|
||||
Term(),
|
||||
ZeroOrMore(
|
||||
// we use a FirstOf(String, String) instead of a AnyOf(String) so we can use the
|
||||
// fromStringLiteral transformation (see below), which automatically consumes trailing whitespace
|
||||
FirstOf("+ ", "- "), op.set(matchedChar()),
|
||||
Term(),
|
||||
|
||||
// same as in CalculatorParser2
|
||||
push(new CalcNode(op.get(), pop(1), pop()))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Rule Term() {
|
||||
Var<Character> op = new Var<Character>();
|
||||
return Sequence(
|
||||
Factor(),
|
||||
ZeroOrMore(
|
||||
FirstOf("* ", "/ "), op.set(matchedChar()),
|
||||
Factor(),
|
||||
push(new CalcNode(op.get(), pop(1), pop()))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Rule Factor() {
|
||||
return Sequence(
|
||||
Atom(),
|
||||
ZeroOrMore(
|
||||
"^ ",
|
||||
Atom(),
|
||||
push(new CalcNode('^', pop(1), pop()))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Rule Atom() {
|
||||
return FirstOf(Number(), SquareRoot(), Parens());
|
||||
}
|
||||
|
||||
Rule SquareRoot() {
|
||||
return Sequence(
|
||||
"SQRT ",
|
||||
Parens(),
|
||||
|
||||
// create a new AST node with a special operator 'R' and only one child
|
||||
push(new CalcNode('R', pop(), null))
|
||||
);
|
||||
}
|
||||
|
||||
Rule Parens() {
|
||||
return Sequence("( ", Expression(), ") ");
|
||||
}
|
||||
|
||||
Rule Number() {
|
||||
return Sequence(
|
||||
// we use another Sequence in the "Number" Sequence so we can easily access the input text matched
|
||||
// by the three enclosed rules with "match()" or "matchOrDefault()"
|
||||
Sequence(
|
||||
Optional('-'),
|
||||
OneOrMore(Digit()),
|
||||
Optional('.', OneOrMore(Digit()))
|
||||
),
|
||||
|
||||
// the matchOrDefault() call returns the matched input text of the immediately preceding rule
|
||||
// or a default string (in this case if it is run during error recovery (resynchronization))
|
||||
push(new CalcNode(Double.parseDouble(matchOrDefault("0")))),
|
||||
WhiteSpace()
|
||||
);
|
||||
}
|
||||
|
||||
Rule Digit() {
|
||||
return CharRange('0', '9');
|
||||
}
|
||||
|
||||
Rule WhiteSpace() {
|
||||
return ZeroOrMore(AnyOf(" \t\f"));
|
||||
}
|
||||
|
||||
// we redefine the rule creation for string literals to automatically match trailing whitespace if the string
|
||||
// literal ends with a space character, this way we don't have to insert extra whitespace() rules after each
|
||||
// character or string literal
|
||||
|
||||
@Override
|
||||
protected Rule fromStringLiteral(String string) {
|
||||
return string.endsWith(" ") ?
|
||||
Sequence(String(string.substring(0, string.length() - 1)), WhiteSpace()) :
|
||||
String(string);
|
||||
}
|
||||
|
||||
//****************************************************************
|
||||
|
||||
/**
|
||||
* The AST node for the calculators. The type of the node is carried as a Character that can either contain
|
||||
* an operator char or be null. In the latter case the AST node is a leaf directly containing a value.
|
||||
*/
|
||||
public static class CalcNode extends ImmutableBinaryTreeNode<CalcNode> {
|
||||
private double value;
|
||||
private Character operator;
|
||||
|
||||
public CalcNode(double value) {
|
||||
super(null, null);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public CalcNode(Character operator, CalcNode left, CalcNode right) {
|
||||
super(left, right);
|
||||
this.operator = operator;
|
||||
}
|
||||
|
||||
public double getValue() {
|
||||
if (operator == null) return value;
|
||||
switch (operator) {
|
||||
case '+':
|
||||
return left().getValue() + right().getValue();
|
||||
case '-':
|
||||
return left().getValue() - right().getValue();
|
||||
case '*':
|
||||
return left().getValue() * right().getValue();
|
||||
case '/':
|
||||
return left().getValue() / right().getValue();
|
||||
case '^':
|
||||
return Math.pow(left().getValue(), right().getValue());
|
||||
case 'R':
|
||||
return Math.sqrt(left().getValue());
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (operator == null ? "Value " + value : "Operator '" + operator + '\'') + " | " + getValue();
|
||||
}
|
||||
}
|
||||
|
||||
//**************** MAIN ****************
|
||||
|
||||
public static void main(String[] args) {
|
||||
main(CalculatorParser3.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2011 Mathias Doenitz
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.parboiled.examples.calculators;
|
||||
|
||||
import org.parboiled.Rule;
|
||||
import org.parboiled.annotations.BuildParseTree;
|
||||
import org.parboiled.examples.calculators.CalculatorParser3.CalcNode;
|
||||
import org.parboiled.support.Var;
|
||||
|
||||
/**
|
||||
* A calculator parser defining the same language as the CalculatorParser3 but using a rule building helper methods
|
||||
* to Factor out common constructs.
|
||||
*/
|
||||
@BuildParseTree
|
||||
public class CalculatorParser4 extends CalculatorParser<CalcNode> {
|
||||
|
||||
@Override
|
||||
public Rule InputLine() {
|
||||
return Sequence(Expression(), EOI);
|
||||
}
|
||||
|
||||
public Rule Expression() {
|
||||
return OperatorRule(Term(), FirstOf("+ ", "- "));
|
||||
}
|
||||
|
||||
public Rule Term() {
|
||||
return OperatorRule(Factor(), FirstOf("* ", "/ "));
|
||||
}
|
||||
|
||||
public Rule Factor() {
|
||||
// by using toRule("^ ") instead of Ch('^') we make use of the fromCharLiteral(...) transformation below
|
||||
return OperatorRule(Atom(), toRule("^ "));
|
||||
}
|
||||
|
||||
public Rule OperatorRule(Rule subRule, Rule operatorRule) {
|
||||
Var<Character> op = new Var<Character>();
|
||||
return Sequence(
|
||||
subRule,
|
||||
ZeroOrMore(
|
||||
operatorRule, op.set(matchedChar()),
|
||||
subRule,
|
||||
push(new CalcNode(op.get(), pop(1), pop()))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public Rule Atom() {
|
||||
return FirstOf(Number(), SquareRoot(), Parens());
|
||||
}
|
||||
|
||||
public Rule SquareRoot() {
|
||||
return Sequence("SQRT", Parens(), push(new CalcNode('R', pop(), null)));
|
||||
}
|
||||
|
||||
public Rule Parens() {
|
||||
return Sequence("( ", Expression(), ") ");
|
||||
}
|
||||
|
||||
public Rule Number() {
|
||||
return Sequence(
|
||||
Sequence(
|
||||
Optional(Ch('-')),
|
||||
OneOrMore(Digit()),
|
||||
Optional(Ch('.'), OneOrMore(Digit()))
|
||||
),
|
||||
// the action uses a default string in case it is run during error recovery (resynchronization)
|
||||
push(new CalcNode(Double.parseDouble(matchOrDefault("0")))),
|
||||
WhiteSpace()
|
||||
);
|
||||
}
|
||||
|
||||
public Rule Digit() {
|
||||
return CharRange('0', '9');
|
||||
}
|
||||
|
||||
public Rule WhiteSpace() {
|
||||
return ZeroOrMore(AnyOf(" \t\f"));
|
||||
}
|
||||
|
||||
// we redefine the rule creation for string literals to automatically match trailing whitespace if the string
|
||||
// literal ends with a space character, this way we don't have to insert extra whitespace() rules after each
|
||||
// character or string literal
|
||||
@Override
|
||||
protected Rule fromStringLiteral(String string) {
|
||||
return string.endsWith(" ") ?
|
||||
Sequence(String(string.substring(0, string.length() - 1)), WhiteSpace()) :
|
||||
String(string);
|
||||
}
|
||||
|
||||
//**************** MAIN ****************
|
||||
|
||||
public static void main(String[] args) {
|
||||
main(CalculatorParser4.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package org.parboiled.examples.indenting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class IndentNode {
|
||||
|
||||
private final String name;
|
||||
private final List<IndentNode> children = new ArrayList<IndentNode>();
|
||||
|
||||
public IndentNode(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public boolean addChild(IndentNode child) {
|
||||
children.add(child);
|
||||
return true;
|
||||
}
|
||||
|
||||
List<IndentNode> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IndentNode [name=" + name + ", children=" + children + "]";
|
||||
}
|
||||
}
|
30
src/main/java/org/parboiled/examples/indenting/Main.java
Normal file
30
src/main/java/org/parboiled/examples/indenting/Main.java
Normal file
@ -0,0 +1,30 @@
|
||||
package org.parboiled.examples.indenting;
|
||||
|
||||
import static org.parboiled.support.ParseTreeUtils.printNodeTree;
|
||||
|
||||
import org.parboiled.Parboiled;
|
||||
import org.parboiled.buffers.IndentDedentInputBuffer;
|
||||
import org.parboiled.errors.ErrorUtils;
|
||||
import org.parboiled.parserunners.ReportingParseRunner;
|
||||
import org.parboiled.support.ParsingResult;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SimpleIndent parser = Parboiled.createParser(SimpleIndent.class);
|
||||
String input = "NodeA \n\tNodeB\n\tNodeC \n\t\tNodeD \nNodeE";
|
||||
|
||||
ParsingResult<?> result = new ReportingParseRunner(parser.Parent())
|
||||
.run(new IndentDedentInputBuffer(input.toCharArray(), 2, ";", true, true));
|
||||
|
||||
if (!result.parseErrors.isEmpty()) {
|
||||
System.out.println(ErrorUtils.printParseError(result.parseErrors
|
||||
.get(0)));
|
||||
} else {
|
||||
System.out.println("NodeTree: " + printNodeTree(result) + '\n');
|
||||
Object value = result.parseTreeRoot.getValue();
|
||||
System.out.println(value.toString());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package org.parboiled.examples.indenting;
|
||||
|
||||
import org.parboiled.BaseParser;
|
||||
import org.parboiled.Rule;
|
||||
import org.parboiled.annotations.BuildParseTree;
|
||||
|
||||
@BuildParseTree
|
||||
public class SimpleIndent extends BaseParser<IndentNode> {
|
||||
|
||||
Rule Parent() {
|
||||
return Sequence(push(new IndentNode("root")), OneOrMore(Data()), EOI);
|
||||
}
|
||||
|
||||
Rule Data() {
|
||||
return Sequence(Identifier(), push(new IndentNode(match())), peek(1)
|
||||
.addChild(peek()),
|
||||
Optional(Sequence(Spacing(), ChildNodeList())), drop());
|
||||
}
|
||||
|
||||
Rule ChildNodeList() {
|
||||
return Sequence(INDENT, Spacing(), OneOrMore(Data(), Spacing()), DEDENT);
|
||||
}
|
||||
|
||||
Rule Identifier() {
|
||||
return Sequence(PN_CHARS_U(), ZeroOrMore(PN_CHARS_DIGIT_U()));
|
||||
}
|
||||
|
||||
public Rule PN_CHARS_DIGIT_U() {
|
||||
return FirstOf(PN_CHARS_U(), DIGIT());
|
||||
}
|
||||
|
||||
public Rule PN_CHARS_U() {
|
||||
return FirstOf(PN_CHARS_BASE(), '_');
|
||||
}
|
||||
|
||||
public Rule PN_CHARS_BASE() {
|
||||
return FirstOf(
|
||||
CharRange('A', 'Z'),
|
||||
CharRange('a', 'z'),
|
||||
CharRange('\u00C0', '\u00D6'),
|
||||
CharRange('\u00D8', '\u00F6'),
|
||||
CharRange('\u00F8', '\u02FF'),
|
||||
CharRange('\u0370', '\u037D'),
|
||||
CharRange('\u037F', '\u1FFF'),
|
||||
CharRange('\u200C', '\u200D'),
|
||||
CharRange('\u2070', '\u218F'),
|
||||
CharRange('\u2C00', '\u2FEF'),
|
||||
CharRange('\u3001', '\uD7FF'),
|
||||
CharRange('\uF900', '\uFDCF'),
|
||||
CharRange('\uFDF0', '\uFFFD')
|
||||
);
|
||||
}
|
||||
|
||||
public Rule DIGIT() {
|
||||
return CharRange('0', '9');
|
||||
}
|
||||
|
||||
Rule Spacing() {
|
||||
return ZeroOrMore(AnyOf(" \t\r\n\f").label("Whitespace"));
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2011 Mathias Doenitz
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.parboiled.examples.java;
|
||||
|
||||
import org.parboiled.MatcherContext;
|
||||
import org.parboiled.matchers.CustomMatcher;
|
||||
|
||||
public abstract class AbstractJavaCharacterMatcher extends CustomMatcher {
|
||||
|
||||
protected AbstractJavaCharacterMatcher(String label) {
|
||||
super(label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isSingleCharMatcher() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean canMatchEmpty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStarterChar(char c) {
|
||||
return acceptChar(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final char getStarterChar() {
|
||||
return 'a';
|
||||
}
|
||||
|
||||
public final <V> boolean match(MatcherContext<V> context) {
|
||||
if (!acceptChar(context.getCurrentChar())) {
|
||||
return false;
|
||||
}
|
||||
context.advanceIndex(1);
|
||||
context.createNode();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract boolean acceptChar(char c);
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2011 Mathias Doenitz
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.parboiled.examples.java;
|
||||
|
||||
public class JavaLetterMatcher extends AbstractJavaCharacterMatcher {
|
||||
|
||||
public JavaLetterMatcher() {
|
||||
super("Letter");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean acceptChar(char c) {
|
||||
return Character.isJavaIdentifierStart(c);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2011 Mathias Doenitz
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.parboiled.examples.java;
|
||||
|
||||
public class JavaLetterOrDigitMatcher extends AbstractJavaCharacterMatcher {
|
||||
|
||||
public JavaLetterOrDigitMatcher() {
|
||||
super("LetterOrDigit");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean acceptChar(char c) {
|
||||
return Character.isJavaIdentifierPart(c);
|
||||
}
|
||||
}
|
1141
src/main/java/org/parboiled/examples/java/JavaParser.java
Normal file
1141
src/main/java/org/parboiled/examples/java/JavaParser.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2011 Mathias Doenitz
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.parboiled.examples.java;
|
||||
|
||||
import org.parboiled.parserunners.ProfilingParseRunner;
|
||||
import org.parboiled.Rule;
|
||||
import org.parboiled.support.ParsingResult;
|
||||
|
||||
public class JavaParserProfiler extends Main {
|
||||
|
||||
private ProfilingParseRunner parseRunner;
|
||||
|
||||
public static void main(String[] args) {
|
||||
new JavaParserProfiler().run(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(String[] args) {
|
||||
super.run(args);
|
||||
ProfilingParseRunner.Report report = parseRunner.getReport();
|
||||
System.out.println();
|
||||
System.out.println(report.print());
|
||||
}
|
||||
@Override
|
||||
protected ParsingResult<?> run(Rule rootRule, String sourceText) {
|
||||
if (parseRunner == null) {
|
||||
parseRunner = new ProfilingParseRunner(rootRule);
|
||||
}
|
||||
return parseRunner.run(sourceText);
|
||||
}
|
||||
|
||||
}
|
171
src/main/java/org/parboiled/examples/java/Main.java
Normal file
171
src/main/java/org/parboiled/examples/java/Main.java
Normal file
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2011 Mathias Doenitz
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.parboiled.examples.java;
|
||||
|
||||
import static org.parboiled.common.Preconditions.*;
|
||||
import org.parboiled.Parboiled;
|
||||
import org.parboiled.parserunners.ReportingParseRunner;
|
||||
import org.parboiled.Rule;
|
||||
import org.parboiled.support.ParsingResult;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.parboiled.errors.ErrorUtils.printParseErrors;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
new Main().run(args);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"ConstantConditions"})
|
||||
protected void run(String[] args) {
|
||||
System.out.println("parboiled Java parser, performance test");
|
||||
System.out.println("---------------------------------------");
|
||||
|
||||
System.out.print("Creating parser... :");
|
||||
long start = System.currentTimeMillis();
|
||||
Parboiled.createParser(JavaParser.class);
|
||||
time(start);
|
||||
|
||||
System.out.print("Creating 100 more parser instances... :");
|
||||
JavaParser parser = null;
|
||||
start = System.currentTimeMillis();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
parser = Parboiled.createParser(JavaParser.class);
|
||||
}
|
||||
time(start);
|
||||
|
||||
System.out.print("Creating 100 more parser instances using BaseParser.newInstance() ... :");
|
||||
start = System.currentTimeMillis();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
parser = parser.newInstance();
|
||||
}
|
||||
time(start);
|
||||
|
||||
start = System.currentTimeMillis();
|
||||
File baseDir = args.length == 1 ? new File(args[0]) : null;
|
||||
if (baseDir == null || !baseDir.exists()) baseDir = new File(".");
|
||||
System.out.printf("Retrieving file list from '%s'", baseDir);
|
||||
List<File> sources = recursiveGetAllJavaSources(baseDir, new ArrayList<File>());
|
||||
time(start);
|
||||
|
||||
System.out.printf("Parsing all %s given java sources", sources.size());
|
||||
Rule rootRule = parser.CompilationUnit().suppressNode(); // we want to see the parse-tree-less performance
|
||||
start = System.currentTimeMillis();
|
||||
long lines = 0, characters = 0;
|
||||
for (File sourceFile : sources) {
|
||||
long dontCountStart = System.currentTimeMillis();
|
||||
String sourceText = readAllText(sourceFile);
|
||||
start += System.currentTimeMillis() - dontCountStart; // do not count the time for reading the text file
|
||||
|
||||
ParsingResult<?> result = null;
|
||||
try {
|
||||
result = run(rootRule, sourceText);
|
||||
} catch (Exception e) {
|
||||
System.out.printf("\nException while parsing file '%s':\n%s", sourceFile, e);
|
||||
System.exit(1);
|
||||
}
|
||||
if (!result.matched) {
|
||||
System.out.printf("\nParse error(s) in file '%s':\n%s", sourceFile, printParseErrors(result));
|
||||
System.exit(1);
|
||||
} else {
|
||||
System.out.print('.');
|
||||
}
|
||||
lines += result.inputBuffer.getLineCount();
|
||||
characters += sourceText.length();
|
||||
}
|
||||
long time = time(start);
|
||||
|
||||
System.out.println("Parsing performance:");
|
||||
System.out.printf(" %6d Files -> %6.2f Files/sec\n", sources.size(), sources.size() * 1000.0 / time);
|
||||
System.out.printf(" %6d Lines -> %6d Lines/sec\n", lines, lines * 1000 / time);
|
||||
System.out.printf(" %6d Chars -> %6d Chars/sec\n", characters, characters * 1000 / time);
|
||||
}
|
||||
|
||||
protected ParsingResult<?> run(Rule rootRule, String sourceText) {
|
||||
return new ReportingParseRunner(rootRule).run(sourceText);
|
||||
}
|
||||
|
||||
private static long time(long start) {
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.printf(" %s ms\n", end - start);
|
||||
return end - start;
|
||||
}
|
||||
|
||||
private static final FileFilter fileFilter = new FileFilter() {
|
||||
public boolean accept(File file) {
|
||||
return file.isDirectory() || file.getName().endsWith(".java");
|
||||
}
|
||||
};
|
||||
|
||||
private static List<File> recursiveGetAllJavaSources(File file, ArrayList<File> list) {
|
||||
if (file.isDirectory()) {
|
||||
for (File f : file.listFiles(fileFilter)) {
|
||||
recursiveGetAllJavaSources(f, list);
|
||||
}
|
||||
} else {
|
||||
list.add(file);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static String readAllText(File file) {
|
||||
checkArgNotNull(file, "file");
|
||||
return readAllText(file, Charset.forName("UTF8"));
|
||||
}
|
||||
|
||||
public static String readAllText(File file, Charset charset) {
|
||||
checkArgNotNull(file, "file");
|
||||
checkArgNotNull(charset, "charset");
|
||||
try {
|
||||
return readAllText(new FileInputStream(file), charset);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String readAllText(InputStream stream, Charset charset) {
|
||||
checkArgNotNull(charset, "charset");
|
||||
if (stream == null) return null;
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, charset));
|
||||
StringWriter writer = new StringWriter();
|
||||
copyAll(reader, writer);
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
public static void copyAll(Reader reader, Writer writer) {
|
||||
checkArgNotNull(reader, "reader");
|
||||
checkArgNotNull(writer, "writer");
|
||||
try {
|
||||
char[] data = new char[4096]; // copy in chunks of 4K
|
||||
int count;
|
||||
while ((count = reader.read(data)) >= 0) writer.write(data, 0, count);
|
||||
|
||||
reader.close();
|
||||
writer.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
796
src/main/java/org/parboiled/examples/sparql/SparqlParser.java
Normal file
796
src/main/java/org/parboiled/examples/sparql/SparqlParser.java
Normal file
@ -0,0 +1,796 @@
|
||||
/*
|
||||
* Copyright (c) 2009 Ken Wenzel
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package org.parboiled.examples.sparql;
|
||||
|
||||
import org.parboiled.BaseParser;
|
||||
import org.parboiled.Rule;
|
||||
|
||||
/**
|
||||
* SPARQL Parser
|
||||
*
|
||||
* @author Ken Wenzel, adapted by Mathias Doenitz
|
||||
*/
|
||||
@SuppressWarnings({"InfiniteRecursion"})
|
||||
public class SparqlParser extends BaseParser<Object> {
|
||||
// <Parser>
|
||||
public Rule Query() {
|
||||
return Sequence(WS(), Prologue(), FirstOf(SelectQuery(),
|
||||
ConstructQuery(), DescribeQuery(), AskQuery()), EOI);
|
||||
}
|
||||
|
||||
public Rule Prologue() {
|
||||
return Sequence(Optional(BaseDecl()), ZeroOrMore(PrefixDecl()));
|
||||
}
|
||||
|
||||
public Rule BaseDecl() {
|
||||
return Sequence(BASE(), IRI_REF());
|
||||
}
|
||||
|
||||
public Rule PrefixDecl() {
|
||||
return Sequence(PREFIX(), PNAME_NS(), IRI_REF());
|
||||
}
|
||||
|
||||
public Rule SelectQuery() {
|
||||
return Sequence(SELECT(), Optional(FirstOf(DISTINCT(),
|
||||
REDUCED())), FirstOf(OneOrMore(Var()), ASTERISK()),
|
||||
ZeroOrMore(DatasetClause()), WhereClause(), SolutionModifier());
|
||||
}
|
||||
|
||||
public Rule ConstructQuery() {
|
||||
return Sequence(CONSTRUCT(), ConstructTemplate(),
|
||||
ZeroOrMore(DatasetClause()), WhereClause(), SolutionModifier());
|
||||
}
|
||||
|
||||
public Rule DescribeQuery() {
|
||||
return Sequence(DESCRIBE(), FirstOf(OneOrMore(VarOrIRIref()),
|
||||
ASTERISK()), ZeroOrMore(DatasetClause()),
|
||||
Optional(WhereClause()), SolutionModifier());
|
||||
}
|
||||
|
||||
public Rule AskQuery() {
|
||||
return Sequence(ASK(), ZeroOrMore(DatasetClause()), WhereClause());
|
||||
}
|
||||
|
||||
public Rule DatasetClause() {
|
||||
return Sequence(FROM(), FirstOf(DefaultGraphClause(),
|
||||
NamedGraphClause()));
|
||||
}
|
||||
|
||||
public Rule DefaultGraphClause() {
|
||||
return SourceSelector();
|
||||
}
|
||||
|
||||
public Rule NamedGraphClause() {
|
||||
return Sequence(NAMED(), SourceSelector());
|
||||
}
|
||||
|
||||
public Rule SourceSelector() {
|
||||
return IriRef();
|
||||
}
|
||||
|
||||
public Rule WhereClause() {
|
||||
return Sequence(Optional(WHERE()), GroupGraphPattern());
|
||||
}
|
||||
|
||||
public Rule SolutionModifier() {
|
||||
return Sequence(Optional(OrderClause()), Optional(LimitOffsetClauses()));
|
||||
}
|
||||
|
||||
public Rule LimitOffsetClauses() {
|
||||
return FirstOf(Sequence(LimitClause(), Optional(OffsetClause())),
|
||||
Sequence(OffsetClause(), Optional(LimitClause())));
|
||||
}
|
||||
|
||||
public Rule OrderClause() {
|
||||
return Sequence(ORDER(), BY(), OneOrMore(OrderCondition()));
|
||||
}
|
||||
|
||||
public Rule OrderCondition() {
|
||||
return FirstOf(
|
||||
Sequence(FirstOf(ASC(), DESC()), BrackettedExpression()),
|
||||
FirstOf(Constraint(), Var()));
|
||||
}
|
||||
|
||||
public Rule LimitClause() {
|
||||
return Sequence(LIMIT(), INTEGER());
|
||||
}
|
||||
|
||||
public Rule OffsetClause() {
|
||||
return Sequence(OFFSET(), INTEGER());
|
||||
}
|
||||
|
||||
public Rule GroupGraphPattern() {
|
||||
return Sequence(OPEN_CURLY_BRACE(), Optional(TriplesBlock()),
|
||||
ZeroOrMore(Sequence(
|
||||
FirstOf(GraphPatternNotTriples(), Filter()),
|
||||
Optional(DOT()), Optional(TriplesBlock()))),
|
||||
CLOSE_CURLY_BRACE());
|
||||
}
|
||||
|
||||
public Rule TriplesBlock() {
|
||||
return Sequence(TriplesSameSubject(), Optional(Sequence(DOT(),
|
||||
Optional(TriplesBlock()))));
|
||||
}
|
||||
|
||||
public Rule GraphPatternNotTriples() {
|
||||
return FirstOf(OptionalGraphPattern(), GroupOrUnionGraphPattern(),
|
||||
GraphGraphPattern());
|
||||
}
|
||||
|
||||
public Rule OptionalGraphPattern() {
|
||||
return Sequence(OPTIONAL(), GroupGraphPattern());
|
||||
}
|
||||
|
||||
public Rule GraphGraphPattern() {
|
||||
return Sequence(GRAPH(), VarOrIRIref(), GroupGraphPattern());
|
||||
}
|
||||
|
||||
public Rule GroupOrUnionGraphPattern() {
|
||||
return Sequence(GroupGraphPattern(), ZeroOrMore(Sequence(UNION(),
|
||||
GroupGraphPattern())));
|
||||
}
|
||||
|
||||
public Rule Filter() {
|
||||
return Sequence(FILTER(), Constraint());
|
||||
}
|
||||
|
||||
public Rule Constraint() {
|
||||
return FirstOf(BrackettedExpression(), BuiltInCall(), FunctionCall());
|
||||
}
|
||||
|
||||
public Rule FunctionCall() {
|
||||
return Sequence(IriRef(), ArgList());
|
||||
}
|
||||
|
||||
public Rule ArgList() {
|
||||
return FirstOf(Sequence(OPEN_BRACE(), CLOSE_BRACE()), Sequence(
|
||||
OPEN_BRACE(), Expression(), ZeroOrMore(Sequence(COMMA(),
|
||||
Expression())), CLOSE_BRACE()));
|
||||
}
|
||||
|
||||
public Rule ConstructTemplate() {
|
||||
return Sequence(OPEN_CURLY_BRACE(), Optional(ConstructTriples()),
|
||||
CLOSE_CURLY_BRACE());
|
||||
}
|
||||
|
||||
public Rule ConstructTriples() {
|
||||
return Sequence(TriplesSameSubject(), Optional(Sequence(DOT(),
|
||||
Optional(ConstructTriples()))));
|
||||
}
|
||||
|
||||
public Rule TriplesSameSubject() {
|
||||
return FirstOf(Sequence(VarOrTerm(), PropertyListNotEmpty()), Sequence(
|
||||
TriplesNode(), PropertyList()));
|
||||
}
|
||||
|
||||
public Rule PropertyListNotEmpty() {
|
||||
return Sequence(Verb(), ObjectList(), ZeroOrMore(Sequence(SEMICOLON(),
|
||||
Optional(Sequence(Verb(), ObjectList())))));
|
||||
}
|
||||
|
||||
public Rule PropertyList() {
|
||||
return Optional(PropertyListNotEmpty());
|
||||
}
|
||||
|
||||
public Rule ObjectList() {
|
||||
return Sequence(Object_(), ZeroOrMore(Sequence(COMMA(), Object_())));
|
||||
}
|
||||
|
||||
public Rule Object_() {
|
||||
return GraphNode();
|
||||
}
|
||||
|
||||
public Rule Verb() {
|
||||
return FirstOf(VarOrIRIref(), A());
|
||||
}
|
||||
|
||||
public Rule TriplesNode() {
|
||||
return FirstOf(Collection(), BlankNodePropertyList());
|
||||
}
|
||||
|
||||
public Rule BlankNodePropertyList() {
|
||||
return Sequence(OPEN_SQUARE_BRACE(), PropertyListNotEmpty(),
|
||||
CLOSE_SQUARE_BRACE());
|
||||
}
|
||||
|
||||
public Rule Collection() {
|
||||
return Sequence(OPEN_BRACE(), OneOrMore(GraphNode()), CLOSE_BRACE());
|
||||
}
|
||||
|
||||
public Rule GraphNode() {
|
||||
return FirstOf(VarOrTerm(), TriplesNode());
|
||||
}
|
||||
|
||||
public Rule VarOrTerm() {
|
||||
return FirstOf(Var(), GraphTerm());
|
||||
}
|
||||
|
||||
public Rule VarOrIRIref() {
|
||||
return FirstOf(Var(), IriRef());
|
||||
}
|
||||
|
||||
public Rule Var() {
|
||||
return FirstOf(VAR1(), VAR2());
|
||||
}
|
||||
|
||||
public Rule GraphTerm() {
|
||||
return FirstOf(IriRef(), RdfLiteral(), NumericLiteral(),
|
||||
BooleanLiteral(), BlankNode(), Sequence(OPEN_BRACE(),
|
||||
CLOSE_BRACE()));
|
||||
}
|
||||
|
||||
public Rule Expression() {
|
||||
return ConditionalOrExpression();
|
||||
}
|
||||
|
||||
public Rule ConditionalOrExpression() {
|
||||
return Sequence(ConditionalAndExpression(), ZeroOrMore(Sequence(OR(),
|
||||
ConditionalAndExpression())));
|
||||
}
|
||||
|
||||
public Rule ConditionalAndExpression() {
|
||||
return Sequence(ValueLogical(), ZeroOrMore(Sequence(AND(),
|
||||
ValueLogical())));
|
||||
}
|
||||
|
||||
public Rule ValueLogical() {
|
||||
return RelationalExpression();
|
||||
}
|
||||
|
||||
public Rule RelationalExpression() {
|
||||
return Sequence(NumericExpression(), Optional(FirstOf(//
|
||||
Sequence(EQUAL(), NumericExpression()), //
|
||||
Sequence(NOT_EQUAL(), NumericExpression()), //
|
||||
Sequence(LESS(), NumericExpression()), //
|
||||
Sequence(GREATER(), NumericExpression()), //
|
||||
Sequence(LESS_EQUAL(), NumericExpression()), //
|
||||
Sequence(GREATER_EQUAL(), NumericExpression()) //
|
||||
) //
|
||||
));
|
||||
}
|
||||
|
||||
public Rule NumericExpression() {
|
||||
return AdditiveExpression();
|
||||
}
|
||||
|
||||
public Rule AdditiveExpression() {
|
||||
return Sequence(MultiplicativeExpression(), //
|
||||
ZeroOrMore(FirstOf(
|
||||
Sequence(PLUS(), MultiplicativeExpression()), //
|
||||
Sequence(MINUS(), MultiplicativeExpression()), //
|
||||
NumericLiteralPositive(), NumericLiteralNegative()) //
|
||||
));
|
||||
}
|
||||
|
||||
public Rule MultiplicativeExpression() {
|
||||
return Sequence(UnaryExpression(), ZeroOrMore(FirstOf(Sequence(
|
||||
ASTERISK(), UnaryExpression()), Sequence(DIVIDE(),
|
||||
UnaryExpression()))));
|
||||
}
|
||||
|
||||
public Rule UnaryExpression() {
|
||||
return FirstOf(Sequence(NOT(), PrimaryExpression()), Sequence(PLUS(),
|
||||
PrimaryExpression()), Sequence(MINUS(), PrimaryExpression()),
|
||||
PrimaryExpression());
|
||||
}
|
||||
|
||||
public Rule PrimaryExpression() {
|
||||
return FirstOf(BrackettedExpression(), BuiltInCall(),
|
||||
IriRefOrFunction(), RdfLiteral(), NumericLiteral(),
|
||||
BooleanLiteral(), Var());
|
||||
}
|
||||
|
||||
public Rule BrackettedExpression() {
|
||||
return Sequence(OPEN_BRACE(), Expression(), CLOSE_BRACE());
|
||||
}
|
||||
|
||||
public Rule BuiltInCall() {
|
||||
return FirstOf(
|
||||
Sequence(STR(), OPEN_BRACE(), Expression(), CLOSE_BRACE()),
|
||||
Sequence(LANG(), OPEN_BRACE(), Expression(), CLOSE_BRACE()),
|
||||
Sequence(LANGMATCHES(), OPEN_BRACE(), Expression(), COMMA(),
|
||||
Expression(), CLOSE_BRACE()),
|
||||
Sequence(DATATYPE(), OPEN_BRACE(), Expression(), CLOSE_BRACE()),
|
||||
Sequence(BOUND(), OPEN_BRACE(), Var(), CLOSE_BRACE()),
|
||||
Sequence(SAMETERM(), OPEN_BRACE(), Expression(), COMMA(),
|
||||
Expression(), CLOSE_BRACE()),
|
||||
Sequence(ISIRI(), OPEN_BRACE(), Expression(), CLOSE_BRACE()),
|
||||
Sequence(ISURI(), OPEN_BRACE(), Expression(), CLOSE_BRACE()),
|
||||
Sequence(ISBLANK(), OPEN_BRACE(), Expression(), CLOSE_BRACE()),
|
||||
Sequence(ISLITERAL(), OPEN_BRACE(), Expression(), CLOSE_BRACE()),
|
||||
RegexExpression());
|
||||
}
|
||||
|
||||
public Rule RegexExpression() {
|
||||
return Sequence(REGEX(), OPEN_BRACE(), Expression(), COMMA(),
|
||||
Expression(), Optional(Sequence(COMMA(), Expression())),
|
||||
CLOSE_BRACE());
|
||||
}
|
||||
|
||||
public Rule IriRefOrFunction() {
|
||||
return Sequence(IriRef(), Optional(ArgList()));
|
||||
}
|
||||
|
||||
public Rule RdfLiteral() {
|
||||
return Sequence(String(), Optional(FirstOf(LANGTAG(), Sequence(
|
||||
REFERENCE(), IriRef()))));
|
||||
}
|
||||
|
||||
public Rule NumericLiteral() {
|
||||
return FirstOf(NumericLiteralUnsigned(), NumericLiteralPositive(),
|
||||
NumericLiteralNegative());
|
||||
}
|
||||
|
||||
public Rule NumericLiteralUnsigned() {
|
||||
return FirstOf(DOUBLE(), DECIMAL(), INTEGER());
|
||||
}
|
||||
|
||||
public Rule NumericLiteralPositive() {
|
||||
return FirstOf(DOUBLE_POSITIVE(), DECIMAL_POSITIVE(),
|
||||
INTEGER_POSITIVE());
|
||||
}
|
||||
|
||||
public Rule NumericLiteralNegative() {
|
||||
return FirstOf(DOUBLE_NEGATIVE(), DECIMAL_NEGATIVE(),
|
||||
INTEGER_NEGATIVE());
|
||||
}
|
||||
|
||||
public Rule BooleanLiteral() {
|
||||
return FirstOf(TRUE(), FALSE());
|
||||
}
|
||||
|
||||
public Rule String() {
|
||||
return FirstOf(STRING_LITERAL_LONG1(), STRING_LITERAL1(),
|
||||
STRING_LITERAL_LONG2(), STRING_LITERAL2());
|
||||
}
|
||||
|
||||
public Rule IriRef() {
|
||||
return FirstOf(IRI_REF(), PrefixedName());
|
||||
}
|
||||
|
||||
public Rule PrefixedName() {
|
||||
return FirstOf(PNAME_LN(), PNAME_NS());
|
||||
}
|
||||
|
||||
public Rule BlankNode() {
|
||||
return FirstOf(BLANK_NODE_LABEL(), Sequence(OPEN_SQUARE_BRACE(),
|
||||
CLOSE_SQUARE_BRACE()));
|
||||
}
|
||||
// </Parser>
|
||||
|
||||
// <Lexer>
|
||||
|
||||
public Rule WS() {
|
||||
return ZeroOrMore(FirstOf(COMMENT(), WS_NO_COMMENT()));
|
||||
}
|
||||
|
||||
public Rule WS_NO_COMMENT() {
|
||||
return FirstOf(Ch(' '), Ch('\t'), Ch('\f'), EOL());
|
||||
}
|
||||
|
||||
public Rule PNAME_NS() {
|
||||
return Sequence(Optional(PN_PREFIX()), ChWS(':'));
|
||||
}
|
||||
|
||||
public Rule PNAME_LN() {
|
||||
return Sequence(PNAME_NS(), PN_LOCAL());
|
||||
}
|
||||
|
||||
public Rule BASE() {
|
||||
return StringIgnoreCaseWS("BASE");
|
||||
}
|
||||
|
||||
public Rule PREFIX() {
|
||||
return StringIgnoreCaseWS("PREFIX");
|
||||
}
|
||||
|
||||
public Rule SELECT() {
|
||||
return StringIgnoreCaseWS("SELECT");
|
||||
}
|
||||
|
||||
public Rule DISTINCT() {
|
||||
return StringIgnoreCaseWS("DISTINCT");
|
||||
}
|
||||
|
||||
public Rule REDUCED() {
|
||||
return StringIgnoreCaseWS("REDUCED");
|
||||
}
|
||||
|
||||
public Rule CONSTRUCT() {
|
||||
return StringIgnoreCaseWS("CONSTRUCT");
|
||||
}
|
||||
|
||||
public Rule DESCRIBE() {
|
||||
return StringIgnoreCaseWS("DESCRIBE");
|
||||
}
|
||||
|
||||
public Rule ASK() {
|
||||
return StringIgnoreCaseWS("ASK");
|
||||
}
|
||||
|
||||
public Rule FROM() {
|
||||
return StringIgnoreCaseWS("FROM");
|
||||
}
|
||||
|
||||
public Rule NAMED() {
|
||||
return StringIgnoreCaseWS("NAMED");
|
||||
}
|
||||
|
||||
public Rule WHERE() {
|
||||
return StringIgnoreCaseWS("WHERE");
|
||||
}
|
||||
|
||||
public Rule ORDER() {
|
||||
return StringIgnoreCaseWS("ORDER");
|
||||
}
|
||||
|
||||
public Rule BY() {
|
||||
return StringIgnoreCaseWS("BY");
|
||||
}
|
||||
|
||||
public Rule ASC() {
|
||||
return StringIgnoreCaseWS("ASC");
|
||||
}
|
||||
|
||||
public Rule DESC() {
|
||||
return StringIgnoreCaseWS("DESC");
|
||||
}
|
||||
|
||||
public Rule LIMIT() {
|
||||
return StringIgnoreCaseWS("LIMIT");
|
||||
}
|
||||
|
||||
public Rule OFFSET() {
|
||||
return StringIgnoreCaseWS("OFFSET");
|
||||
}
|
||||
|
||||
public Rule OPTIONAL() {
|
||||
return StringIgnoreCaseWS("OPTIONAL");
|
||||
}
|
||||
|
||||
public Rule GRAPH() {
|
||||
return StringIgnoreCaseWS("GRAPH");
|
||||
}
|
||||
|
||||
public Rule UNION() {
|
||||
return StringIgnoreCaseWS("UNION");
|
||||
}
|
||||
|
||||
public Rule FILTER() {
|
||||
return StringIgnoreCaseWS("FILTER");
|
||||
}
|
||||
|
||||
public Rule A() {
|
||||
return ChWS('a');
|
||||
}
|
||||
|
||||
public Rule STR() {
|
||||
return StringIgnoreCaseWS("STR");
|
||||
}
|
||||
|
||||
public Rule LANG() {
|
||||
return StringIgnoreCaseWS("LANG");
|
||||
}
|
||||
|
||||
public Rule LANGMATCHES() {
|
||||
return StringIgnoreCaseWS("LANGMATCHES");
|
||||
}
|
||||
|
||||
public Rule DATATYPE() {
|
||||
return StringIgnoreCaseWS("DATATYPE");
|
||||
}
|
||||
|
||||
public Rule BOUND() {
|
||||
return StringIgnoreCaseWS("BOUND");
|
||||
}
|
||||
|
||||
public Rule SAMETERM() {
|
||||
return StringIgnoreCaseWS("SAMETERM");
|
||||
}
|
||||
|
||||
public Rule ISIRI() {
|
||||
return StringIgnoreCaseWS("ISIRI");
|
||||
}
|
||||
|
||||
public Rule ISURI() {
|
||||
return StringIgnoreCaseWS("ISURI");
|
||||
}
|
||||
|
||||
public Rule ISBLANK() {
|
||||
return StringIgnoreCaseWS("ISBLANK");
|
||||
}
|
||||
|
||||
public Rule ISLITERAL() {
|
||||
return StringIgnoreCaseWS("ISLITERAL");
|
||||
}
|
||||
|
||||
public Rule REGEX() {
|
||||
return StringIgnoreCaseWS("REGEX");
|
||||
}
|
||||
|
||||
public Rule TRUE() {
|
||||
return StringIgnoreCaseWS("TRUE");
|
||||
}
|
||||
|
||||
public Rule FALSE() {
|
||||
return StringIgnoreCaseWS("FALSE");
|
||||
}
|
||||
|
||||
public Rule IRI_REF() {
|
||||
return Sequence(LESS_NO_COMMENT(), //
|
||||
ZeroOrMore(Sequence(TestNot(FirstOf(LESS_NO_COMMENT(), GREATER(), '"', OPEN_CURLY_BRACE(),
|
||||
CLOSE_CURLY_BRACE(), '|', '^', '\\', '`', CharRange('\u0000', '\u0020'))), ANY)), //
|
||||
GREATER());
|
||||
}
|
||||
|
||||
public Rule BLANK_NODE_LABEL() {
|
||||
return Sequence("_:", PN_LOCAL(), WS());
|
||||
}
|
||||
|
||||
public Rule VAR1() {
|
||||
return Sequence('?', VARNAME(), WS());
|
||||
}
|
||||
|
||||
public Rule VAR2() {
|
||||
return Sequence('$', VARNAME(), WS());
|
||||
}
|
||||
|
||||
public Rule LANGTAG() {
|
||||
return Sequence('@', OneOrMore(PN_CHARS_BASE()), ZeroOrMore(Sequence(
|
||||
MINUS(), OneOrMore(Sequence(PN_CHARS_BASE(), DIGIT())))), WS());
|
||||
}
|
||||
|
||||
public Rule INTEGER() {
|
||||
return Sequence(OneOrMore(DIGIT()), WS());
|
||||
}
|
||||
|
||||
public Rule DECIMAL() {
|
||||
return Sequence(FirstOf( //
|
||||
Sequence(OneOrMore(DIGIT()), DOT(), ZeroOrMore(DIGIT())), //
|
||||
Sequence(DOT(), OneOrMore(DIGIT())) //
|
||||
), WS());
|
||||
}
|
||||
|
||||
public Rule DOUBLE() {
|
||||
return Sequence(FirstOf(//
|
||||
Sequence(OneOrMore(DIGIT()), DOT(), ZeroOrMore(DIGIT()),
|
||||
EXPONENT()), //
|
||||
Sequence(DOT(), OneOrMore(DIGIT()), EXPONENT()), //
|
||||
Sequence(OneOrMore(DIGIT()), EXPONENT())), WS());
|
||||
}
|
||||
|
||||
public Rule INTEGER_POSITIVE() {
|
||||
return Sequence(PLUS(), INTEGER());
|
||||
}
|
||||
|
||||
public Rule DECIMAL_POSITIVE() {
|
||||
return Sequence(PLUS(), DECIMAL());
|
||||
}
|
||||
|
||||
public Rule DOUBLE_POSITIVE() {
|
||||
return Sequence(PLUS(), DOUBLE());
|
||||
}
|
||||
|
||||
public Rule INTEGER_NEGATIVE() {
|
||||
return Sequence(MINUS(), INTEGER());
|
||||
}
|
||||
|
||||
public Rule DECIMAL_NEGATIVE() {
|
||||
return Sequence(MINUS(), DECIMAL());
|
||||
}
|
||||
|
||||
public Rule DOUBLE_NEGATIVE() {
|
||||
return Sequence(MINUS(), DOUBLE());
|
||||
}
|
||||
|
||||
public Rule EXPONENT() {
|
||||
return Sequence(IgnoreCase('e'), Optional(FirstOf(PLUS(), MINUS())),
|
||||
OneOrMore(DIGIT()));
|
||||
}
|
||||
|
||||
public Rule STRING_LITERAL1() {
|
||||
return Sequence("'", ZeroOrMore(FirstOf(Sequence(TestNot(FirstOf("'",
|
||||
'\\', '\n', '\r')), ANY), ECHAR())), "'", WS());
|
||||
}
|
||||
|
||||
public Rule STRING_LITERAL2() {
|
||||
return Sequence('"', ZeroOrMore(FirstOf(Sequence(TestNot(AnyOf("\"\\\n\r")), ANY), ECHAR())), '"', WS());
|
||||
}
|
||||
|
||||
public Rule STRING_LITERAL_LONG1() {
|
||||
return Sequence("'''", ZeroOrMore(Sequence(
|
||||
Optional(FirstOf("''", "'")), FirstOf(Sequence(TestNot(FirstOf(
|
||||
"'", "\\")), ANY), ECHAR()))), "'''", WS());
|
||||
}
|
||||
|
||||
public Rule STRING_LITERAL_LONG2() {
|
||||
return Sequence("\"\"\"", ZeroOrMore(Sequence(Optional(FirstOf("\"\"", "\"")),
|
||||
FirstOf(Sequence(TestNot(FirstOf("\"", "\\")), ANY), ECHAR()))), "\"\"\"", WS());
|
||||
}
|
||||
|
||||
public Rule ECHAR() {
|
||||
return Sequence('\\', AnyOf("tbnrf\\\"\'"));
|
||||
}
|
||||
|
||||
public Rule PN_CHARS_U() {
|
||||
return FirstOf(PN_CHARS_BASE(), '_');
|
||||
}
|
||||
|
||||
public Rule VARNAME() {
|
||||
return Sequence(FirstOf(PN_CHARS_U(), DIGIT()), ZeroOrMore(FirstOf(
|
||||
PN_CHARS_U(), DIGIT(), '\u00B7', CharRange('\u0300', '\u036F'), CharRange('\u203F', '\u2040'))), WS());
|
||||
}
|
||||
|
||||
public Rule PN_CHARS() {
|
||||
return FirstOf(MINUS(), DIGIT(), PN_CHARS_U(), '\u00B7',
|
||||
CharRange('\u0300', '\u036F'), CharRange('\u203F', '\u2040'));
|
||||
}
|
||||
|
||||
public Rule PN_PREFIX() {
|
||||
return Sequence(PN_CHARS_BASE(), Optional(ZeroOrMore(FirstOf(PN_CHARS(), Sequence(DOT(), PN_CHARS())))));
|
||||
}
|
||||
|
||||
public Rule PN_LOCAL() {
|
||||
return Sequence(FirstOf(PN_CHARS_U(), DIGIT()),
|
||||
Optional(ZeroOrMore(FirstOf(PN_CHARS(), Sequence(DOT(), PN_CHARS())))), WS());
|
||||
}
|
||||
|
||||
public Rule PN_CHARS_BASE() {
|
||||
return FirstOf( //
|
||||
CharRange('A', 'Z'),//
|
||||
CharRange('a', 'z'), //
|
||||
CharRange('\u00C0', '\u00D6'), //
|
||||
CharRange('\u00D8', '\u00F6'), //
|
||||
CharRange('\u00F8', '\u02FF'), //
|
||||
CharRange('\u0370', '\u037D'), //
|
||||
CharRange('\u037F', '\u1FFF'), //
|
||||
CharRange('\u200C', '\u200D'), //
|
||||
CharRange('\u2070', '\u218F'), //
|
||||
CharRange('\u2C00', '\u2FEF'), //
|
||||
CharRange('\u3001', '\uD7FF'), //
|
||||
CharRange('\uF900', '\uFDCF'), //
|
||||
CharRange('\uFDF0', '\uFFFD') //
|
||||
);
|
||||
}
|
||||
|
||||
public Rule DIGIT() {
|
||||
return CharRange('0', '9');
|
||||
}
|
||||
|
||||
public Rule COMMENT() {
|
||||
return Sequence('#', ZeroOrMore(Sequence(TestNot(EOL()), ANY)), EOL());
|
||||
}
|
||||
|
||||
public Rule EOL() {
|
||||
return AnyOf("\n\r");
|
||||
}
|
||||
|
||||
public Rule REFERENCE() {
|
||||
return StringWS("^^");
|
||||
}
|
||||
|
||||
public Rule LESS_EQUAL() {
|
||||
return StringWS("<=");
|
||||
}
|
||||
|
||||
public Rule GREATER_EQUAL() {
|
||||
return StringWS(">=");
|
||||
}
|
||||
|
||||
public Rule NOT_EQUAL() {
|
||||
return StringWS("!=");
|
||||
}
|
||||
|
||||
public Rule AND() {
|
||||
return StringWS("&&");
|
||||
}
|
||||
|
||||
public Rule OR() {
|
||||
return StringWS("||");
|
||||
}
|
||||
|
||||
public Rule OPEN_BRACE() {
|
||||
return ChWS('(');
|
||||
}
|
||||
|
||||
public Rule CLOSE_BRACE() {
|
||||
return ChWS(')');
|
||||
}
|
||||
|
||||
public Rule OPEN_CURLY_BRACE() {
|
||||
return ChWS('{');
|
||||
}
|
||||
|
||||
public Rule CLOSE_CURLY_BRACE() {
|
||||
return ChWS('}');
|
||||
}
|
||||
|
||||
public Rule OPEN_SQUARE_BRACE() {
|
||||
return ChWS('[');
|
||||
}
|
||||
|
||||
public Rule CLOSE_SQUARE_BRACE() {
|
||||
return ChWS(']');
|
||||
}
|
||||
|
||||
public Rule SEMICOLON() {
|
||||
return ChWS(';');
|
||||
}
|
||||
|
||||
public Rule DOT() {
|
||||
return ChWS('.');
|
||||
}
|
||||
|
||||
public Rule PLUS() {
|
||||
return ChWS('+');
|
||||
}
|
||||
|
||||
public Rule MINUS() {
|
||||
return ChWS('-');
|
||||
}
|
||||
|
||||
public Rule ASTERISK() {
|
||||
return ChWS('*');
|
||||
}
|
||||
|
||||
public Rule COMMA() {
|
||||
return ChWS(',');
|
||||
}
|
||||
|
||||
public Rule NOT() {
|
||||
return ChWS('!');
|
||||
}
|
||||
|
||||
public Rule DIVIDE() {
|
||||
return ChWS('/');
|
||||
}
|
||||
|
||||
public Rule EQUAL() {
|
||||
return ChWS('=');
|
||||
}
|
||||
|
||||
public Rule LESS_NO_COMMENT() {
|
||||
return Sequence(Ch('<'), ZeroOrMore(WS_NO_COMMENT()));
|
||||
}
|
||||
|
||||
public Rule LESS() {
|
||||
return ChWS('<');
|
||||
}
|
||||
|
||||
public Rule GREATER() {
|
||||
return ChWS('>');
|
||||
}
|
||||
// </Lexer>
|
||||
|
||||
public Rule ChWS(char c) {
|
||||
return Sequence(Ch(c), WS());
|
||||
}
|
||||
|
||||
public Rule StringWS(String s) {
|
||||
return Sequence(String(s), WS());
|
||||
}
|
||||
|
||||
public Rule StringIgnoreCaseWS(String string) {
|
||||
return Sequence(IgnoreCase(string), WS());
|
||||
}
|
||||
|
||||
}
|
48
src/main/java/org/parboiled/examples/time/Main.java
Normal file
48
src/main/java/org/parboiled/examples/time/Main.java
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2011 Mathias Doenitz
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.parboiled.examples.time;
|
||||
|
||||
import org.parboiled.Parboiled;
|
||||
import org.parboiled.parserunners.RecoveringParseRunner;
|
||||
import org.parboiled.common.StringUtils;
|
||||
import static org.parboiled.support.ParseTreeUtils.printNodeTree;
|
||||
import org.parboiled.support.ParsingResult;
|
||||
|
||||
import java.util.Scanner;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
TimeParser parser = Parboiled.createParser(TimeParser.class);
|
||||
|
||||
while (true) {
|
||||
System.out.print("Enter a time expression (hh:mm(:ss)?, hh(mm(ss)?)? or h(mm)?, single RETURN to exit)!\n");
|
||||
String input = new Scanner(System.in).nextLine();
|
||||
if (StringUtils.isEmpty(input)) break;
|
||||
|
||||
ParsingResult<?> result = new RecoveringParseRunner(parser.Time()).run(input);
|
||||
|
||||
System.out.println(input + " = " + result.parseTreeRoot.getValue() + '\n');
|
||||
System.out.println(printNodeTree(result) + '\n');
|
||||
|
||||
if (!result.matched) {
|
||||
System.out.println(StringUtils.join(result.parseErrors, "---\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
103
src/main/java/org/parboiled/examples/time/TimeParser.java
Normal file
103
src/main/java/org/parboiled/examples/time/TimeParser.java
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2011 Mathias Doenitz
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.parboiled.examples.time;
|
||||
|
||||
import org.parboiled.BaseParser;
|
||||
import org.parboiled.Rule;
|
||||
import org.parboiled.annotations.BuildParseTree;
|
||||
|
||||
/**
|
||||
* Parser for very relaxed time literals. Demonstrates usage of the value stack with default values for unmatched rules.
|
||||
*/
|
||||
@BuildParseTree
|
||||
public class TimeParser extends BaseParser<Object> {
|
||||
|
||||
public Rule Time() {
|
||||
return FirstOf(Time_HH_MM_SS(), Time_HHMMSS(), Time_HMM());
|
||||
}
|
||||
|
||||
// h(h)?:mm(:ss)?
|
||||
Rule Time_HH_MM_SS() {
|
||||
return Sequence(
|
||||
OneOrTwoDigits(), ':',
|
||||
TwoDigits(),
|
||||
FirstOf(Sequence(':', TwoDigits()), push(0)),
|
||||
EOI,
|
||||
swap3() && push(convertToTime(popAsInt(), popAsInt(), popAsInt()))
|
||||
);
|
||||
}
|
||||
|
||||
// hh(mm(ss)?)?
|
||||
Rule Time_HHMMSS() {
|
||||
return Sequence(
|
||||
TwoDigits(),
|
||||
FirstOf(
|
||||
Sequence(
|
||||
TwoDigits(),
|
||||
FirstOf(TwoDigits(), push(0))
|
||||
),
|
||||
pushAll(0, 0)
|
||||
),
|
||||
EOI,
|
||||
swap3() && push(convertToTime(popAsInt(), popAsInt(), popAsInt()))
|
||||
);
|
||||
}
|
||||
|
||||
// h(mm)?
|
||||
Rule Time_HMM() {
|
||||
return Sequence(
|
||||
OneDigit(),
|
||||
FirstOf(TwoDigits(), push(0)),
|
||||
EOI,
|
||||
swap() && push(convertToTime(popAsInt(), popAsInt()))
|
||||
);
|
||||
}
|
||||
|
||||
Rule OneOrTwoDigits() {
|
||||
return FirstOf(TwoDigits(), OneDigit());
|
||||
}
|
||||
|
||||
Rule OneDigit() {
|
||||
return Sequence(Digit(), push(Integer.parseInt(matchOrDefault("0"))));
|
||||
}
|
||||
|
||||
Rule TwoDigits() {
|
||||
return Sequence(Sequence(Digit(), Digit()), push(Integer.parseInt(matchOrDefault("0"))));
|
||||
}
|
||||
|
||||
Rule Digit() {
|
||||
return CharRange('0', '9');
|
||||
}
|
||||
|
||||
// ************************* ACTIONS *****************************
|
||||
|
||||
protected Integer popAsInt() {
|
||||
return (Integer) pop();
|
||||
}
|
||||
|
||||
protected String convertToTime(Integer hours, Integer minutes) {
|
||||
return convertToTime(hours, minutes, 0);
|
||||
}
|
||||
|
||||
protected String convertToTime(Integer hours, Integer minutes, Integer seconds) {
|
||||
return String.format("%s h, %s min, %s s",
|
||||
hours != null ? hours : 0,
|
||||
minutes != null ? minutes : 0,
|
||||
seconds != null ? seconds : 0);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user