pax_global_header00006660000000000000000000000064144024274570014523gustar00rootroot0000000000000052 comment=5c0eaf86e39b91114420dd693dd741d4d99a93a3 parsington-parsington-3.1.0/000077500000000000000000000000001440242745700160725ustar00rootroot00000000000000parsington-parsington-3.1.0/.github/000077500000000000000000000000001440242745700174325ustar00rootroot00000000000000parsington-parsington-3.1.0/.github/build.sh000077500000000000000000000001611440242745700210660ustar00rootroot00000000000000#!/bin/sh curl -fsLO https://raw.githubusercontent.com/scijava/scijava-scripts/master/ci-build.sh sh ci-build.sh parsington-parsington-3.1.0/.github/setup.sh000077500000000000000000000002171440242745700211310ustar00rootroot00000000000000#!/bin/sh curl -fsLO https://raw.githubusercontent.com/scijava/scijava-scripts/master/ci-setup-github-actions.sh sh ci-setup-github-actions.sh parsington-parsington-3.1.0/.github/workflows/000077500000000000000000000000001440242745700214675ustar00rootroot00000000000000parsington-parsington-3.1.0/.github/workflows/build-main.yml000066400000000000000000000014071440242745700242350ustar00rootroot00000000000000name: build on: push: branches: - main tags: - "*-[0-9]+.*" jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Java uses: actions/setup-java@v3 with: java-version: '8' distribution: 'zulu' cache: 'maven' - name: Set up CI environment run: .github/setup.sh - name: Execute the build run: .github/build.sh env: GPG_KEY_NAME: ${{ secrets.GPG_KEY_NAME }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} MAVEN_USER: ${{ secrets.MAVEN_USER }} MAVEN_PASS: ${{ secrets.MAVEN_PASS }} OSSRH_PASS: ${{ secrets.OSSRH_PASS }} SIGNING_ASC: ${{ secrets.SIGNING_ASC }} parsington-parsington-3.1.0/.github/workflows/build-pr.yml000066400000000000000000000006701440242745700237330ustar00rootroot00000000000000name: build PR on: pull_request: branches: - main jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Java uses: actions/setup-java@v3 with: java-version: '8' distribution: 'zulu' cache: 'maven' - name: Set up CI environment run: .github/setup.sh - name: Execute the build run: .github/build.sh parsington-parsington-3.1.0/.gitignore000066400000000000000000000001301440242745700200540ustar00rootroot00000000000000# Maven # /target/ # Eclipse # /.project /.classpath /.settings/ # IntelliJ # /.idea/ parsington-parsington-3.1.0/.mailmap000066400000000000000000000000741440242745700175140ustar00rootroot00000000000000Jan Eglinger parsington-parsington-3.1.0/LICENSE.txt000066400000000000000000000025071440242745700177210ustar00rootroot00000000000000Copyright (c) 2015 - 2023, Board of Regents of the University of Wisconsin-Madison. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. parsington-parsington-3.1.0/README.md000066400000000000000000000160241440242745700173540ustar00rootroot00000000000000[![](https://img.shields.io/maven-central/v/org.scijava/parsington.svg)](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.scijava%22%20AND%20a%3A%22parsington%22) [![](https://github.com/scijava/parsington/actions/workflows/build-main.yml/badge.svg)](https://github.com/scijava/parsington/actions/workflows/build-main.yml) # Parsington Parsington is an infix-to-postfix and infix-to-syntax-tree expression parser for mathematical expressions written in Java. It is simple yet fancy, handling (customizable) operators, functions, variables and constants in a similar way to what the Java language itself supports. Parsington is part of the [SciJava](https://scijava.org/) project for scientific computing in Java. ## Example Infix input: `(-b + sqrt(b^2 - 4*a*c)) / (2*a)` → postfix output: `b - sqrt b 2 ^ 4 a * c * - (1) + (1) 2 a * (1) /` ## Rationale Expression parsers are as old as the hills; what makes this one different? * __No dependencies.__ * __[Available on Maven Central](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.scijava%22%20AND%20a%3A%22parsington%22).__ * __Permissive BSD-2 license.__ See [LICENSE.txt](LICENSE.txt). * __Separation of concerns.__ Parsington's parser stands alone, giving you a postfix queue or syntax tree from your infix expression, which you can then process or evaluate however you see fit. Parsington also provides an [eval subpackage](src/main/java/org/scijava/parsington/eval) that can evaluate expressions involving objects of common types (`java.lang.Boolean`, `java.lang.Number`, `java.lang.String`), and which is extensible to your own needs—there is no assumption that your variables will consist of any particular data type, numerical or otherwise. * __Extensibility.__ The [default operators](src/main/java/org/scijava/parsington/Operators.java), a synthesis of Java and MATLAB syntax, work well. But if you need something else, you can define your own unary and binary operators with whatever symbols, precedence and associativity you desire. * __Clean, well-commented codebase with unit tests.__ Import the source into your favorite IDE and watch Parsington in action by putting a breakpoint [here](src/main/java/org/scijava/parsington/ParseOperation.java#L70-L72). ## History The [ImageJ Ops](https://github.com/imagej/imagej-ops) project needed an expression parser so that it could be more awesome. But not one limited to primitive doubles, or one that conflated parsing with evaluation, or one licensed in a restrictive way. Just a simple infix parser: a nice shunting yard implementation, or maybe some lovely recursive descent. Something on GitHub, with no dependencies, available on Maven Central. But surprisingly, there was only tumbleweed. So Parsington was born, and all our problems are now over! ## Usage In your POM ``: ```xml org.scijava parsington 3.0.0 ``` ### Postfix queues To parse an infix expression to a postfix queue: ```java LinkedList queue = new ExpressionParser().parsePostfix("a+b*c^f(1,2)'"); // queue = [a, b, c, f, 1, 2, (2), , ^, ', *, +] ``` ### Syntax trees To parse an infix expression to a syntax tree: ```java SyntaxTree tree = new ExpressionParser().parseTree("a+b*c^f(1,2)'"); ``` ``` +-------+ | + | +---+---+ | +------+------+ | | +---+---+ +---+---+ | a | | * | +-------+ +---+---+ | +------+------+ | | +---+---+ +---+---+ | b | | ' | +-------+ +---+---+ | | +---+---+ | ^ | +---+---+ | +------+------+ | | +---+---+ +---+---+ | c | | | +-------+ +---+---+ | +------+------+ | | +---+---+ +---+---+ | f | | (2) | +-------+ +---+---+ | +------+------+ | | +---+---+ +---+---+ | 1 | | 2 | +-------+ +-------+ ``` ### Evaluation To evaluate an expression involving basic types: ```java Object result = new DefaultTreeEvaluator().evaluate("6.5*7.8^2.3"); ``` ### Interactive console There is also an [interactive console shell](src/main/java/org/scijava/parsington/Main.java) you can play with. Run it easily using [jgo](https://github.com/scijava/jgo): ``` jgo org.scijava:parsington ``` Or run from source, after cloning this repository: ```shell mvn java -jar target/parsington-*-SNAPSHOT.jar ``` Simple example invocations with the console's default evaluator: ``` > 6.5*7.8^2.3 732.3706691398969 : java.lang.Double > 2*3,4 ^ Misplaced separator or mismatched groups at index 4 ``` The `postfix` built-in function lets you introspect a parsed postfix queue: ``` > postfix('6.5*7.8^2.3') 6.5 : java.lang.Double 7.8 : java.lang.Double 2.3 : java.lang.Double ^ : org.scijava.parsington.Operator * : org.scijava.parsington.Operator > postfix('[1, 2f, 3d, 4., 5L, 123456789987654321, 9987654321234567899]') 1 : java.lang.Integer 2.0 : java.lang.Float 3.0 : java.lang.Double 4.0 : java.lang.Double 5 : java.lang.Long 123456789987654321 : java.lang.Long 9987654321234567899 : java.math.BigInteger [7] : org.scijava.parsington.Group > postfix('f(x, y) = x*y') f : org.scijava.parsington.Variable x : org.scijava.parsington.Variable y : org.scijava.parsington.Variable (2) : org.scijava.parsington.Group : org.scijava.parsington.Function x : org.scijava.parsington.Variable y : org.scijava.parsington.Variable * : org.scijava.parsington.Operator = : org.scijava.parsington.Operator > postfix('math.pow(q) = q^q') math : org.scijava.parsington.Variable pow : org.scijava.parsington.Variable . : org.scijava.parsington.Operator q : org.scijava.parsington.Variable (1) : org.scijava.parsington.Group : org.scijava.parsington.Function q : org.scijava.parsington.Variable q : org.scijava.parsington.Variable ^ : org.scijava.parsington.Operator = : org.scijava.parsington.Operator ``` The `tree` function is another way to introspect, in syntax tree form: ``` > tree('math.pow(q) = q^q') '=' - '' -- '.' --- 'math' --- 'pow' -- '(1)' --- 'q' - '^' -- 'q' -- 'q' : org.scijava.parsington.SyntaxTree ``` ## Customization Parsington supports various kinds of customization, including custom operators, custom separator symbols, and even custom parsing of literals and/or other expression elements. See the [TestExamples](src/test/java/org/scijava/parsington/TestExamples.java) for illustrations of these sorts of customizations in practice. parsington-parsington-3.1.0/pom.xml000066400000000000000000000063071440242745700174150ustar00rootroot00000000000000 4.0.0 org.scijava pom-scijava 34.0.0 parsington 3.1.0 Parsington: The SciJava Expression Parser A general-purpose mathematical expression parser, which converts infix expression strings into postfix queues and/or syntax trees. https://github.com/scijava/parsington 2015 SciJava https://scijava.org/ Simplified BSD License repo ctrueden Curtis Rueden https://imagej.net/people/ctrueden founder lead developer debugger reviewer support maintainer Jan Eglinger https://imagej.net/people/imagejan imagejan Matthew Miner mminer SciJava https://groups.google.com/group/scijava https://groups.google.com/group/scijava scijava@googlegroups.com https://groups.google.com/group/scijava scm:git:https://github.com/scijava/parsington scm:git:git@github.com:scijava/parsington parsington-3.1.0 https://github.com/scijava/parsington GitHub Issues https://github.com/scijava/parsington/issues GitHub Actions https://github.com/scijava/parsington/actions org.scijava.parsington.Main org.scijava.parsington bsd_2 Parsington: the SciJava mathematical expression parser. Board of Regents of the University of Wisconsin-Madison. org.junit.jupiter junit-jupiter-api test org.junit.jupiter junit-jupiter-engine test parsington-parsington-3.1.0/release.properties000066400000000000000000000023461440242745700216350ustar00rootroot00000000000000#release configuration #Thu Mar 09 13:10:39 CST 2023 scm.commentPrefix=[maven-release-plugin] exec.pomFileName=pom.xml pushChanges=false releaseStrategyId=default project.scm.org.scijava\:parsington.url=https\://github.com/scijava/parsington scm.tag=parsington-3.1.0 remoteTagging=true exec.additionalArguments=-Dgpg.skip\=true scm.branchCommitComment=@{prefix} prepare branch @{releaseLabel} projectVersionPolicyId=default scm.url=scm\:git\:https\://github.com/scijava/parsington scm.tagNameFormat=@{project.artifactId}-@{project.version} project.rel.org.scijava\:parsington=3.1.0 project.scm.org.scijava\:parsington.developerConnection=scm\:git\:git@github.com\:scijava/parsington project.dev.org.scijava\:parsington=3.1.1-SNAPSHOT pinExternals=false preparationGoals=clean verify scm.releaseCommitComment=@{prefix} prepare release @{releaseLabel} exec.snapshotReleasePluginAllowed=false exec.activateProfiles=deploy-to-scijava project.scm.org.scijava\:parsington.connection=scm\:git\:https\://github.com/scijava/parsington scm.developmentCommitComment=@{prefix} prepare for next development iteration scm.rollbackCommitComment=@{prefix} rollback the release of @{releaseLabel} project.scm.org.scijava\:parsington.tag=HEAD completedPhase=end-release parsington-parsington-3.1.0/src/000077500000000000000000000000001440242745700166615ustar00rootroot00000000000000parsington-parsington-3.1.0/src/main/000077500000000000000000000000001440242745700176055ustar00rootroot00000000000000parsington-parsington-3.1.0/src/main/java/000077500000000000000000000000001440242745700205265ustar00rootroot00000000000000parsington-parsington-3.1.0/src/main/java/org/000077500000000000000000000000001440242745700213155ustar00rootroot00000000000000parsington-parsington-3.1.0/src/main/java/org/scijava/000077500000000000000000000000001440242745700227355ustar00rootroot00000000000000parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/000077500000000000000000000000001440242745700251215ustar00rootroot00000000000000parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/ExpressionParser.java000066400000000000000000000204021440242745700312760ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.function.BiFunction; /** * A parser for mathematical expressions, using Dijkstra's famous shunting-yard * algorithm. *

* It is important to note that this parser does not attempt to * evaluate the expression in any way; rather, it only provides the * parsed tree according to the desired operators. *

* * @author Curtis Rueden */ public class ExpressionParser { private static final String DEFAULT_ELEMENT_SEPARATOR = ","; private static final String DEFAULT_STATEMENT_SEPARATOR = ";"; private final List operators; private final String elementSeparator; private final String statementSeparator; private final BiFunction parseOperationFactory; /** * Creates an expression parser with the standard set of operators and default * separator symbols ({@code ,} for group elements, {@code ;} for statements). * * @see Operators#standardList() */ public ExpressionParser() { this(Operators.standardList()); } /** * Creates an expression parser with custom operators and default separator * symbols ({@code ,} for group elements, {@code ;} for statements). * * @param operators The collection of operators available to expressions. */ public ExpressionParser(final Collection operators) { this(operators, DEFAULT_ELEMENT_SEPARATOR, DEFAULT_STATEMENT_SEPARATOR); } /** * Creates an expression parser with custom separator symbols. * * @param elementSeparator The symbol to use for separating group elements. * @param statementSeparator The symbol to use for separating statements. */ public ExpressionParser(final String elementSeparator, final String statementSeparator) { this(Operators.standardList(), elementSeparator, statementSeparator); } /** * Creates an expression parser with custom {@link ParseOperation} behavior. * Customizing this behavior allows you to control lower level parsing * characteristics, including what character sequences constitute whitespace, * literals, variables, operators, group terminators, element separators, and * statement separators. * * @param parseOperationFactory A function producing {@link ParseOperation} * objects with behavior customized to your requirements. The typical * use case is to subclass {@link ParseOperation} to override one or * more of its {@code parseSomething} methods, and then pass * {@code MyCustomParseOperation::new} for this argument. */ public ExpressionParser( final BiFunction parseOperationFactory) { this(Operators.standardList(), DEFAULT_ELEMENT_SEPARATOR, DEFAULT_STATEMENT_SEPARATOR, parseOperationFactory); } /** * Creates an expression parser with custom operators and separator symbols. * * @param operators The collection of operators available to expressions. * @param elementSeparator The symbol to use for separating group elements. * @param statementSeparator The symbol to use for separating statements. */ public ExpressionParser(final Collection operators, final String elementSeparator, final String statementSeparator) { this(operators, elementSeparator, statementSeparator, ParseOperation::new); } /** * Creates an expression parser maximally customized to your requirements! * * @param operators The collection of operators available to expressions. * @param elementSeparator The symbol to use for separating group elements. * @param statementSeparator The symbol to use for separating statements. * @param parseOperationFactory A function producing {@link ParseOperation} * objects with behavior customized to your requirements. The typical * use case is to subclass {@link ParseOperation} to override one or * more of its {@code parseSomething} methods, and then pass * {@code MyCustomParseOperation::new} for this argument. */ public ExpressionParser(final Collection operators, final String elementSeparator, final String statementSeparator, final BiFunction parseOperationFactory) { final List operatorsList = new ArrayList<>(operators); // NB: Ensure operators with longer symbols come first. // This prevents e.g. '-' from being matched before '-=' and '--'. Collections.sort(operatorsList, (o1, o2) -> { final String t1 = o1.getToken(); final String t2 = o2.getToken(); final int len1 = t1.length(); final int len2 = t2.length(); if (len1 > len2) return -1; // o1 is longer, so o1 comes first. if (len1 < len2) return 1; // o2 is longer, so o2 comes first. return t1.compareTo(t2); }); this.operators = Collections.unmodifiableList(operatorsList); this.elementSeparator = elementSeparator; this.statementSeparator = statementSeparator; this.parseOperationFactory = parseOperationFactory; } // -- ExpressionParser methods -- /** * Parses the given mathematical expression into a syntax tree. * * @param expression The mathematical expression to parse. * @return Parsed hierarchy of tokens. * @throws IllegalArgumentException if the syntax of the expression is * incorrect. */ public SyntaxTree parseTree(final String expression) { return new SyntaxTree(parsePostfix(expression)); } /** * Parses the given mathematical expression into a queue in Reverse Polish * notation (i.e., postfix notation). * * @param expression The mathematical expression to parse. * @return Parsed queue of tokens in postfix notation. * @throws IllegalArgumentException if the syntax of the expression is * incorrect. */ public LinkedList parsePostfix(final String expression) { return parseOperationFactory.apply(this, expression).parsePostfix(); } /** * Gets the list of operators available to expressions. * * @return A read-only view of the operators list. */ public List operators() { return operators; } /** * Separator symbol between group elements. The default symbol is comma * ({@code ,}). Example: {@code f(1, 2)} * * @see ExpressionParser#ExpressionParser(Collection, String, String) */ public String elementSeparator() { return elementSeparator; } /** * Separator symbol between statements. The default symbol is semicolon * ({@code ;}). Example: {@code x = 1; y = 2; z = x + y} * * @see ExpressionParser#ExpressionParser(Collection, String, String) */ public String statementSeparator() { return statementSeparator; } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/Function.java000066400000000000000000000042171440242745700275550ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; /** * A function is an implicit binary operator between two "noun" * tokens—typically between a variable on the left and a group on the * right, in which case the function's precedence is inferred from the group. *

* Examples: *

*
    *
  • {@code f()} → {@code f (0) }
  • *
  • {@code f(a)} → {@code f a (1) }
  • *
  • {@code f(a, b)} → {@code f a b (2) }
  • *
  • {@code f(g(a))} → {@code f g a (1) (1) }
  • *
* * @author Curtis Rueden */ public class Function extends Operator { public Function(final double precedence) { super("", 2, Associativity.LEFT, precedence); } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/Group.java000066400000000000000000000100751440242745700270630ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; /** * A group is a special N-ary operator delineated by a left-hand symbol and a * right-hand symbol, with comma-separated arguments. *

* Typically, these are various forms of parentheses, although in principle any * pair of two distinct symbols is allowed. *

* * @author Curtis Rueden */ public class Group extends Operator { private final String terminator; private int arity; /** * Creates a new group. * * @param initiator The lefthand symbol signaling the start of the group. * @param terminator The righthand symbol signaling the end of the group. * @param precedence The group's operator precedence. */ public Group(final String initiator, final String terminator, final double precedence) { super(initiator, 0, Associativity.NONE, precedence); this.terminator = terminator; } // -- Group methods -- /** * Gets the group's terminator symbol. (Use {@link #getToken()} to obtain the * initiator symbol.) * * @return The terminator symbol. */ public String getTerminator() { return terminator; } /** Increments the group's arity. */ public void incArity() { arity++; } /** * Gets whether the given group is the same as this one, in terms of token * (lefthand symbol), terminator (righthand symbol) and precedence. *

* Note that this method intentionally does not compare arity; the idea is * that if you have a {@link Group} and call {@link #instance} to duplicate * it, that copy will match this one, even though the copy initially starts at * arity 0. *

* * @param g The group to compare with this one. * @return True iff the given group is the same as this one, in terms of * token, terminator, and precedence. */ public boolean matches(final Group g) { return getToken().equals(g.getToken()) && getTerminator().equals(g.getTerminator()) && getPrecedence() == g.getPrecedence(); } // -- Operator methods -- @Override public int getArity() { return arity; } @Override public boolean isInfix() { return true; } @Override public boolean isPrefix() { return true; } /** * Creates an instance of a group operator, using this one as a template. *

* The created group will have the same initiator and terminator symbols, as * well as the same precedence. But it will begin as a nullary group until * {@link #incArity} is called. *

*/ @Override public Group instance() { return new Group(getToken(), getTerminator(), getPrecedence()); } // -- Object methods -- @Override public String toString() { return getToken() + getArity() + getTerminator(); } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/Literals.java000066400000000000000000000546661440242745700275640ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; import java.math.BigDecimal; import java.math.BigInteger; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Utility methods for parsing literals from strings. These methods largely * conform to the Java specification's ideas of what constitutes a numeric or * string literal. * * @author Curtis Rueden */ public final class Literals { private static final Pattern HEX = Pattern.compile( "(([-+]?)0[Xx]([0-9a-fA-F]+)" + "([Ll]|(\\.[0-9a-fA-F]*)?[Pp]([-+]?)([0-9]+)([Dd]|[Ff]|)|)).*"); private static final Pattern BINARY = Pattern.compile( "(([-+]?)0[Bb]([01]+)([Ll]?)).*"); private static final Pattern OCTAL = Pattern.compile( "(([-+]?)0([0-7]+)([Ll]?)).*"); private static final Pattern DECIMAL = Pattern.compile( "(([-+]?[0-9]+(\\.[0-9]*)?([Ee][-+]?[0-9]+)?)([Dd]|[Ff]|[Ll])?).*"); private Literals() { // NB: Prevent instantiation of utility class. } /** * Parses a boolean literal (i.e., true and false). * * @param s The string from which the boolean literal should be parsed. * @return The parsed boolean value—either {@link Boolean#TRUE} or * {@link Boolean#FALSE}— or null if the string does not begin * with a boolean literal. */ public static Boolean parseBoolean(final CharSequence s) { return parseBoolean(s, new Position()); } /** * Parses a string literal which is enclosed in single or double quotes. *

* For literals in double quotes, this parsing mechanism is intended to be as * close as possible to the numeric literals supported by the Java programming * language itself. Literals in single quotes are completely verbatim, with no * escaping performed. *

* * @param s The string from which the string literal should be parsed. * @return The parsed string value, unescaped according to Java conventions. * Returns null if the string does not begin with a single or double * quote. */ public static String parseString(final CharSequence s) { return parseString(s, new Position()); } /** * Parses a hexidecimal literal. Both hexadecimal integer (e.g., * {@code 0xfedcba9876543210}) and hexidecimal floating point (e.g., * {@code 0xfedcba.98765432p10f}) are supported. * * @param s The string from which the numeric literal should be parsed. * @return The parsed numeric value. For hexidecimal integers, returns an * {@link Integer} if sufficiently small; or a {@link Long} if needed * or if the {@code L} suffix is given; or a {@link BigInteger} if the * value is too large even for {@code long}. For hexidecimal floating * point, returns a {@link Float} if sufficiently small and the * {@code F} suffix is given; or a {@link Double} otherwise (the * {@code D} suffix is optional). */ public static Number parseHex(final CharSequence s) { return parseHex(s, new Position()); } /** * Parses a binary literal (e.g., {@code 0b010101000011}). * * @param s The string from which the numeric literal should be parsed. * @return The parsed numeric value—an {@link Integer} if sufficiently * small, or a {@link Long} if needed or if the {@code L} suffix is * given; or a {@link BigInteger} if the value is too large even for * {@code long}. */ public static Number parseBinary(final CharSequence s) { return parseBinary(s, new Position()); } /** * Parses an octal literal (e.g., {@code 01234567}). * * @param s The string from which the numeric literal should be parsed. * @return The parsed numeric value—an {@link Integer} if sufficiently * small, or a {@link Long} if needed or if the {@code L} suffix is * given; or a {@link BigInteger} if the value is too large even for * {@code long}. */ public static Number parseOctal(final CharSequence s) { return parseOctal(s, new Position()); } /** * Parses a decimal literal (integer or otherwise; e.g., {@code 1234567890}, * {@code 1234.0987} or {@code 1.2e34}). * * @param s The string from which the numeric literal should be parsed. * @return The parsed numeric value, of a type consistent with Java's support * for numeric primitives—or for values outside the normal range * of Java primitives, {@link BigInteger} or {@link BigDecimal} as * appropriate. Returns null if the string does not begin with the * numeric literal telltale of a 0-9 digit with optional leading sign. */ public static Number parseDecimal(final CharSequence s) { return parseDecimal(s, new Position()); } /** * Parses a numeric literal of any known type. *

* This parsing mechanism is intended to be as close as possible to the * numeric literals supported by the Java programming language itself. *

* * @param s The string from which the numeric literal should be parsed. * @return The parsed numeric value, of a type consistent with Java's support * for numeric primitives—or for values outside the normal range * of Java primitives, {@link BigInteger} or {@link BigDecimal} as * appropriate. Returns null if the string does not begin with the * numeric literal telltale of a 0-9 digit with optional leading sign. */ public static Number parseNumber(final CharSequence s) { return parseNumber(s, new Position()); } /** * Parses a literal of any known type (booleans, strings and numbers). * * @param s The string from which the literal should be parsed. * @return The parsed value, of a type consistent with Java's support for * literals: either {@link Boolean}, {@link String} or a concrete * {@link Number} subclass. Returns null if the string does * not match the syntax of a known literal. * @see #parseBoolean(CharSequence) * @see #parseString(CharSequence) * @see #parseNumber(CharSequence) */ public static Object parseLiteral(final CharSequence s) { return parseLiteral(s, new Position()); } /** * Parses a boolean literal (i.e., true and false). * * @param s The string from which the boolean literal should be parsed. * @param pos The offset from which the literal should be parsed. If parsing * is successful, the position will be advanced to the next index * after the parsed literal. * @return The parsed boolean value—either {@link Boolean#TRUE} or * {@link Boolean#FALSE}— or null if the string does not begin * with a boolean literal. */ public static Boolean parseBoolean(final CharSequence s, final Position pos) { if (isWord(s, pos, "true")) { pos.inc(4); return Boolean.TRUE; } if (isWord(s, pos, "false")) { pos.inc(5); return Boolean.FALSE; } return null; } /** * Parses a string literal which is enclosed in single or double quotes. *

* For literals in double quotes, this parsing mechanism is intended to be as * close as possible to the numeric literals supported by the Java programming * language itself. Literals in single quotes are completely verbatim, with no * escaping performed. *

* * @param s The string from which the string literal should be parsed. * @param pos The offset from which the literal should be parsed. If parsing * is successful, the position will be advanced to the next index * after the parsed literal. * @return The parsed string value, unescaped according to Java conventions. * Returns null if the string does not begin with a single or double * quote. */ public static String parseString(final CharSequence s, final Position pos) { final char quote = pos.ch(s); if (quote != '"' && quote != '\'') return null; int index = pos.get() + 1; boolean escaped = false; final StringBuilder sb = new StringBuilder(); while (true) { if (index >= s.length()) pos.die("Unclosed string literal"); final char c = s.charAt(index); if (escaped) { escaped = false; if (isOctal(c)) { // octal sequence String octal = "" + c; final char c1 = pos.ch(s, index + 1); if (isOctal(c1)) { octal += c1; if (c >= '0' && c <= '3') { final char c2 = pos.ch(s, index + 2); if (isOctal(c2)) octal += c2; } } sb.append((char) Integer.parseInt(octal, 8)); index += octal.length(); continue; } switch (c) { case 'b': // backspace sb.append('\b'); break; case 't': // tab sb.append('\t'); break; case 'n': // linefeed sb.append('\n'); break; case 'f': // form feed sb.append('\f'); break; case 'r': // carriage return sb.append('\r'); break; case '"': // double quote sb.append('"'); break; case '\\': // backslash sb.append('\\'); break; case 'u': // unicode sequence final char u1 = hex(s, pos, index + 1); final char u2 = hex(s, pos, index + 2); final char u3 = hex(s, pos, index + 3); final char u4 = hex(s, pos, index + 4); sb.append((char) Integer.parseInt("" + u1 + u2 + u3 + u4, 16)); index += 4; break; default: // invalid escape pos.die("Invalid escape sequence"); } } else if (c == '\\' && quote == '"') escaped = true; else if (c == quote) break; else sb.append(c); index++; } pos.set(index + 1); return sb.toString(); } /** * Parses a hexidecimal literal. Both hexadecimal integer (e.g., * {@code 0xfedcba9876543210}) and hexidecimal floating point (e.g., * {@code 0xfedcba.98765432p10f}) are supported. * * @param s The string from which the numeric literal should be parsed. * @param pos The offset from which the literal should be parsed. If parsing * is successful, the position will be advanced to the next index * after the parsed literal. * @return The parsed numeric value. For hexidecimal integers, returns an * {@link Integer} if sufficiently small; or a {@link Long} if needed * or if the {@code L} suffix is given; or a {@link BigInteger} if the * value is too large even for {@code long}. For hexidecimal floating * point, returns a {@link Float} if sufficiently small and the * {@code F} suffix is given; or a {@link Double} otherwise (the * {@code D} suffix is optional). In either case, returns {@code null} * if the string does not begin with the numeric literal telltale of a * 0-9 digit with optional leading sign. */ public static Number parseHex(final CharSequence s, final Position pos) { if (!isNumberSyntax(s, pos)) return null; final Matcher m = matcher(HEX, s, pos); if (!m.matches()) return null; final String sign = m.group(2); // + or - or nothing final String integer = m.group(3); // hex digits before decimal point final String suffix = m.group(4); // L or floating point expression final boolean forceLong = "L".equalsIgnoreCase(suffix); final Number result; if (forceLong || suffix.isEmpty()) { // Integer notation. final String number = sign + integer; result = parseInteger(number, forceLong, 16); } else { // Floating point notation. final String token = m.group(1); // entire matched literal //final String mantissa = m.group(5); // dot & hex digits after decimal point //final String expSign = m.group(6); // + or - or nothing //final String exp = m.group(7); // decimal exponent final String expSuffix = m.group(8); // f or d or nothing final boolean forceFloat = "F".equalsIgnoreCase(expSuffix); final boolean forceDouble = "D".equalsIgnoreCase(expSuffix); // NB: The BigDecimal code does not understand floating point // hex strings, so the following invocation will never produce // a larger-than-double-precision floating point BigDecimal. // It's a convenient way to support float and double precision, // but for BigDecimal support, we would need to process the // matched groups above, converting hex to base 10 first. result = parseDecimal(token, forceFloat, forceDouble); } return verifyResult(result, m, pos); } /** * Parses a binary literal (e.g., {@code 0b010101000011}). * * @param s The string from which the numeric literal should be parsed. * @param pos The offset from which the literal should be parsed. If parsing * is successful, the position will be advanced to the next index * after the parsed literal. * @return The parsed numeric value—an {@link Integer} if sufficiently * small, or a {@link Long} if needed or if the {@code L} suffix is * given; or a {@link BigInteger} if the value is too large even for * {@code long}; or {@code null} if the string does not begin with the * numeric literal telltale of a 0-9 digit with optional leading sign. */ public static Number parseBinary(final CharSequence s, final Position pos) { return parseInteger(BINARY, s, pos, 2); } /** * Parses an octal literal (e.g., {@code 01234567}). * * @param s The string from which the numeric literal should be parsed. * @param pos The offset from which the literal should be parsed. If parsing * is successful, the position will be advanced to the next index * after the parsed literal. * @return The parsed numeric value—an {@link Integer} if sufficiently * small, or a {@link Long} if needed or if the {@code L} suffix is * given; or a {@link BigInteger} if the value is too large even for * {@code long}; or {@code null} if the string does not begin with the * numeric literal telltale of a 0-9 digit with optional leading sign. */ public static Number parseOctal(final CharSequence s, final Position pos) { return parseInteger(OCTAL, s, pos, 8); } /** * Parses a decimal literal (e.g., {@code 1234.0987} or {@code 1.2e34}). * * @param s The string from which the numeric literal should be parsed. * @param pos The offset from which the literal should be parsed. If parsing * is successful, the position will be advanced to the next index * after the parsed literal. * @return The parsed numeric value, of a type consistent with Java's support * for numeric primitives—or for values outside the normal range * of Java primitives, {@link BigInteger} or {@link BigDecimal} as * appropriate. Returns null if the string does not begin with the * numeric literal telltale of a 0-9 digit with optional leading sign. */ public static Number parseDecimal(final CharSequence s, final Position pos) { if (!isNumberSyntax(s, pos)) return null; final Matcher m = matcher(DECIMAL, s, pos); if (!m.matches()) return null; final String number = m.group(2); final String force = m.group(5); final boolean forceLong = "l".equalsIgnoreCase(force); final boolean forceFloat = "f".equalsIgnoreCase(force); final boolean forceDouble = "d".equalsIgnoreCase(force); Number result = null; if (!forceFloat && !forceDouble) { result = parseInteger(number, forceLong, 10); } if (result == null && !forceLong) { result = parseDecimal(number, forceFloat, forceDouble); } return verifyResult(result, m, pos); } /** * Parses a numeric literal of any known type. *

* This parsing mechanism is intended to be as close as possible to the * numeric literals supported by the Java programming language itself. *

* * @param s The string from which the numeric literal should be parsed. * @param pos The offset from which the literal should be parsed. If parsing * is successful, the position will be advanced to the next index * after the parsed literal. * @return The parsed numeric value, of a type consistent with Java's support * for numeric primitives—or for values outside the normal range * of Java primitives, {@link BigInteger} or {@link BigDecimal} as * appropriate. Returns null if the string does not begin with the * numeric literal telltale of a 0-9 digit with optional leading sign. */ public static Number parseNumber(final CharSequence s, final Position pos) { final Number hex = parseHex(s, pos); if (hex != null) return hex; final Number binary = parseBinary(s, pos); if (binary != null) return binary; final Number octal = parseOctal(s, pos); if (octal != null) return octal; final Number decimal = parseDecimal(s, pos); if (decimal != null) return decimal; return null; } /** * Parses a literal of any known type (booleans, strings and numbers). * * @param s The string from which the literal should be parsed. * @param pos The offset from which the literal should be parsed. If parsing * is successful, the position will be advanced to the next index * after the parsed literal. * @return The parsed value, of a type consistent with Java's support for * literals: either {@link Boolean}, {@link String} or a concrete * {@link Number} subclass. Returns null if the string does * not match the syntax of a known literal. * @see #parseBoolean(CharSequence, Position) * @see #parseString(CharSequence, Position) * @see #parseNumber(CharSequence, Position) */ public static Object parseLiteral(final CharSequence s, final Position pos) { final Boolean bool = parseBoolean(s, pos); if (bool != null) return bool; final String str = parseString(s, pos); if (str != null) return str; final Number num = parseNumber(s, pos); if (num != null) return num; return null; } // -- Helper methods -- private static boolean isOctal(final char c) { return c >= '0' && c <= '7'; } private static char hex(final CharSequence s, final Position pos, final int index) { final char c = pos.ch(s, index); if (c >= '0' && c <= '9') return c; if (c >= 'a' && c <= 'f') return c; if (c >= 'A' && c <= 'F') return c; pos.die("Invalid unicode sequence"); return '\0'; // NB: Unreachable. } private static boolean isNumberSyntax(final CharSequence s, final Position pos) { final int i = pos.get(); final boolean sign = s.charAt(i) == '-' || s.charAt(i) == '+'; final int digitIndex = sign ? i + 1 : i; if (digitIndex >= s.length()) return false; // at end of string final char digit = s.charAt(digitIndex); return digit >= '0' && digit <= '9'; } private static Number parseInteger(final Pattern p, final CharSequence s, final Position pos, final int base) { if (!isNumberSyntax(s, pos)) return null; final Matcher m = matcher(p, s, pos); if (!m.matches()) return null; final String sign = m.group(2); final String number = sign + m.group(3); final boolean forceLong = !m.group(4).isEmpty(); final Number result = parseInteger(number, forceLong, base); return verifyResult(result, m, pos); } private static Number parseInteger(final String number, final boolean forceLong, final int base) { if (!forceLong) { // Try to fit it into an int. try { return Integer.parseInt(number, base); } catch (final NumberFormatException exc) { // NB: No action needed. } } // Try to fit it into a long. try { return Long.parseLong(number, base); } catch (final NumberFormatException exc) { // NB: No action needed. } if (!forceLong) { // Try to treat it as a BigInteger. try { return new BigInteger(number, base); } catch (final NumberFormatException exc) { // NB: No action needed. } } return null; } private static Number parseDecimal(final String number, final boolean forceFloat, final boolean forceDouble) { if (forceFloat) { // Try to fit it into a flaot. try { return Float.parseFloat(number); } catch (final NumberFormatException exc) { // NB: No action needed. } } else { // Try to fit it into a double. try { return Double.parseDouble(number); } catch (final NumberFormatException exc) { // NB: No action needed. } } if (!forceDouble && !forceFloat) { // Try to treat it as a BigDecimal. try { return new BigDecimal(number); } catch (final NumberFormatException exc) { // NB: No action needed. } } return null; } private static Matcher matcher(final Pattern p, final CharSequence s, final Position pos) { return p.matcher(sub(s, pos)); } private static CharSequence sub(final CharSequence s, final Position pos) { return pos.get() == 0 ? s : new SubSequence(s, pos.get()); } private static Number verifyResult(final Number result, final Matcher m, final Position pos) { if (result == null) pos.die("Illegal numeric literal"); pos.inc(m.group(1).length()); return result; } private static boolean isWord(final CharSequence s, final Position pos, final String word) { if (s.length() - pos.get() < word.length()) return false; for (int i=0; i= 'a' && next <= 'z') return false; if (next >= 'A' && next <= 'Z') return false; if (next >= '0' && next <= '9') return false; if (next == '_') return false; return true; } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/Main.java000066400000000000000000000046041440242745700266540ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; import java.io.IOException; import org.scijava.parsington.eval.DefaultTreeEvaluator; import org.scijava.parsington.eval.EvaluatorConsole; /** * Launches the console-driven expression evaluator. * * @author Curtis Rueden * @see EvaluatorConsole */ public final class Main { private Main() { // Prevent instantiation of utility class. } // -- Main method -- public static void main(final String[] args) throws IOException { final DefaultTreeEvaluator evaluator = new DefaultTreeEvaluator(); if (args.length > 0) { // Evaluate the given expressions. for (final String expression : args) { Object result = evaluator.evaluate(expression); if (result instanceof Variable) { // Unwrap the variable. result = evaluator.get((Variable) result); } System.out.println(result); } } else { // Show the REPL. new EvaluatorConsole(evaluator).showConsole(); } } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/Operator.java000066400000000000000000000115301440242745700275570ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; /** * A mathematical operator is a special symbol or "verb" which defines a * relation between "nouns" (i.e.: literals and variables). For binary * operators, it is infix (between the nouns); for unary operators, it is either * prefix (preceding the noun) or postfix (following the noun). * * @author Curtis Rueden */ public class Operator extends Token implements Comparable { public enum Associativity { EITHER, LEFT, RIGHT, NONE } private final int arity; private final Associativity associativity; private final double precedence; public Operator(final String symbol, final int arity, final Associativity associativity, final double precedence) { super(symbol); this.arity = arity; this.associativity = associativity; this.precedence = precedence; } // -- Operator methods -- /** * Gets the operator's arity. * * @return The arity of the operator: 1 for unary, 2 for binary, etc. */ public int getArity() { return arity; } /** * Gets the operator's associativity. * * @return One of {@link Associativity#EITHER}, {@link Associativity#LEFT}, * {@link Associativity#RIGHT}, or {@link Associativity#NONE}. */ public Associativity getAssociativity() { return associativity; } /** * Gets whether the operator is left associative. * * @return True iff the operator's associativity is {@link Associativity#LEFT} * or {@link Associativity#EITHER}. */ public boolean isLeftAssociative() { final Associativity a = getAssociativity(); return a == Associativity.LEFT || a == Associativity.EITHER; } /** * Gets whether the operator is right associative. * * @return True iff the operator's associativity is * {@link Associativity#RIGHT} or {@link Associativity#EITHER}. */ public boolean isRightAssociative() { final Associativity a = getAssociativity(); return a == Associativity.RIGHT || a == Associativity.EITHER; } /** * Gets whether the operator is an infix operator (e.g., {@code a-b}). * * @return True iff the operator is an infix operator. */ public boolean isInfix() { return getArity() > 1; } /** * Gets whether the operator is a prefix operator (e.g., {@code -a}). * * @return True iff the operator is a prefix operator. */ public boolean isPrefix() { return getArity() == 1 && isRightAssociative(); } /** * Gets whether the operator is a postfix operator (e.g., {@code a'}). * * @return True iff the operator is a postfix operator. */ public boolean isPostfix() { return getArity() == 1 && isLeftAssociative(); } /** * Gets the operator precedence. Larger is higher. * * @return The operator precedence. */ public double getPrecedence() { return precedence; } /** * Gets an instance of the operator, using this one as a template. *

* For stateless operators, no copy will be made. But for operators with state * (e.g. {@link Group}), a new instance will be returned. *

* * @return {@code this} or a new instance, depending on the type of operator. */ public Operator instance() { // NB: Properties are immutable, so instance can be reused. return this; } // -- Comparable methods -- @Override public int compareTo(final Operator that) { final double thisP = getPrecedence(); final double thatP = that.getPrecedence(); if (thisP == thatP) return 0; return thisP < thatP ? -1 : 1; } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/Operators.java000066400000000000000000000201661440242745700277470ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; import static org.scijava.parsington.Operator.Associativity.LEFT; import static org.scijava.parsington.Operator.Associativity.RIGHT; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import org.scijava.parsington.Operator.Associativity; /** * A collection of standard {@link Operator}s. This set of operators was * synthesized by combining Java standard operators and MATLAB standard operators. *

* Note that the {@code ^} operator is assigned MATLAB's meaning of * exponentiation, rather than Java's meaning of bitwise XOR. And the {@code :} * operator—which in MATLAB resides at a precedence between the shift and * relational operators—is instead assigned a lower precedence to * facilitate support for a Java-like ternary {@code ?:} operation. *

* * @author Curtis Rueden */ public final class Operators { // -- dot -- public static final Operator DOT = op(".", 2, LEFT, 16); // -- groups -- public static final Group PARENS = group("(", ")", 16); public static final Group BRACKETS = group("[", "]", 16); public static final Group BRACES = group("{", "}", 16); // -- transpose, power -- public static final Operator TRANSPOSE = op("'", 1, LEFT, 15); public static final Operator DOT_TRANSPOSE = op(".'", 1, LEFT, 15); public static final Operator POW = op("^", 2, RIGHT, 15); public static final Operator DOT_POW = op(".^", 2, RIGHT, 15); // -- postfix -- public static final Operator POST_INC = op("++", 1, LEFT, 14); public static final Operator POST_DEC = op("--", 1, LEFT, 14); // -- unary -- public static final Operator PRE_INC = op("++", 1, RIGHT, 13); public static final Operator PRE_DEC = op("--", 1, RIGHT, 13); public static final Operator POS = op("+", 1, RIGHT, 13); public static final Operator NEG = op("-", 1, RIGHT, 13); public static final Operator COMPLEMENT = op("~", 1, RIGHT, 13); public static final Operator NOT = op("!", 1, RIGHT, 13); // -- multiplicative -- public static final Operator MUL = op("*", 2, LEFT, 12); public static final Operator DIV = op("/", 2, LEFT, 12); public static final Operator MOD = op("%", 2, LEFT, 12); public static final Operator RIGHT_DIV = op("\\", 2, LEFT, 12); public static final Operator DOT_MUL = op(".*", 2, LEFT, 12); public static final Operator DOT_DIV = op("./", 2, LEFT, 12); public static final Operator DOT_RIGHT_DIV = op(".\\", 2, LEFT, 12); // -- additive -- public static final Operator ADD = op("+", 2, LEFT, 11); public static final Operator SUB = op("-", 2, LEFT, 11); // -- shift -- public static final Operator LEFT_SHIFT = op("<<", 2, LEFT, 10); public static final Operator RIGHT_SHIFT = op(">>", 2, LEFT, 10); public static final Operator UNSIGNED_RIGHT_SHIFT = op(">>>", 2, LEFT, 10); // -- relational -- public static final Operator LESS_THAN = op("<", 2, LEFT, 8); public static final Operator GREATER_THAN = op(">", 2, LEFT, 8); public static final Operator LESS_THAN_OR_EQUAL = op("<=", 2, LEFT, 8); public static final Operator GREATER_THAN_OR_EQUAL = op(">=", 2, LEFT, 8); public static final Operator INSTANCEOF = op("instanceof", 2, LEFT, 8); // -- equality -- public static final Operator EQUAL = op("==", 2, LEFT, 7); public static final Operator NOT_EQUAL = op("!=", 2, LEFT, 7); // -- bitwise AND -- public static final Operator BITWISE_AND = op("&", 2, LEFT, 6); // -- bitwise exclusive OR -- // NB: No bitwise XOR operator, because '^' is reserved for POW above. //public static final Operator BITWISE_XOR = op("^", 2, LEFT, 5); // -- bitwise inclusive OR -- public static final Operator BITWISE_OR = op("|", 2, LEFT, 4); // -- logical AND -- public static final Operator LOGICAL_AND = op("&&", 2, LEFT, 3); // -- logical OR -- public static final Operator LOGICAL_OR = op("||", 2, LEFT, 2); // -- ternary -- // NB: These will not be parsed as true ternary operators. // But the behavior can be simulated at evaluation time. public static final Operator QUESTION = op("?", 2, LEFT, 1); public static final Operator COLON = op(":", 2, LEFT, 1.5); // -- assignment -- public static final Operator ASSIGN = op("=", 2, RIGHT, 0); public static final Operator POW_ASSIGN = op("^=", 2, RIGHT, 0); public static final Operator DOT_POW_ASSIGN = op(".^=", 2, RIGHT, 0); public static final Operator MUL_ASSIGN = op("*=", 2, RIGHT, 0); public static final Operator DIV_ASSIGN = op("/=", 2, RIGHT, 0); public static final Operator MOD_ASSIGN = op("%=", 2, RIGHT, 0); public static final Operator RIGHT_DIV_ASSIGN = op("\\=", 2, RIGHT, 0); public static final Operator DOT_DIV_ASSIGN = op("./=", 2, RIGHT, 0); public static final Operator DOT_RIGHT_DIV_ASSIGN = op(".\\=", 2, RIGHT, 0); public static final Operator ADD_ASSIGN = op("+=", 2, RIGHT, 0); public static final Operator SUB_ASSIGN = op("-=", 2, RIGHT, 0); public static final Operator AND_ASSIGN = op("&=", 2, RIGHT, 0); public static final Operator OR_ASSIGN = op("|=", 2, RIGHT, 0); public static final Operator LEFT_SHIFT_ASSIGN = op("<<=", 2, RIGHT, 0); public static final Operator RIGHT_SHIFT_ASSIGN = op(">>=", 2, RIGHT, 0); public static final Operator UNSIGNED_RIGHT_SHIFT_ASSIGN = op(">>>=", 2, RIGHT, 0); private Operators() { // NB: Prevent instantiation of utility class. } /** * Gets the standard list of operators. * * @return A new list containing each operator constant from this * {@link Operators} class, in declaration order. */ public static List standardList() { // Build the standard list from all available Operator constants. final ArrayList ops = new ArrayList<>(); for (final Field f : Operators.class.getFields()) { if (!isOperator(f)) continue; try { ops.add((Operator) f.get(null)); } catch (final IllegalAccessException exc) { // This should never happen. throw new IllegalStateException(exc); } } return ops; } // -- Helper methods -- private static Operator op(final String symbol, final int arity, final Associativity associativity, final double precedence) { return new Operator(symbol, arity, associativity, precedence); } private static Group group(final String leftSymbol, final String rightSymbol, final double precedence) { return new Group(leftSymbol, rightSymbol, precedence); } private static boolean isOperator(final Field f) { final int mods = f.getModifiers(); return Modifier.isStatic(mods) && Modifier.isFinal(mods) && Operator.class.isAssignableFrom(f.getType()); } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/ParseOperation.java000066400000000000000000000274021440242745700307240ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; import java.util.ArrayDeque; import java.util.Deque; import java.util.LinkedList; /** A stateful parsing operation. */ public class ParseOperation { protected final ExpressionParser parser; protected final String expression; protected final Position pos = new Position(); protected final Deque stack = new ArrayDeque<>(); protected final LinkedList outputQueue = new LinkedList<>(); /** * State flag for parsing context. *
    *
  • If true, we are expecting an infix or postfix operator next.
  • *
  • If false, we are expecting a prefix operator or "noun" token (e.g., * variable or literal) next.
  • *
*/ protected boolean infix; public ParseOperation(final ExpressionParser parser, final String expression) { this.parser = parser; this.expression = expression; } /** * Parses the expression into an output queue in Reverse * Polish notation (i.e., postfix notation). */ public LinkedList parsePostfix() { while (true) { // PROTIP: Put a breakpoint here, and watch the expression // parser do its thing piece by piece in your debugger! parseWhitespace(); // Stop if there are no more tokens to be read. if (pos.get() == expression.length()) break; // If next token is a literal, add it to the output queue. final Object literal = parseLiteral(); if (literal != null) { outputQueue.add(literal); // Update the state flag. infix = true; continue; } // If next token is a function argument separator (i.e., a comma)... if (parseElementSeparator() != null) { handleElementSeparator(); continue; } // If next token is a statement separator (i.e., a semicolon)... if (parseStatementSeparator() != null) { // Flush the stack and begin a new statement. flushStack(); infix = false; continue; } // If the token is an operator... final Operator o1 = parseOperator(); if (o1 != null) { if (Tokens.isGroup(o1) && infix) { // NB: Group initiator symbol following a "noun" token; // we infer an implicit function operator between them. handleOperator(new Function(o1.getPrecedence())); } handleOperator(o1); continue; } // If the token is a group terminator... final Group group = parseGroupTerminator(); if (group != null) { handleGroupTerminator(group); continue; } // If next token is a variable, add it to the output queue. final Variable variable = parseVariable(); if (variable != null) { outputQueue.add(variable); // Update the state flag. infix = true; continue; } pos.die("Invalid character"); } // No more tokens to read! flushStack(); return outputQueue; } // -- Object methods -- @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append(expression); sb.append("\n"); for (int i = 0; i < pos.get(); i++) { sb.append(" "); } sb.append("^"); return sb.toString(); } // -- Internal methods -- protected char currentChar() { return futureChar(0); } protected char futureChar(final int offset) { return pos.ch(expression, offset); } /** Skips past any whitespace to the next interesting character. */ protected void parseWhitespace() { while (Character.isWhitespace(currentChar())) pos.inc(); } /** * Attempts to parse a literal (e.g. boolean, string, or number). * * @return The parsed literal, or null if the next token is not one. * @see Literals#parseLiteral */ protected Object parseLiteral() { // Only accept a literal in the appropriate context. // This avoids confusing e.g. the unary and binary minus // operators, or a quoted string with a quote operator. if (infix) return null; return Literals.parseLiteral(expression, pos); } /** * Attempts to parse a variable. * * @return The parsed variable name, or null if the next token is not one. */ protected Variable parseVariable() { final int length = parseIdentifier(); if (length == 0) return null; return new Variable(parseToken(length)); } /** * Attempts to parse an identifier, as defined by * {@link #isIdentifierStart(char)} and {@link #isIdentifierPart(char)}. * * @return The length of the parsed identifier, or 0 if the next * token is not one. */ protected int parseIdentifier() { // Only accept an identifier in the appropriate context. if (infix) return 0; if (!isIdentifierStart(currentChar())) return 0; int length = 0; while (true) { final char next = futureChar(length); if (next == '\0') break; if (!isIdentifierPart(next)) break; length++; } return length; } /** * Determines whether the given character is allowed to start an identifier. *

* The default implementation uses * {@link Character#isUnicodeIdentifierStart(char)}, but also accepts * underscores, since many popular programming languages (e.g. Python and * Java) allow identifiers to begin with an underscore symbol. *

*/ protected boolean isIdentifierStart(char c) { return c == '_' || Character.isUnicodeIdentifierStart(c); } /** * Determines whether the given character is allowed to start an identifier. *

* The default implementation uses * {@link Character#isUnicodeIdentifierPart(char)}. *

*/ protected boolean isIdentifierPart(char c) { return Character.isUnicodeIdentifierPart(c); } /** * Attempts to parse an operator. * * @return The parsed operator, or null if the next token is not one. */ protected Operator parseOperator() { for (final Operator op : parser.operators()) { final String symbol = op.getToken(); if (operatorMatches(op, symbol)) return op; } return null; } /** * Attempts to parse a group terminator symbol. * * @return The group, or null if the next token is not a group terminator. */ protected Group parseGroupTerminator() { for (final Operator op : parser.operators()) { if (!(op instanceof Group)) continue; final Group group = (Group) op; final String symbol = group.getTerminator(); if (operatorMatches(op, symbol)) return group; } return null; } /** * Attempts to parse an element separator symbol. * * @return The separator symbol, or null if the next token is not one. */ protected String parseElementSeparator() { // Only accept an element separator in the appropriate context. if (!infix) return null; return parseChars(parser.elementSeparator()); } /** * Attempts to parse a statement separator symbol. * * @return The separator symbol, or null if the next token is not one. */ protected String parseStatementSeparator() { return parseChars(parser.statementSeparator()); } /** * Attempts to parse the given character. * * @return The character, or null if the next token is not that character. */ protected Character parseChar(final char c) { if (currentChar() == c) { pos.inc(); return c; } return null; } /** * Attempts to parse the given characters. * * @return The characters, or null if the next tokens are not those * characters. */ protected CS parseChars(final CS cs) { for (int i = 0; i < cs.length(); i++) { if (futureChar(i) != cs.charAt(i)) return null; } pos.inc(cs.length()); return cs; } /** * Parses a token of the given length. * * @return The parsed token. */ protected String parseToken(final int length) { final int offset = pos.get(); final String token = expression.substring(offset, offset + length); pos.inc(length); return token; } // -- Helper methods -- private void handleOperator(final Operator o1) { // While there is an operator token, o2, at the top of the stack... final double p1 = o1.getPrecedence(); while (!stack.isEmpty() && Tokens.isOperator(stack.peek()) && !Tokens.isGroup(stack.peek())) { final Operator o2 = (Operator) stack.peek(); final double p2 = o2.getPrecedence(); // ...and o1 has lower precedence than o2... if (o1.isLeftAssociative() && p1 <= p2 || // o1.isRightAssociative() && p1 < p2) { // Pop o2 off the stack, onto the output queue. outputQueue.add(stack.pop()); } else break; } // Push o1 onto the stack. stack.push(o1.instance()); // Update the state flag. if (o1.isPrefix() || o1.isInfix()) infix = false; else if (o1.isPostfix()) infix = true; else pos.fail("Impenetrable operator '" + o1 + "'"); } private void handleElementSeparator() { // Pop from stack to output queue until function found. while (true) { if (stack.isEmpty()) { pos.die("Misplaced separator or mismatched groups"); } if (Tokens.isGroup(stack.peek())) { // Count the completed argument in the group's arity. ((Group) stack.peek()).incArity(); break; } outputQueue.add(stack.pop()); } // Update the state flag. infix = false; } private void handleGroupTerminator(final Group group) { // Pop from stack to output queue until matching group found. while (true) { if (stack.isEmpty()) { // No group found: mismatched group symbols! pos.die("Mismatched group terminator '" + group.getTerminator() + "'"); } // If token is a group... if (Tokens.isMatchingGroup(stack.peek(), group)) { // Count the completed argument in the group's arity. if (infix) ((Group) stack.peek()).incArity(); // Pop the group onto the output queue. outputQueue.add(stack.pop()); break; } outputQueue.add(stack.pop()); } // Update the state flag. infix = true; } private void flushStack() { // While there are still operator tokens in the stack... while (!stack.isEmpty()) { final Object token = stack.pop(); // There shouldn't be any groups left. if (Tokens.isGroup(token)) pos.die("Mismatched groups"); // Pop the operator onto the output queue. outputQueue.add(token); } } private boolean operatorMatches(final Operator op, final String symbol) { if (!expression.startsWith(symbol, pos.get())) return false; // Ensure the operator is appropriate to the current context. final boolean prefixOK = !infix; final boolean postfixOK = infix; final boolean infixOK = infix; if (prefixOK && op.isPrefix() || // postfixOK && op.isPostfix() || // infixOK && op.isInfix()) { pos.inc(symbol.length()); return true; } return false; } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/Position.java000066400000000000000000000066041440242745700275760ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; /** * A mutable parse position. Similar to {@link java.text.ParsePosition}, but * less complex. * * @author Curtis Rueden */ public class Position { private int index; public int get() { return index; } public void set(final int index) { this.index = index; } public void inc() { inc(1); } public void inc(final int count) { index += count; } public char ch(final CharSequence s) { return ch(s, 0); } public char ch(final CharSequence s, final int offset) { final int i = get() + offset; return i < s.length() ? s.charAt(i) : '\0'; } /** * Throws an exception with an informative message. Called by the parsing * infrastructure when syntax is incorrect. * * @param message The text to use as a basis for the error message. * @throws IllegalArgumentException Always. */ public void die(final String message) { throw new IllegalArgumentException(messageWithDetails(message)); } /** * Calls {@link #fail(String)} if a condition is not met. Called by the * parsing infrastructure to assert that things are going OK. * * @param condition If false, throw the exception. * @param message The text to use as a basis for the error message. * @throws IllegalStateException If the condition is not met. */ public void assertThat(final boolean condition, final String message) { if (condition) return; fail(message); } /** * Throws an exception. Called by the parsing infrastructure when something * goes wrong. * * @param message The text to use as a basis for the error message. * @throws IllegalStateException Always. */ public void fail(final String message) { throw new IllegalStateException(messageWithDetails(message)); } // -- Object methods -- @Override public String toString() { return "" + get(); } // -- Helper methods -- private String messageWithDetails(final String message) { return message + " at index " + get(); } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/SubSequence.java000066400000000000000000000065331440242745700302150ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; /** * A {@link CharSequence} which is a by-reference subsequence of another * {@link CharSequence}. This is particularly useful for * {@link java.util.regex.Pattern regex} matching without excessive string * copying. *

* Surprisingly, core Java does not seem to have this capability (apart from * {@link javax.swing.text.Segment}, which seems misplaced in the Swing * library); all of {@link String#subSequence}, {@link StringBuffer#subSequence} * and {@link StringBuilder#subSequence} internally copy the requested string * segment. *

* * @author Curtis Rueden */ public class SubSequence implements CharSequence { private final CharSequence seq; private final int offset; private final int length; public SubSequence(final CharSequence seq, final int offset) { this(seq, offset, seq.length() - offset); } private static void outOfBounds(final String message) { throw new IndexOutOfBoundsException(message); } public SubSequence(final CharSequence seq, final int offset, final int length) { if (offset < 0) outOfBounds("Offset " + offset + " < 0"); if (offset > seq.length()) { outOfBounds("Offset " + offset + " > " + seq.length()); } if (length < 0) outOfBounds("Length " + length + " < 0"); if (offset + length > seq.length()) { outOfBounds("Offset " + offset + " + length " + length + " > " + seq.length()); } this.seq = seq; this.offset = offset; this.length = length; } // -- CharSequence methods -- @Override public int length() { return length; } @Override public char charAt(final int index) { return seq.charAt(offset + index); } @Override public SubSequence subSequence(final int start, final int end) { return new SubSequence(seq, offset + start, end - start); } // -- Object methods -- @Override public String toString() { return seq.subSequence(offset, offset + length).toString(); } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/SyntaxTree.java000066400000000000000000000102571440242745700300770ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; /** * A syntax * tree corresponding to an expression. * * @author Curtis Rueden */ public class SyntaxTree implements Iterable { private final Object token; private SyntaxTree[] children; /** * Creates a syntax tree built from the given postfix * token queue. This process will consume the entire queue. * * @param tokens The token queue, in postfix order. */ public SyntaxTree(final LinkedList tokens) { token = tokens.removeLast(); if (Tokens.isOperator(token)) { final Operator op = (Operator) token; final int arity = op.getArity(); if (arity > 0) { children = new SyntaxTree[arity]; for (int i = children.length - 1; i >= 0; i--) { children[i] = new SyntaxTree(tokens); } } } } public Object token() { return token; } public SyntaxTree child(final int index) { return children[index]; } public int count() { return children == null ? 0 : children.length; } /** * Converts the syntax tree into a token queue in postfix order. * * @return Token queue representation of the syntax tree. */ public LinkedList postfix() { final LinkedList queue = new LinkedList<>(); postfix(queue); return queue; } // -- Object methods -- @Override public String toString() { return toString(""); } @Override public boolean equals(final Object o) { if (!(o instanceof SyntaxTree)) return false; final SyntaxTree tree = (SyntaxTree) o; return token.equals(tree.token) && Arrays.equals(children, tree.children); } @Override public int hashCode() { return token.hashCode() ^ Arrays.deepHashCode(children); } // -- Iterable methods -- @Override public Iterator iterator() { return new Iterator() { private int index; @Override public boolean hasNext() { return index < count(); } @Override public SyntaxTree next() { return child(index++); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } // -- Helper methods -- private void postfix(final LinkedList queue) { for (final SyntaxTree child : this) { child.postfix(queue); } queue.add(token()); } private String toString(final String prefix) { final StringBuilder sb = new StringBuilder(); sb.append(prefix + " '" + token + "'\n"); final String deeperPrefix = " " + prefix + "-"; for (int i = 0; i < count(); i++) { sb.append(child(i).toString(deeperPrefix)); } return sb.toString(); } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/Token.java000066400000000000000000000042511440242745700270460ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; /** * Base class for various types of tokens: operators, groups, functions and * variables. *

* The only exception are literals, which use the standard Java types of * {@link String}, {@link Boolean} and {@link Number} rather than extending this * class. *

* * @author Curtis Rueden */ public abstract class Token { private final String token; public Token(final String token) { this.token = token; } // -- Token methods -- /** * Gets the token's sequence of characters. * * @return The token's character sequence. */ public String getToken() { return token; } // -- Object methods -- @Override public String toString() { return getToken(); } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/Tokens.java000066400000000000000000000045251440242745700272350ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; /** * Utility methods for working with tokens. * * @author Curtis Rueden */ public final class Tokens { private Tokens() { // NB: Prevent instantiation of utility class. } public static boolean isNumber(final Object o) { return o instanceof Number; } public static boolean isGroup(final Object o) { return o instanceof Group; } public static boolean isVariable(final Object o) { return o instanceof Variable; } public static boolean isOperator(final Object o) { return o instanceof Operator; } public static boolean isComma(final Object o) { return isCharacter(o, ','); } public static boolean isCharacter(final Object o, final Character c) { return o instanceof Character && ((Character) o).equals(c); } public static boolean isMatchingGroup(final Object o, final Group g) { return isGroup(o) && ((Group) o).matches(g); } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/Variable.java000066400000000000000000000032431440242745700275130ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; /** * A "noun" token representing a variable. * * @author Curtis Rueden */ public class Variable extends Token { public Variable(final String token) { super(token); } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/eval/000077500000000000000000000000001440242745700260505ustar00rootroot00000000000000parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/eval/AbstractEvaluator.java000066400000000000000000000052631440242745700323470ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington.eval; import java.util.HashMap; import java.util.Map; import org.scijava.parsington.ExpressionParser; /** * Base class for {@link Evaluator} implementations. * * @author Curtis Rueden */ public abstract class AbstractEvaluator implements Evaluator { private final HashMap vars = new HashMap<>(); private final ExpressionParser parser; private boolean strict = true; public AbstractEvaluator() { this(new ExpressionParser()); } public AbstractEvaluator(final ExpressionParser parser) { this.parser = parser; } // -- Evaluator methods -- @Override public ExpressionParser getParser() { return parser; } @Override public boolean isStrict() { return strict; } @Override public void setStrict(final boolean strict) { this.strict = strict; } @Override public Object get(final String name) { if (vars.containsKey(name)) return vars.get(name); if (strict) throw new IllegalArgumentException("Unknown variable: " + name); return new Unresolved(name); } @Override public void set(final String name, final Object value) { vars.put(name, value); } @Override public void setAll(final Map map) { vars.putAll(map); } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/eval/AbstractStandardEvaluator.java000066400000000000000000000502631440242745700340300ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington.eval; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Arrays; import java.util.List; import org.scijava.parsington.ExpressionParser; import org.scijava.parsington.Literals; import org.scijava.parsington.Tokens; import org.scijava.parsington.Variable; /** * Base class for {@link StandardEvaluator} implementations on common * built-in types: {@link Boolean}s, {@link String}s and {@link Number}s. *

* This class is a big bag of case logic for various operators and types. * Looking at it, you might think: "It sure would be nice to modularize this, * with each operation in its own class, with properly declared types, and * called dynamically at runtime as appropriate." *

*

* "Great idea!" I would reply. Then I would suggest you have a look at the * SciJava Ops and * ImageJ Ops projects, which * do exactly that in an extensible way. *

*

* Or maybe you are thinking: "This can't possibly work as well as awesome * JVM-based scripting languages like Jython * and Groovy..." *

*

* To which I would reply: "You are absolutely right! This class is mostly just * a demonstration of an extensible, working evaluator built using the * {@link org.scijava.parsington.eval} package. If your use case is only * concerned with feature-rich evaluation of standard types, then building on * top of a scripting language might make more sense." *

* * @author Curtis Rueden */ public abstract class AbstractStandardEvaluator extends AbstractEvaluator implements StandardEvaluator { public AbstractStandardEvaluator() { super(); } public AbstractStandardEvaluator(final ExpressionParser parser) { super(parser); } // -- StandardEvaluator methods -- // -- function -- @Override public Object function(final Object a, final Object b) { final Object element = listElement(a, b); if (element != null) return element; if (Tokens.isVariable(a)) { final String name = ((Variable) a).getToken(); final Object result = callFunction(name, b); if (result != null) return result; } // NB: Unknown function type. return null; } // -- dot -- @Override public Object dot(final Object a, final Object b) { // NB: Unimplemented. return null; } // -- groups -- @Override public Object parens(final Object... args) { if (args.length == 1) return args[0]; return Arrays.asList(args); } @Override public Object brackets(final Object... args) { return Arrays.asList(args); } @Override public Object braces(final Object... args) { return Arrays.asList(args); } // -- transpose, power -- @Override public Object transpose(final Object a) { // NB: Unimplemented. return null; } @Override public Object dotTranspose(final Object a) { // NB: Unimplemented. return null; } @Override public Object pow(final Object a, final Object b) { if (isD(a) && isD(b)) return pow(d(a), d(b)); if (isBI(a) && isI(b)) return pow(bi(a), i(b)); if (isBD(a) && isI(b)) return pow(bd(a), i(b)); return null; } public double pow(final double a, final double b) { return Math.pow(a, b); } public BigInteger pow(final BigInteger a, final int b) { return a.pow(b); } public BigDecimal pow(final BigDecimal a, final int b) { return a.pow(b); } @Override public Object dotPow(final Object a, final Object b) { // NB: Unimplemented. return null; } // -- unary -- @Override public Object pos(final Object a) { if (isI(a)) return pos(i(a)); if (isL(a)) return pos(l(a)); if (isF(a)) return pos(f(a)); if (isD(a)) return pos(d(a)); return value(a); } public int pos(final int num) { return +num; } public long pos(final long num) { return +num; } public float pos(final float num) { return +num; } public double pos(final double num) { return +num; } @Override public Object neg(final Object a) { if (isI(a)) return neg(i(a)); if (isL(a)) return neg(l(a)); if (isF(a)) return neg(f(a)); if (isD(a)) return neg(d(a)); if (isBI(a)) return neg(bi(a)); if (isBD(a)) return neg(bd(a)); return sub(0, a); } public int neg(final int num) { return -num; } public long neg(final long num) { return -num; } public float neg(final float num) { return -num; } public double neg(final double num) { return -num; } public BigInteger neg(final BigInteger num) { return num.negate(); } public BigDecimal neg(final BigDecimal num) { return num.negate(); } @Override public Object complement(final Object a) { if (isI(a)) return complement(i(a)); if (isL(a)) return complement(l(a)); return null; } public int complement(final int a) { return ~a; } public long complement(final long a) { return ~a; } @Override public Object not(final Object a) { return not(bool(a)); } public boolean not(final boolean a) { return !a; } // -- multiplicative -- @Override public Object mul(final Object a, final Object b) { if (isI(a) && isI(b)) return mul(i(a), i(b)); if (isL(a) && isL(b)) return mul(l(a), l(b)); if (isF(a) && isF(b)) return mul(f(a), f(b)); if (isD(a) && isD(b)) return mul(d(a), d(b)); if (isBI(a) && isBI(b)) return mul(bi(a), bi(b)); if (isBD(a) && isBD(b)) return mul(bd(a), bd(b)); return null; } public int mul(final int a, final int b) { return a * b; } public long mul(final long a, final long b) { return a * b; } public float mul(final float a, final float b) { return a * b; } public double mul(final double a, final double b) { return a * b; } public BigInteger mul(final BigInteger a, final BigInteger b) { return a.multiply(b); } public BigDecimal mul(final BigDecimal a, final BigDecimal b) { return a.multiply(b); } @Override public Object div(final Object a, final Object b) { if (isI(a) && isI(b)) return div(i(a), i(b)); if (isL(a) && isL(b)) return div(l(a), l(b)); if (isF(a) && isF(b)) return div(f(a), f(b)); if (isD(a) && isD(b)) return div(d(a), d(b)); if (isBI(a) && isBI(b)) return div(bi(a), bi(b)); if (isBD(a) && isBD(b)) return div(bd(a), bd(b)); return null; } public int div(final int a, final int b) { return a / b; } public long div(final long a, final long b) { return a / b; } public float div(final float a, final float b) { return a / b; } public double div(final double a, final double b) { return a / b; } public BigInteger div(final BigInteger a, final BigInteger b) { return a.divide(b); } public BigDecimal div(final BigDecimal a, final BigDecimal b) { return a.divide(b); } @Override public Object mod(final Object a, final Object b) { if (isI(a) && isI(b)) return mod(i(a), i(b)); if (isL(a) && isL(b)) return mod(l(a), l(b)); if (isF(a) && isF(b)) return mod(f(a), f(b)); if (isD(a) && isD(b)) return mod(d(a), d(b)); if (isBI(a) && isBI(b)) return mod(bi(a), bi(b)); if (isBD(a) && isBD(b)) return mod(bd(a), bd(b)); return null; } public int mod(final int a, final int b) { return a % b; } public long mod(final long a, final long b) { return a % b; } public float mod(final float a, final float b) { return a % b; } public double mod(final double a, final double b) { return a % b; } public BigInteger mod(final BigInteger a, final BigInteger b) { return a.remainder(b); } public BigDecimal mod(final BigDecimal a, final BigDecimal b) { return a.remainder(b); } @Override public Object rightDiv(final Object a, final Object b) { // NB: Unimplemented. return null; } @Override public Object dotMul(Object a, Object b) { // NB: Unimplemented. return null; } @Override public Object dotDiv(final Object a, final Object b) { // NB: Unimplemented. return null; } @Override public Object dotRightDiv(final Object a, final Object b) { // NB: Unimplemented. return null; } // -- additive -- @Override public Object add(final Object a, final Object b) { if (isStr(a)) return add(str(a), str(b)); if (isI(a) && isI(b)) return add(i(a), i(b)); if (isL(a) && isL(b)) return add(l(a), l(b)); if (isF(a) && isF(b)) return add(f(a), f(b)); if (isD(a) && isD(b)) return add(d(a), d(b)); if (isBI(a) && isBI(b)) return add(bi(a), bi(b)); if (isBD(a) && isBD(b)) return add(bd(a), bd(b)); return null; } public String add(final String a, final String b) { return a + b; } public int add(final int a, final int b) { return a + b; } public long add(final long a, final long b) { return a + b; } public float add(final float a, final float b) { return a + b; } public double add(final double a, final double b) { return a + b; } public BigInteger add(final BigInteger a, final BigInteger b) { return a.add(b); } public BigDecimal add(final BigDecimal a, final BigDecimal b) { return a.add(b); } @Override public Object sub(final Object a, final Object b) { if (isI(a) && isI(b)) return sub(i(a), i(b)); if (isL(a) && isL(b)) return sub(l(a), l(b)); if (isF(a) && isF(b)) return sub(f(a), f(b)); if (isD(a) && isD(b)) return sub(d(a), d(b)); if (isBI(a) && isBI(b)) return sub(bi(a), bi(b)); if (isBD(a) && isBD(b)) return sub(bd(a), bd(b)); return null; } public int sub(final int a, final int b) { return a - b; } public long sub(final long a, final long b) { return a - b; } public float sub(final float a, final float b) { return a - b; } public double sub(final double a, final double b) { return a - b; } public BigInteger sub(final BigInteger a, final BigInteger b) { return a.subtract(b); } public BigDecimal sub(final BigDecimal a, final BigDecimal b) { return a.subtract(b); } // -- shift -- @Override public Object leftShift(final Object a, final Object b) { if (isI(a) && isI(b)) return leftShift(i(a), i(b)); if (isL(a) && isL(b)) return leftShift(l(a), l(b)); if (isBI(a) && isI(b)) return leftShift(bi(a), i(b)); return null; } public int leftShift(final int a, final int b) { return a << b; } public long leftShift(final long a, final long b) { return a << b; } public BigInteger leftShift(final BigInteger a, final int b) { return a.shiftLeft(b); } @Override public Object rightShift(final Object a, final Object b) { if (isI(a) && isI(b)) return rightShift(i(a), i(b)); if (isL(a) && isL(b)) return rightShift(l(a), l(b)); if (isBI(a) && isI(b)) return rightShift(bi(a), i(b)); return null; } public int rightShift(final int a, final int b) { return a >> b; } public long rightShift(final long a, final long b) { return a >> b; } public BigInteger rightShift(final BigInteger a, final int b) { return a.shiftRight(b); } @Override public Object unsignedRightShift(final Object a, final Object b) { if (isI(a) && isI(b)) return unsignedRightShift(i(a), i(b)); if (isL(a) && isL(b)) return unsignedRightShift(l(a), l(b)); return null; } public int unsignedRightShift(final int a, final int b) { return a >>> b; } public long unsignedRightShift(final long a, final long b) { return a >>> b; } // -- relational -- @Override public Object lessThan(final Object a, final Object b) { if (isBool(a) && isBool(b)) return lessThan(bool(a), bool(b)); if (isStr(a) && isStr(b)) return lessThan(str(a), str(b)); if (isI(a) && isI(b)) return lessThan(i(a), i(b)); if (isL(a) && isL(b)) return lessThan(l(a), l(b)); if (isF(a) && isF(b)) return lessThan(f(a), f(b)); if (isD(a) && isD(b)) return lessThan(d(a), d(b)); if (isBI(a) && isBI(b)) return lessThan(bi(a), bi(b)); if (isBD(a) && isBD(b)) return lessThan(bd(a), bd(b)); return null; } public boolean lessThan(final Comparable a, final T b) { return a.compareTo(b) < 0; } @Override public Object greaterThan(final Object a, final Object b) { if (isBool(a) && isBool(b)) return greaterThan(bool(a), bool(b)); if (isStr(a) && isStr(b)) return greaterThan(str(a), str(b)); if (isI(a) && isI(b)) return greaterThan(i(a), i(b)); if (isL(a) && isL(b)) return greaterThan(l(a), l(b)); if (isF(a) && isF(b)) return greaterThan(f(a), f(b)); if (isD(a) && isD(b)) return greaterThan(d(a), d(b)); if (isBI(a) && isBI(b)) return greaterThan(bi(a), bi(b)); if (isBD(a) && isBD(b)) return greaterThan(bd(a), bd(b)); return null; } public boolean greaterThan(final Comparable a, final T b) { return a.compareTo(b) > 0; } @Override public Object lessThanOrEqual(final Object a, final Object b) { if (isBool(a) && isBool(b)) return lessThanOrEqual(bool(a), bool(b)); if (isStr(a) && isStr(b)) return lessThanOrEqual(str(a), str(b)); if (isI(a) && isI(b)) return lessThanOrEqual(i(a), i(b)); if (isL(a) && isL(b)) return lessThanOrEqual(l(a), l(b)); if (isF(a) && isF(b)) return lessThanOrEqual(f(a), f(b)); if (isD(a) && isD(b)) return lessThanOrEqual(d(a), d(b)); if (isBI(a) && isBI(b)) return lessThanOrEqual(bi(a), bi(b)); if (isBD(a) && isBD(b)) return lessThanOrEqual(bd(a), bd(b)); return null; } public boolean lessThanOrEqual(final Comparable a, final T b) { return a.compareTo(b) <= 0; } @Override public Object greaterThanOrEqual(final Object a, final Object b) { if (isBool(a) && isBool(b)) return greaterThanOrEqual(bool(a), bool(b)); if (isStr(a) && isStr(b)) return greaterThanOrEqual(str(a), str(b)); if (isI(a) && isI(b)) return greaterThanOrEqual(i(a), i(b)); if (isL(a) && isL(b)) return greaterThanOrEqual(l(a), l(b)); if (isF(a) && isF(b)) return greaterThanOrEqual(f(a), f(b)); if (isD(a) && isD(b)) return greaterThanOrEqual(d(a), d(b)); if (isBI(a) && isBI(b)) return greaterThanOrEqual(bi(a), bi(b)); if (isBD(a) && isBD(b)) return greaterThanOrEqual(bd(a), bd(b)); return null; } public boolean greaterThanOrEqual(final Comparable a, final T b) { return a.compareTo(b) >= 0; } @Override public Object instanceOf(final Object a, final Object b) { // NB: Unimplemented. return null; } // -- equality -- @Override public Object equal(final Object a, final Object b) { return value(a).equals(value(b)); } @Override public Object notEqual(final Object a, final Object b) { return !value(a).equals(value(b)); } // -- bitwise -- @Override public Object bitwiseAnd(final Object a, final Object b) { if (isI(a) && isI(b)) return bitwiseAnd(i(a), i(b)); if (isL(a) && isL(b)) return bitwiseAnd(l(a), l(b)); if (isBI(a) && isBI(b)) return bitwiseAnd(bi(a), bi(b)); return null; } public int bitwiseAnd(final int a, final int b) { return a & b; } public long bitwiseAnd(final long a, final long b) { return a & b; } public BigInteger bitwiseAnd(final BigInteger a, final BigInteger b) { return a.and(b); } @Override public Object bitwiseOr(final Object a, final Object b) { if (isI(a) && isI(b)) return bitwiseOr(i(a), i(b)); if (isL(a) && isL(b)) return bitwiseOr(l(a), l(b)); if (isBI(a) && isBI(b)) return bitwiseOr(bi(a), bi(b)); return null; } public int bitwiseOr(final int a, final int b) { return a | b; } public long bitwiseOr(final long a, final long b) { return a | b; } public BigInteger bitwiseOr(final BigInteger a, final BigInteger b) { return a.or(b); } // -- logical -- @Override public Object logicalAnd(final Object a, final Object b) { if (isBool(a) && isBool(b)) return logicalAnd(bool(a), bool(b)); return null; } public boolean logicalAnd(final boolean a, final boolean b) { return a && b; } @Override public Object logicalOr(final Object a, final Object b) { if (isBool(a) && isBool(b)) return logicalOr(bool(a), bool(b)); return null; } public boolean logicalOr(final boolean a, final boolean b) { return a || b; } // -- ternary -- @Override public Object question(final Object a, final Object b) { // NB: Unimplemented. return null; } @Override public Object colon(Object a, Object b) { // NB: Unimplemented. return null; } // -- Helper methods - type matching -- private boolean is(final Object o, final Class c) { return c.isInstance(value(o)); } private boolean isBool(final Object o) { return is(o, Boolean.class); } private boolean isStr(final Object o) { return is(o, String.class); } private boolean isB(final Object o) { return is(o, Byte.class); } private boolean isS(final Object o) { return is(o, Short.class) || isB(o); } private boolean isI(final Object o) { return is(o, Integer.class) || isS(o); } private boolean isL(final Object o) { return is(o, Long.class) || isI(o); } // NB: Java allows assignment of all integer primitive types to float! private boolean isF(final Object o) { return is(o, Float.class) || isL(o); } private boolean isD(final Object o) { return is(o, Double.class) || isF(o); } private boolean isBI(final Object o) { return is(o, BigInteger.class) || isL(o); } private boolean isBD(final Object o) { return is(o, BigDecimal.class) || isBI(o) || isD(o); } // -- Helper methods - type coercion -- /** Casts the given token to the specified class, or null if incompatible. */ private T cast(final Object token, final Class type) { if (type.isInstance(token)) { @SuppressWarnings("unchecked") final T result = (T) token; return result; } return null; } /** Coerces the given token to a boolean. */ private boolean bool(final Object token) { final Boolean b = cast(value(token), Boolean.class); return b != null ? b : Boolean.valueOf(token.toString()); } /** Coerces the given token to a string. */ private String str(final Object token) { final String s = cast(value(token), String.class); return s != null ? s : token.toString(); } /** Coerces the given token to a number. */ private Number num(final Object token) { final Number n = cast(value(token), Number.class); return n != null ? n : Literals.parseNumber(token.toString()); } private int i(final Object o) { return num(o).intValue(); } private long l(final Object o) { return num(o).longValue(); } private float f(final Object o) { return num(o).floatValue(); } private double d(final Object o) { return num(o).doubleValue(); } private BigInteger bi(final Object o) { final BigInteger bi = cast(o, BigInteger.class); return bi != null ? bi : new BigInteger("" + value(o)); } private BigDecimal bd(final Object o) { final BigDecimal bd = cast(o, BigDecimal.class); return bd != null ? bd : new BigDecimal("" + value(o)); } private Object listElement(final Object a, final Object b) { final Object value; try { value = value(a); } catch (final IllegalArgumentException exc) { return null; } if (!(value instanceof List)) return null; final List list = (List) value; if (!(b instanceof List)) return null; final List indices = (List) b; if (indices.size() != 1) return null; // not a 1-D access return list.get(i(indices.get(0))); } /** Executes built-in functions. */ private Object callFunction(final String name, final Object b) { if (name.equals("postfix") && b instanceof String) { return getParser().parsePostfix((String) b); } if (name.equals("tree") && b instanceof String) { return getParser().parseTree((String) b); } return null; } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/eval/DefaultStackEvaluator.java000066400000000000000000000045211440242745700331520ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington.eval; import org.scijava.parsington.ExpressionParser; import org.scijava.parsington.Operators; /** * An expression evaluator for most {@link Operators standard operators} with * common built-in types (i.e.: {@link Boolean}s, {@link String}s and * {@link Number}s). Does not handle short circuiting of ternary expressions. *

* It is recommended to use {@link DefaultTreeEvaluator} instead, unless your * expression's syntax tree is so deep it exceeds the maximum recursion depth. *

* * @author Curtis Rueden * @see DefaultTreeEvaluator For an evaluator that supports ternary * short-circuiting. */ public class DefaultStackEvaluator extends AbstractStandardEvaluator implements StandardStackEvaluator { public DefaultStackEvaluator() { super(); } public DefaultStackEvaluator(final ExpressionParser parser) { super(parser); } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/eval/DefaultTreeEvaluator.java000066400000000000000000000042321440242745700330030ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington.eval; import org.scijava.parsington.ExpressionParser; import org.scijava.parsington.Operators; /** * An expression evaluator for most {@link Operators standard operators} with * common built-in types (i.e.: {@link Boolean}s, {@link String}s and * {@link Number}s). Simulates the ternary {@code ?:} operator including * short-circuiting. * * @author Curtis Rueden * @see org.scijava.parsington.Main The main class, to give it a spin. */ public class DefaultTreeEvaluator extends AbstractStandardEvaluator implements StandardTreeEvaluator { public DefaultTreeEvaluator() { super(); } public DefaultTreeEvaluator(final ExpressionParser parser) { super(parser); } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/eval/Evaluator.java000066400000000000000000000142401440242745700306560ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington.eval; import java.util.LinkedList; import java.util.Map; import org.scijava.parsington.ExpressionParser; import org.scijava.parsington.SyntaxTree; import org.scijava.parsington.Tokens; import org.scijava.parsington.Variable; /** * Interface for expression evaluators. * * @author Curtis Rueden */ public interface Evaluator { /** * Gets the parser used when evaluating expressions. * * @return The expression parser used by this evaluator. */ ExpressionParser getParser(); /** * Gets whether the evaluator is operating in strict mode. * * @return True iff the evaluator is operating in strict mode. * @see #setStrict(boolean) */ boolean isStrict(); /** * Sets whether the evaluator is operating in strict mode. Evaluators operate * in strict mode by default. *

* When evaluating strictly, usage of an unassigned variable token in a place * where its value is needed will generate an {@link IllegalArgumentException} * with an "Unknown variable" message; in non-strict mode, such a variable * will instead be resolved to an object of type {@link Unresolved} with the * same name as the original variable. *

*

* In cases such as assignment, this may be sufficient to complete the * evaluation; for example, the expression {@code foo=bar} will complete * successfully in non-strict mode, with the variable {@code foo} containing * an object of type {@link Unresolved} and token value {@code "bar"}. But in * cases where the unresolved value is needed as an input for additional * operations, the evaluation may still ultimately fail if the operation in * question is not defined for unresolved values. For example, the * {@link DefaultStackEvaluator} will fail with an "Unsupported binary * operator" exception when given the expression {@code foo+bar}, since * {@code foo} and {@code bar} are unresolved variables, and the {@code +} * operator cannot handle such objects. *

* * @param strict True iff the evaluator should operate in strict mode. */ void setStrict(boolean strict); /** * Evaluates an infix expression. * * @param expression The infix expression to evaluate. * @return The result of the evaluation. */ Object evaluate(String expression); /** * Evaluates a postfix token queue. * * @param queue The postfix token queue to evaluate. * @return The result of the evaluation. */ Object evaluate(LinkedList queue); /** * Evaluates a syntax tree. * * @param syntaxTree The syntax tree to evaluate. * @return The result of the evaluation. */ Object evaluate(SyntaxTree syntaxTree); /** * Gets the value of a token. For variables, returns the value of the * variable, throwing an exception if the variable is not set. For literals, * returns the token itself. * * @param token The token whose value you want. * @return The token's value. */ default Object value(final Object token) { return Tokens.isVariable(token) ? get((Variable) token) : token; } /** * Casts the given token to a variable. * * @param token The token to cast to a {@link Variable}. * @return {@link Variable} representation of the token. * @throws IllegalArgumentException if the given token is not a * {@link Variable}. */ default Variable var(final Object token) { if (Tokens.isVariable(token)) return (Variable) token; throw new IllegalArgumentException("Not a variable: " + token); } /** * Gets the value of a variable. * * @param name The name of the variable whose value you want. * @return The variable's value. * @throws IllegalArgumentException If the variable's value is not set, and * the evaluator is operating in {@link #isStrict() strict mode}. */ Object get(String name); /** * Sets the value of a variable. * * @param name The name of the variable whose value you want to set. * @param value The value to assign to the variable. */ void set(String name, Object value); /** * Gets the value of a variable. * * @param v The variable whose value you want. * @return The variable's value. * @throws IllegalArgumentException If the variable's value is not set, and * the evaluator is operating in {@link #isStrict() strict mode}. */ default Object get(final Variable v) { return get(v.getToken()); } /** * Sets the value of a variable. * * @param v The variable whose value you want to set. * @param value The value to assign to the variable. */ default void set(final Variable v, final Object value) { set(v.getToken(), value); } /** * Assigns variables en masse. * * @param map A map from variable names to variable values. */ void setAll(Map map); } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/eval/EvaluatorConsole.java000066400000000000000000000074701440242745700322100ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington.eval; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.scijava.parsington.Tokens; /** * A simple console-driven expression evaluator. * * @author Curtis Rueden */ public class EvaluatorConsole { private static final String PROMPT = "> "; private final Evaluator evaluator; public EvaluatorConsole() { this(new DefaultStackEvaluator()); } public EvaluatorConsole(final Evaluator evaluator) { this.evaluator = evaluator; } // -- EvaluatorConsole methods -- public void showConsole() throws IOException { showConsole(new BufferedReader(new InputStreamReader(System.in))); } public void showConsole(final BufferedReader in) throws IOException { while (true) { print(PROMPT); final String line = in.readLine(); if (line == null) break; try { final Object result = evaluator.evaluate(line); evaluator.setStrict(false); if (result != null) printResult(result); } catch (final IllegalArgumentException exc) { final String msg = exc.getMessage(); if (msg == null) throw exc; // Probably a serious exception. final Matcher m = Pattern.compile(".* at index (\\d+)").matcher(msg); if (m.matches()) { // Show a helpful caret to indicate where the problem is. final int index = Integer.parseInt(m.group(1)); println(caret(index)); } println(msg); } } } // -- Helper methods -- private void printResult(final Object o) { if (o instanceof List) { for (final Object item : (List) o) { printResult(item); } } else if (Tokens.isVariable(o)) { final Object value = evaluator.value(o); if (value instanceof Unresolved) println(valueAndType(o)); else println(valueAndType(o) + " = " + valueAndType(value)); } else println(valueAndType(o)); } private String valueAndType(final Object o) { return o + " : " + o.getClass().getName(); } private void print(final Object o) { System.out.print(o); } private void println(final Object o) { System.out.println(o); } private String caret(final int index) { final StringBuilder sb = new StringBuilder(); final int count = PROMPT.length() + index; for (int i = 0; i < count; i++) { sb.append(" "); } sb.append("^"); return sb.toString(); } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/eval/StackEvaluator.java000066400000000000000000000100621440242745700316420ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington.eval; import java.util.ArrayDeque; import java.util.Deque; import java.util.LinkedList; import java.util.function.Function; import org.scijava.parsington.Group; import org.scijava.parsington.Operator; import org.scijava.parsington.SyntaxTree; import org.scijava.parsington.Tokens; /** * Interface for stack-based expression evaluators, operating on postfix queues. * * @author Curtis Rueden */ public interface StackEvaluator extends Evaluator { /** * Executes an {@link Operator operation} with the specified value stack. * * @param op The operator to execute. * @param stack The value stack containing the arguments to pass. * @return The result of the operation. */ Object execute(final Operator op, final Deque stack); // -- Evaluator methods -- @Override default Object evaluate(final String expression) { // Convert the expression to postfix. return evaluate(getParser().parsePostfix(expression)); } @Override default Object evaluate(final SyntaxTree syntaxTree) { // Convert the syntax tree to postfix. return evaluate(syntaxTree.postfix()); } @Override default Object evaluate(final LinkedList queue) { // Process the postfix token queue. final Deque stack = new ArrayDeque<>(); while (!queue.isEmpty()) { final Object token = queue.removeFirst(); final Object result; if (Tokens.isOperator(token)) { result = execute((Operator) token, stack); } else { // Token is a variable or a literal. result = token; } if (result == null) { // Throw an informative exception. final StringBuilder message = new StringBuilder("Unsupported"); if (token instanceof Operator) { final int arity = ((Operator) token).getArity(); final String[] aryNames = { "nullary", "unary", "binary", "ternary", "quaternary", "quinary", "senary", "septenary", "octary", "nonary" }; final String aryName = arity < aryNames.length ? aryNames[arity] : arity + "-ary"; message.append(" " + aryName); } final String type; if (token instanceof Function) type = "function"; else if (token instanceof Group) type = "group"; else if (token instanceof Operator) type = "operator"; else type = "token"; message.append(" " + type + ": " + token); throw new IllegalArgumentException(message.toString()); } stack.push(result); } if (stack.isEmpty()) return null; if (stack.size() == 1) return stack.pop(); final LinkedList resultList = new LinkedList<>(); while (!stack.isEmpty()) { resultList.addFirst(stack.pop()); } return resultList; } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/eval/StandardEvaluator.java000066400000000000000000000457231440242745700323510ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington.eval; import org.scijava.parsington.Function; import org.scijava.parsington.Operator; import org.scijava.parsington.Operators; import org.scijava.parsington.Tokens; import org.scijava.parsington.Variable; /** * Interface for expression evaluators which support the {@link Operators * standard operators}. * * @author Curtis Rueden */ public interface StandardEvaluator extends Evaluator { // -- function -- /** * Applies the {@link Function} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object function(Object a, Object b); // -- dot -- /** * Applies the {@link Operators#DOT} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object dot(Object a, Object b); // -- groups -- /** * Applies the {@link Operators#PARENS} operator. * * @param args The arguments. * @return The result of the operation. */ Object parens(Object... args); /** * Applies the {@link Operators#BRACKETS} operator. * * @param args The arguments. * @return The result of the operation. */ Object brackets(Object... args); /** * Applies the {@link Operators#BRACES} operator. * * @param args The arguments. * @return The result of the operation. */ Object braces(Object... args); // -- transpose, power -- /** * Applies the {@link Operators#TRANSPOSE} operator. * * @param a The argument. * @return The result of the operation. */ Object transpose(Object a); /** * Applies the {@link Operators#DOT_TRANSPOSE} operator. * * @param a The argument. * @return The result of the operation. */ Object dotTranspose(Object a); /** * Applies the {@link Operators#POW} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object pow(Object a, Object b); /** * Applies the {@link Operators#DOT_POW} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object dotPow(Object a, Object b); // -- postfix -- /** * Applies the {@link Operators#POST_INC} operator. * * @param a The argument. * @return The result of the operation. */ default Object postInc(final Object a) { final Variable v = var(a); final Object value = value(v); if (value == null) return null; set(v, add(a, 1)); return value; } /** * Applies the {@link Operators#POST_DEC} operator. * * @param a The argument. * @return The result of the operation. */ default Object postDec(final Object a) { final Variable v = var(a); final Object value = value(v); if (value == null) return null; set(v, sub(a, 1)); return value; } // -- unary -- /** * Applies the {@link Operators#PRE_INC} operator. * * @param a The argument. * @return The result of the operation. */ default Object preInc(final Object a) { final Variable v = var(a); final Object result = add(a, 1); set(v, result); return result; } /** * Applies the {@link Operators#PRE_DEC} operator. * * @param a The argument. * @return The result of the operation. */ default Object preDec(final Object a) { final Variable v = var(a); final Object result = sub(a, 1); set(v, result); return result; } /** * Applies the {@link Operators#POS} operator. * * @param a The argument. * @return The result of the operation. */ Object pos(Object a); /** * Applies the {@link Operators#NEG} operator. * * @param a The argument. * @return The result of the operation. */ Object neg(Object a); /** * Applies the {@link Operators#COMPLEMENT} operator. * * @param a The argument. * @return The result of the operation. */ Object complement(Object a); /** * Applies the {@link Operators#NOT} operator. * * @param a The argument. * @return The result of the operation. */ Object not(Object a); // -- multiplicative -- /** * Applies the {@link Operators#MUL} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object mul(Object a, Object b); /** * Applies the {@link Operators#DIV} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object div(Object a, Object b); /** * Applies the {@link Operators#MOD} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object mod(Object a, Object b); /** * Applies the {@link Operators#RIGHT_DIV} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object rightDiv(Object a, Object b); /** * Applies the {@link Operators#DOT_MUL} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object dotMul(Object a, Object b); /** * Applies the {@link Operators#DOT_DIV} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object dotDiv(Object a, Object b); /** * Applies the {@link Operators#DOT_RIGHT_DIV} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object dotRightDiv(Object a, Object b); // -- additive -- /** * Applies the {@link Operators#ADD} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object add(Object a, Object b); /** * Applies the {@link Operators#SUB} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object sub(Object a, Object b); // -- shift -- /** * Applies the {@link Operators#LEFT_SHIFT} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object leftShift(Object a, Object b); /** * Applies the {@link Operators#RIGHT_SHIFT} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object rightShift(Object a, Object b); /** * Applies the {@link Operators#UNSIGNED_RIGHT_SHIFT} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object unsignedRightShift(Object a, Object b); // -- relational -- /** * Applies the {@link Operators#LESS_THAN} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object lessThan(Object a, Object b); /** * Applies the {@link Operators#GREATER_THAN} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object greaterThan(Object a, Object b); /** * Applies the {@link Operators#LESS_THAN_OR_EQUAL} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object lessThanOrEqual(Object a, Object b); /** * Applies the {@link Operators#GREATER_THAN_OR_EQUAL} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object greaterThanOrEqual(Object a, Object b); /** * Applies the {@link Operators#INSTANCEOF} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object instanceOf(Object a, Object b); // -- equality -- /** * Applies the {@link Operators#EQUAL} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object equal(Object a, Object b); /** * Applies the {@link Operators#NOT_EQUAL} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object notEqual(Object a, Object b); // -- bitwise -- /** * Applies the {@link Operators#BITWISE_AND} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object bitwiseAnd(Object a, Object b); /** * Applies the {@link Operators#BITWISE_OR} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object bitwiseOr(Object a, Object b); // -- logical -- /** * Applies the {@link Operators#LOGICAL_AND} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object logicalAnd(Object a, Object b); /** * Applies the {@link Operators#LOGICAL_OR} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object logicalOr(Object a, Object b); // -- ternary -- /** * Applies the {@link Operators#QUESTION} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object question(Object a, Object b); /** * Applies the {@link Operators#COLON} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ Object colon(Object a, Object b); // -- assignment -- /** * Applies the {@link Operators#ASSIGN} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ default Object assign(final Object a, final Object b) { final Variable v = var(a); set(v, value(b)); return v; } /** * Applies the {@link Operators#POW_ASSIGN} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ default Object powAssign(final Object a, final Object b) { return assign(a, pow(a, b)); } /** * Applies the {@link Operators#DOT_POW_ASSIGN} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ default Object dotPowAssign(final Object a, final Object b) { return assign(a, dotPow(a, b)); } /** * Applies the {@link Operators#MUL_ASSIGN} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ default Object mulAssign(final Object a, final Object b) { return assign(a, mul(a, b)); } /** * Applies the {@link Operators#DIV_ASSIGN} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ default Object divAssign(final Object a, final Object b) { return assign(a, div(a, b)); } /** * Applies the {@link Operators#MOD_ASSIGN} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ default Object modAssign(final Object a, final Object b) { return assign(a, mod(a, b)); } /** * Applies the {@link Operators#RIGHT_DIV_ASSIGN} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ default Object rightDivAssign(final Object a, final Object b) { return assign(a, rightDiv(a, b)); } /** * Applies the {@link Operators#DOT_DIV_ASSIGN} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ default Object dotDivAssign(final Object a, final Object b) { return assign(a, dotDiv(a, b)); } /** * Applies the {@link Operators#DOT_RIGHT_DIV_ASSIGN} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ default Object dotRightDivAssign(final Object a, final Object b) { return assign(a, dotRightDiv(a, b)); } /** * Applies the {@link Operators#ADD_ASSIGN} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ default Object addAssign(final Object a, final Object b) { return assign(a, add(a, b)); } /** * Applies the {@link Operators#SUB_ASSIGN} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ default Object subAssign(final Object a, final Object b) { return assign(a, sub(a, b)); } /** * Applies the {@link Operators#AND_ASSIGN} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ default Object andAssign(final Object a, final Object b) { return assign(a, bitwiseAnd(a, b)); } /** * Applies the {@link Operators#OR_ASSIGN} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ default Object orAssign(final Object a, final Object b) { return assign(a, bitwiseOr(a, b)); } /** * Applies the {@link Operators#LEFT_SHIFT_ASSIGN} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ default Object leftShiftAssign(final Object a, final Object b) { return assign(a, leftShift(a, b)); } /** * Applies the {@link Operators#RIGHT_SHIFT_ASSIGN} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ default Object rightShiftAssign(final Object a, final Object b) { return assign(a, rightShift(a, b)); } /** * Applies the {@link Operators#UNSIGNED_RIGHT_SHIFT_ASSIGN} operator. * * @param a The first argument. * @param b The second argument. * @return The result of the operation. */ default Object unsignedRightShiftAssign(final Object a, final Object b) { return assign(a, unsignedRightShift(a, b)); } /** * Performs an operation. * * @param op The operator to execute. * @param args The arguments to pass. * @return The result of the operation. */ default Object execute(final Operator op, final Object... args) { final Object a = args.length > 0 ? args[0] : null; final Object b = args.length > 1 ? args[1] : null; // Let the case logic begin! if (op instanceof Function) return function(a, b); if (op == Operators.DOT) return dot(a, b); if (Tokens.isMatchingGroup(op, Operators.PARENS)) return parens(args); if (Tokens.isMatchingGroup(op, Operators.BRACKETS)) return brackets(args); if (Tokens.isMatchingGroup(op, Operators.BRACES)) return braces(args); if (op == Operators.TRANSPOSE) return transpose(a); if (op == Operators.DOT_TRANSPOSE) return dotTranspose(a); if (op == Operators.POW) return pow(a, b); if (op == Operators.DOT_POW) return dotPow(a, b); if (op == Operators.POST_INC) return postInc(a); if (op == Operators.POST_DEC) return postDec(a); if (op == Operators.PRE_INC) return preInc(a); if (op == Operators.PRE_DEC) return preDec(a); if (op == Operators.POS) return pos(a); if (op == Operators.NEG) return neg(a); if (op == Operators.COMPLEMENT) return complement(a); if (op == Operators.NOT) return not(a); if (op == Operators.MUL) return mul(a, b); if (op == Operators.DIV) return div(a, b); if (op == Operators.MOD) return mod(a, b); if (op == Operators.RIGHT_DIV) return rightDiv(a, b); if (op == Operators.DOT_MUL) return dotMul(a, b); if (op == Operators.DOT_DIV) return dotDiv(a, b); if (op == Operators.DOT_RIGHT_DIV) return dotRightDiv(a, b); if (op == Operators.ADD) return add(a, b); if (op == Operators.SUB) return sub(a, b); if (op == Operators.LEFT_SHIFT) return leftShift(a, b); if (op == Operators.RIGHT_SHIFT) return rightShift(a, b); if (op == Operators.UNSIGNED_RIGHT_SHIFT) return unsignedRightShift(a, b); if (op == Operators.LESS_THAN) return lessThan(a, b); if (op == Operators.GREATER_THAN) return greaterThan(a, b); if (op == Operators.LESS_THAN_OR_EQUAL) return lessThanOrEqual(a, b); if (op == Operators.GREATER_THAN_OR_EQUAL) return greaterThanOrEqual(a, b); if (op == Operators.INSTANCEOF) return instanceOf(a, b); if (op == Operators.EQUAL) return equal(a, b); if (op == Operators.NOT_EQUAL) return notEqual(a, b); if (op == Operators.BITWISE_AND) return bitwiseAnd(a, b); if (op == Operators.BITWISE_OR) return bitwiseOr(a, b); if (op == Operators.LOGICAL_AND) return logicalAnd(a, b); if (op == Operators.LOGICAL_OR) return logicalOr(a, b); if (op == Operators.QUESTION) return question(a, b); if (op == Operators.COLON) return colon(a, b); if (op == Operators.ASSIGN) return assign(a, b); if (op == Operators.POW_ASSIGN) return powAssign(a, b); if (op == Operators.DOT_POW_ASSIGN) return dotPowAssign(a, b); if (op == Operators.MUL_ASSIGN) return mulAssign(a, b); if (op == Operators.DIV_ASSIGN) return divAssign(a, b); if (op == Operators.MOD_ASSIGN) return modAssign(a, b); if (op == Operators.RIGHT_DIV_ASSIGN) return rightDivAssign(a, b); if (op == Operators.DOT_DIV_ASSIGN) return dotDivAssign(a, b); if (op == Operators.DOT_RIGHT_DIV_ASSIGN) return dotRightDivAssign(a, b); if (op == Operators.ADD_ASSIGN) return addAssign(a, b); if (op == Operators.SUB_ASSIGN) return subAssign(a, b); if (op == Operators.AND_ASSIGN) return andAssign(a, b); if (op == Operators.OR_ASSIGN) return orAssign(a, b); if (op == Operators.LEFT_SHIFT_ASSIGN) return leftShiftAssign(a, b); if (op == Operators.RIGHT_SHIFT_ASSIGN) return rightShiftAssign(a, b); if (op == Operators.UNSIGNED_RIGHT_SHIFT_ASSIGN) return unsignedRightShiftAssign(a, b); // Unknown operator. return null; } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/eval/StandardStackEvaluator.java000066400000000000000000000041121440242745700333220ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington.eval; import java.util.Deque; import org.scijava.parsington.Operator; /** * Interface for stack-based evaluators which support the standard operators. * * @author Curtis Rueden */ public interface StandardStackEvaluator extends StandardEvaluator, StackEvaluator { // -- StackEvaluator methods -- @Override default Object execute(final Operator op, final Deque stack) { // Pop the arguments. final int arity = op.getArity(); final Object[] args = new Object[arity]; for (int i = args.length - 1; i >= 0; i--) { args[i] = stack.pop(); } return execute(op, args); } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/eval/StandardTreeEvaluator.java000066400000000000000000000072041440242745700331610ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington.eval; import org.scijava.parsington.Operator; import org.scijava.parsington.Operators; import org.scijava.parsington.SyntaxTree; /** * Interface for tree-based evaluators which support the standard operators. * * @author Curtis Rueden */ public interface StandardTreeEvaluator extends StandardEvaluator, TreeEvaluator { // -- TreeEvaluator methods -- @Override default Object execute(final Operator op, final SyntaxTree tree) { // Handle short-circuiting operators first. if (op == Operators.LOGICAL_AND) { final Object leftValue = value(evaluate(tree.child(0))); if (leftValue instanceof Boolean && !((Boolean) leftValue).booleanValue()) { // Left side is false, so entire expression will be false. return false; } final Object rightValue = value(evaluate(tree.child(1))); return execute(op, new Object[] {leftValue, rightValue}); } else if (op == Operators.LOGICAL_OR) { final Object leftValue = value(evaluate(tree.child(0))); if (leftValue instanceof Boolean && ((Boolean) leftValue).booleanValue()) { // Left side is true, so entire expression will be true. return true; } final Object rightValue = value(evaluate(tree.child(1))); return execute(op, new Object[] {leftValue, rightValue}); } else if (op == Operators.QUESTION) { final SyntaxTree conditional = tree.child(0); final SyntaxTree consequent = tree.child(1); if (consequent.token() != Operators.COLON || consequent.count() != 2) { throw new IllegalArgumentException("Invalid ternary operator syntax"); } final Object conditionalValue = value(evaluate(conditional)); if (!(conditionalValue instanceof Boolean)) { throw new IllegalArgumentException("Invalid ternary operator conditional expression"); } return ((Boolean) conditionalValue) ? evaluate(consequent.child(0)) : evaluate(consequent.child(1)); } // Recursively evaluate the subtrees. None of the remaining operators // benefit from short-circuiting, so we can evaluate subexpressions eagerly. final Object[] args = new Object[tree.count()]; for (int i = 0; i < args.length; i++) { args[i] = evaluate(tree.child(i)); } return execute(op, args); } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/eval/TreeEvaluator.java000066400000000000000000000055741440242745700315100ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington.eval; import java.util.LinkedList; import org.scijava.parsington.Operator; import org.scijava.parsington.SyntaxTree; import org.scijava.parsington.Tokens; /** * Interface for tree-based expression evaluators, operating on syntax trees. * * @author Curtis Rueden */ public interface TreeEvaluator extends Evaluator { /** * Executes an {@link Operator operation} on the specified {@link SyntaxTree * syntax tree}'s children. * * @param op The operator to execute. * @param tree The syntax tree containing the arguments to pass. * @return The result of the operation. */ Object execute(final Operator op, final SyntaxTree tree); // -- Evaluator methods -- @Override default Object evaluate(final String expression) { // Convert the expression to a syntax tree. return evaluate(getParser().parseTree(expression)); } @Override default Object evaluate(final LinkedList queue) { // Convert the postfix queue to a syntax tree. return evaluate(new SyntaxTree(queue)); } @Override default Object evaluate(final SyntaxTree syntaxTree) { // Evaluate the syntax tree recursively. final Object token = syntaxTree.token(); if (Tokens.isOperator(token)) { final Operator op = (Operator) token; assert op.getArity() == syntaxTree.count(); return execute(op, syntaxTree); } // Token is a variable or a literal. assert syntaxTree.count() == 0; return token; } } parsington-parsington-3.1.0/src/main/java/org/scijava/parsington/eval/Unresolved.java000066400000000000000000000033641440242745700310470ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington.eval; import org.scijava.parsington.Token; /** * An unresolved/unknown variable value. * * @author Curtis Rueden * @see Evaluator#setStrict(boolean) */ public class Unresolved extends Token { public Unresolved(final String token) { super(token); } } parsington-parsington-3.1.0/src/test/000077500000000000000000000000001440242745700176405ustar00rootroot00000000000000parsington-parsington-3.1.0/src/test/java/000077500000000000000000000000001440242745700205615ustar00rootroot00000000000000parsington-parsington-3.1.0/src/test/java/org/000077500000000000000000000000001440242745700213505ustar00rootroot00000000000000parsington-parsington-3.1.0/src/test/java/org/scijava/000077500000000000000000000000001440242745700227705ustar00rootroot00000000000000parsington-parsington-3.1.0/src/test/java/org/scijava/parsington/000077500000000000000000000000001440242745700251545ustar00rootroot00000000000000parsington-parsington-3.1.0/src/test/java/org/scijava/parsington/AbstractTest.java000066400000000000000000000072071440242745700304300ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; /** * Base class for unit test classes. * * @author Curtis Rueden */ public class AbstractTest { // -- Helper methods -- protected void assertString(final String expected, final Object actual) { assertNotNull(actual); assertSame(expected.getClass(), actual.getClass()); assertEquals(expected, actual); } protected void assertNumber(final Number expected, final Object actual) { assertNotNull(actual); assertSame(expected.getClass(), actual.getClass()); assertEquals(expected, actual); } protected void assertGroup(final Group expected, final int arity, final Object token) { assertInstance(token, Group.class); final Group group = (Group) token; assertEquals(arity, group.getArity()); assertTrue(expected.matches(group)); } protected void assertFunction(final Object token) { assertInstance(token, Function.class); } protected void assertVariable(final String expected, final Object token) { assertInstance(token, Variable.class); assertEquals(expected, ((Variable) token).getToken()); } protected void assertInstance(final Object token, final Class c) { assertNotNull(token); assertTrue(c.isInstance(token), token.getClass().getName()); } protected void assertCount(final int expected, final SyntaxTree tree) { assertNotNull(tree); assertEquals(expected, tree.count()); } protected void assertUnary(final SyntaxTree tree) { assertCount(1, tree); } protected void assertBinary(final SyntaxTree tree) { assertCount(2, tree); } protected void assertToken(final String expected, final Object token) { assertNotNull(token); assertEquals(expected, token.toString()); } protected void assertSameLists(final List expected, final List actual) { assertNotNull(actual); assertEquals(expected.size(), actual.size()); for (int i = 0; i < expected.size(); i++) { assertSame(expected.get(i), actual.get(i), "Non-matching index: " + i); } } } parsington-parsington-3.1.0/src/test/java/org/scijava/parsington/ExpressionParserTest.java000066400000000000000000001056321440242745700322020ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import java.util.LinkedList; import java.util.List; import org.junit.jupiter.api.Test; import org.scijava.parsington.Operator.Associativity; /** * Tests {@link ExpressionParser}. * * @author Curtis Rueden */ public class ExpressionParserTest extends AbstractTest { @Test public void testStrings() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("'hello'*\"world\""); // "hello" "world" * assertNotNull(queue); assertEquals(3, queue.size()); assertString("hello", queue.pop()); assertString("world", queue.pop()); assertSame(Operators.MUL, queue.pop()); } @Test public void testNumbers() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("1.1+2L+3f+4-+5d-6"); // 1.1 2L + 3f + 4 + +5d - 6 - assertNotNull(queue); assertEquals(11, queue.size()); assertNumber(1.1, queue.pop()); assertNumber(2L, queue.pop()); assertSame(Operators.ADD, queue.pop()); assertNumber(3f, queue.pop()); assertSame(Operators.ADD, queue.pop()); assertNumber(4, queue.pop()); assertSame(Operators.ADD, queue.pop()); assertNumber(+5d, queue.pop()); assertSame(Operators.SUB, queue.pop()); assertNumber(6, queue.pop()); assertSame(Operators.SUB, queue.pop()); } @Test public void testVariables() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("x+y-_z"); assertNotNull(queue); assertEquals(5, queue.size()); assertVariable("x", queue.pop()); assertVariable("y", queue.pop()); assertSame(Operators.ADD, queue.pop()); assertVariable("_z", queue.pop()); assertSame(Operators.SUB, queue.pop()); } /** Tests each individual standard operator. */ @Test public void testOperatorsIndividual() { final ExpressionParser p = new ExpressionParser(); assertBinary(p, "a", Operators.DOT, "b", "a.b"); assertUnary(p, "a", Operators.TRANSPOSE, "a'"); assertUnary(p, "a", Operators.DOT_TRANSPOSE, "a.'"); assertBinary(p, "a", Operators.POW, "b", "a^b"); assertBinary(p, "a", Operators.DOT_POW, "b", "a.^b"); assertUnary(p, "a", Operators.PRE_INC, "++a"); assertUnary(p, "a", Operators.POST_INC, "a++"); assertUnary(p, "a", Operators.PRE_DEC, "--a"); assertUnary(p, "a", Operators.POST_DEC, "a--"); assertUnary(p, "a", Operators.POS, "+a"); assertUnary(p, "a", Operators.NEG, "-a"); assertUnary(p, "a", Operators.COMPLEMENT, "~a"); assertUnary(p, "a", Operators.NOT, "!a"); assertBinary(p, "a", Operators.MUL, "b", "a*b"); assertBinary(p, "a", Operators.DIV, "b", "a/b"); assertBinary(p, "a", Operators.MOD, "b", "a%b"); assertBinary(p, "a", Operators.ADD, "b", "a+b"); assertBinary(p, "a", Operators.SUB, "b", "a-b"); assertBinary(p, "a", Operators.LEFT_SHIFT, "b", "a<>b"); assertBinary(p, "a", Operators.UNSIGNED_RIGHT_SHIFT, "b", "a>>>b"); assertBinary(p, "a", Operators.COLON, "b", "a:b"); assertBinary(p, "a", Operators.LESS_THAN, "b", "ab"); assertBinary(p, "a", Operators.LESS_THAN_OR_EQUAL, "b", "a<=b"); assertBinary(p, "a", Operators.GREATER_THAN_OR_EQUAL, "b", "a>=b"); assertBinary(p, "a", Operators.INSTANCEOF, "b", "a instanceof b"); assertBinary(p, "a", Operators.EQUAL, "b", "a==b"); assertBinary(p, "a", Operators.NOT_EQUAL, "b", "a!=b"); assertBinary(p, "a", Operators.BITWISE_AND, "b", "a&b"); assertBinary(p, "a", Operators.BITWISE_OR, "b", "a|b"); assertBinary(p, "a", Operators.LOGICAL_AND, "b", "a&&b"); assertBinary(p, "a", Operators.LOGICAL_OR, "b", "a||b"); assertBinary(p, "a", Operators.ASSIGN, "b", "a=b"); assertBinary(p, "a", Operators.POW_ASSIGN, "b", "a^=b"); assertBinary(p, "a", Operators.DOT_POW_ASSIGN, "b", "a.^=b"); assertBinary(p, "a", Operators.MUL_ASSIGN, "b", "a*=b"); assertBinary(p, "a", Operators.DIV_ASSIGN, "b", "a/=b"); assertBinary(p, "a", Operators.MOD_ASSIGN, "b", "a%=b"); assertBinary(p, "a", Operators.RIGHT_DIV_ASSIGN, "b", "a\\=b"); assertBinary(p, "a", Operators.DOT_DIV_ASSIGN, "b", "a./=b"); assertBinary(p, "a", Operators.DOT_RIGHT_DIV_ASSIGN, "b", "a.\\=b"); assertBinary(p, "a", Operators.ADD_ASSIGN, "b", "a+=b"); assertBinary(p, "a", Operators.SUB_ASSIGN, "b", "a-=b"); assertBinary(p, "a", Operators.AND_ASSIGN, "b", "a&=b"); assertBinary(p, "a", Operators.OR_ASSIGN, "b", "a|=b"); assertBinary(p, "a", Operators.LEFT_SHIFT_ASSIGN, "b", "a<<=b"); assertBinary(p, "a", Operators.RIGHT_SHIFT_ASSIGN, "b", "a>>=b"); assertBinary(p, "a", Operators.UNSIGNED_RIGHT_SHIFT_ASSIGN, "b", "a>>>=b"); } /** Tests all the numeric operators in a single expression. */ @Test public void testMathOperators() { final String expression = "(a|=b)|(c&=d)&(e>>>=f)>>>(g>>=h)>>(i<<=j)<<(k-=l)-(m+=n)" + "+(o.\\=p).\\(q./=r)./(s\\=t)\\(u%=v)%(w/=x)/(y*=z)" + "*(aa.^=bb).^(cc^=dd)^f(~ee--,-ff++,+--gg',++hh.')"; final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix(expression); // a b |= (1) c d &= (1) e f >>>= (1) g h >>= (1) >>> i j <<= (1) >> k l // -= (1) m n += (1) - o p .\= (1) q r ./= (1) .\ s t \= (1) ./ u v %= // (1) \ w x /= (1) % y z *= (1) / aa bb .^= (1) cc dd ^= (1) f ee -- ~ // ff ++ - gg ' -- + hh .' ++ (4) ^ .^ * + << & | assertNotNull(queue); assertEquals(91, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertSame(Operators.OR_ASSIGN, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertVariable("c", queue.pop()); assertVariable("d", queue.pop()); assertSame(Operators.AND_ASSIGN, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertVariable("e", queue.pop()); assertVariable("f", queue.pop()); assertSame(Operators.UNSIGNED_RIGHT_SHIFT_ASSIGN, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertVariable("g", queue.pop()); assertVariable("h", queue.pop()); assertSame(Operators.RIGHT_SHIFT_ASSIGN, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertSame(Operators.UNSIGNED_RIGHT_SHIFT, queue.pop()); assertVariable("i", queue.pop()); assertVariable("j", queue.pop()); assertSame(Operators.LEFT_SHIFT_ASSIGN, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertSame(Operators.RIGHT_SHIFT, queue.pop()); assertVariable("k", queue.pop()); assertVariable("l", queue.pop()); assertSame(Operators.SUB_ASSIGN, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertVariable("m", queue.pop()); assertVariable("n", queue.pop()); assertSame(Operators.ADD_ASSIGN, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertSame(Operators.SUB, queue.pop()); assertVariable("o", queue.pop()); assertVariable("p", queue.pop()); assertSame(Operators.DOT_RIGHT_DIV_ASSIGN, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertVariable("q", queue.pop()); assertVariable("r", queue.pop()); assertSame(Operators.DOT_DIV_ASSIGN, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertSame(Operators.DOT_RIGHT_DIV, queue.pop()); assertVariable("s", queue.pop()); assertVariable("t", queue.pop()); assertSame(Operators.RIGHT_DIV_ASSIGN, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertSame(Operators.DOT_DIV, queue.pop()); assertVariable("u", queue.pop()); assertVariable("v", queue.pop()); assertSame(Operators.MOD_ASSIGN, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertSame(Operators.RIGHT_DIV, queue.pop()); assertVariable("w", queue.pop()); assertVariable("x", queue.pop()); assertSame(Operators.DIV_ASSIGN, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertSame(Operators.MOD, queue.pop()); assertVariable("y", queue.pop()); assertVariable("z", queue.pop()); assertSame(Operators.MUL_ASSIGN, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertSame(Operators.DIV, queue.pop()); assertVariable("aa", queue.pop()); assertVariable("bb", queue.pop()); assertSame(Operators.DOT_POW_ASSIGN, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertVariable("cc", queue.pop()); assertVariable("dd", queue.pop()); assertSame(Operators.POW_ASSIGN, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertVariable("f", queue.pop()); assertVariable("ee", queue.pop()); assertSame(Operators.POST_DEC, queue.pop()); assertSame(Operators.COMPLEMENT, queue.pop()); assertVariable("ff", queue.pop()); assertSame(Operators.POST_INC, queue.pop()); assertSame(Operators.NEG, queue.pop()); assertVariable("gg", queue.pop()); assertSame(Operators.TRANSPOSE, queue.pop()); assertSame(Operators.PRE_DEC, queue.pop()); assertSame(Operators.POS, queue.pop()); assertVariable("hh", queue.pop()); assertSame(Operators.DOT_TRANSPOSE, queue.pop()); assertSame(Operators.PRE_INC, queue.pop()); assertGroup(Operators.PARENS, 4, queue.pop()); assertFunction(queue.pop()); assertSame(Operators.POW, queue.pop()); assertSame(Operators.DOT_POW, queue.pop()); assertSame(Operators.MUL, queue.pop()); assertSame(Operators.ADD, queue.pop()); assertSame(Operators.LEFT_SHIFT, queue.pop()); assertSame(Operators.BITWISE_AND, queue.pop()); assertSame(Operators.BITWISE_OR, queue.pop()); } /** Tests all the boolean operators in a single expression. */ @Test public void testLogicOperators() { final String expression = "ad && e<=f || g>=h && i==j || k!=l && m instanceof n || !o"; final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix(expression); // a b |= c d &= e f >>>= g h >>= >>> i j <<= >> k l -= m n += - o p .\= // q r ./= .\ s t \= ./ u v %= \ w x /= % y z *= / aa bb .^= cc dd ^= ee // -- ~ ff ++ - gg ' -- + hh .' ++ f ^ .^ * + << & | assertNotNull(queue); assertEquals(30, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertSame(Operators.LESS_THAN, queue.pop()); assertVariable("c", queue.pop()); assertVariable("d", queue.pop()); assertSame(Operators.GREATER_THAN, queue.pop()); assertVariable("e", queue.pop()); assertVariable("f", queue.pop()); assertSame(Operators.LESS_THAN_OR_EQUAL, queue.pop()); assertSame(Operators.LOGICAL_AND, queue.pop()); assertSame(Operators.LOGICAL_OR, queue.pop()); assertVariable("g", queue.pop()); assertVariable("h", queue.pop()); assertSame(Operators.GREATER_THAN_OR_EQUAL, queue.pop()); assertVariable("i", queue.pop()); assertVariable("j", queue.pop()); assertSame(Operators.EQUAL, queue.pop()); assertSame(Operators.LOGICAL_AND, queue.pop()); assertSame(Operators.LOGICAL_OR, queue.pop()); assertVariable("k", queue.pop()); assertVariable("l", queue.pop()); assertSame(Operators.NOT_EQUAL, queue.pop()); assertVariable("m", queue.pop()); assertVariable("n", queue.pop()); assertSame(Operators.INSTANCEOF, queue.pop()); assertSame(Operators.LOGICAL_AND, queue.pop()); assertSame(Operators.LOGICAL_OR, queue.pop()); assertVariable("o", queue.pop()); assertSame(Operators.NOT, queue.pop()); assertSame(Operators.LOGICAL_OR, queue.pop()); } @Test public void testUnaryOperators1() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("-+a"); // a + - assertNotNull(queue); assertEquals(3, queue.size()); assertVariable("a", queue.pop()); assertSame(Operators.POS, queue.pop()); assertSame(Operators.NEG, queue.pop()); } @Test public void testUnaryOperators2() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("a+-b"); // a b - + assertNotNull(queue); assertEquals(4, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertSame(Operators.NEG, queue.pop()); assertSame(Operators.ADD, queue.pop()); } @Test public void testUnaryOperators3() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("a-+-b"); // a b - + - assertNotNull(queue); assertEquals(5, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertSame(Operators.NEG, queue.pop()); assertSame(Operators.POS, queue.pop()); assertSame(Operators.SUB, queue.pop()); } @Test public void testUnaryOperators4() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("-+a-+-b"); // a + - b - + - assertNotNull(queue); assertEquals(7, queue.size()); assertVariable("a", queue.pop()); assertSame(Operators.POS, queue.pop()); assertSame(Operators.NEG, queue.pop()); assertVariable("b", queue.pop()); assertSame(Operators.NEG, queue.pop()); assertSame(Operators.POS, queue.pop()); assertSame(Operators.SUB, queue.pop()); } @Test public void testNullaryGroup() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("()"); // (0) assertNotNull(queue); assertEquals(1, queue.size()); assertGroup(Operators.PARENS, 0, queue.pop()); } @Test public void testUnaryGroup() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("(a)"); // a (1) assertNotNull(queue); assertEquals(2, queue.size()); assertVariable("a", queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); } @Test public void testNestedGroups() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("(())"); // (0) (1) assertNotNull(queue); assertEquals(2, queue.size()); assertGroup(Operators.PARENS, 0, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); } @Test public void testNullaryFunction() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("f()"); // f (0) assertNotNull(queue); assertEquals(3, queue.size()); assertVariable("f", queue.pop()); assertGroup(Operators.PARENS, 0, queue.pop()); assertFunction(queue.pop()); } @Test public void testUnaryFunction() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("f(a)"); // f a (1) assertNotNull(queue); assertVariable("f", queue.pop()); assertVariable("a", queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertFunction(queue.pop()); } @Test public void testBinaryFunction() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("f(a,b)"); // f a b (2) assertNotNull(queue); assertEquals(5, queue.size()); assertVariable("f", queue.pop()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertGroup(Operators.PARENS, 2, queue.pop()); assertFunction(queue.pop()); } @Test public void testTernaryFunction() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("f(a,b,c)"); // f a b c (3) assertNotNull(queue); assertEquals(6, queue.size()); assertVariable("f", queue.pop()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertVariable("c", queue.pop()); assertGroup(Operators.PARENS, 3, queue.pop()); assertFunction(queue.pop()); } @Test public void testNestedFunctions() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("f(g(),a,h(b),i(c,d))"); // f g (0) a h b (1) i c d (2) (4) assertNotNull(queue); assertEquals(16, queue.size()); assertVariable("f", queue.pop()); assertVariable("g", queue.pop()); assertGroup(Operators.PARENS, 0, queue.pop()); assertFunction(queue.pop()); assertVariable("a", queue.pop()); assertVariable("h", queue.pop()); assertVariable("b", queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertFunction(queue.pop()); assertVariable("i", queue.pop()); assertVariable("c", queue.pop()); assertVariable("d", queue.pop()); assertGroup(Operators.PARENS, 2, queue.pop()); assertFunction(queue.pop()); assertGroup(Operators.PARENS, 4, queue.pop()); assertFunction(queue.pop()); } @Test public void testFunctionParens1() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("a.b(c)"); // a b . c (1) assertNotNull(queue); assertEquals(6, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertSame(Operators.DOT, queue.pop()); assertVariable("c", queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertFunction(queue.pop()); } @Test public void testFunctionParens2() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("a(b).c"); // a b (1) c . assertNotNull(queue); assertEquals(6, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertFunction(queue.pop()); assertVariable("c", queue.pop()); assertSame(Operators.DOT, queue.pop()); } @Test public void testFunctionParens3() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("a.b(c).d"); // a b . c (1) d . assertNotNull(queue); assertEquals(8, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertSame(Operators.DOT, queue.pop()); assertVariable("c", queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertFunction(queue.pop()); assertVariable("d", queue.pop()); assertSame(Operators.DOT, queue.pop()); } @Test public void testFunctionParens4() { final List operators = Operators.standardList(); final Operator atOp = new Operator("@", 2, Associativity.LEFT, 100); operators.add(atOp); final ExpressionParser parser = new ExpressionParser(operators); final LinkedList queue = parser.parsePostfix("a@b(c)@d"); // a b @ c (1) d @ assertNotNull(queue); assertEquals(8, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertSame(atOp, queue.pop()); assertVariable("c", queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertVariable("d", queue.pop()); assertSame(atOp, queue.pop()); assertFunction(queue.pop()); } @Test public void testFunctionBrackets1() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("a.b[c]"); // a b . c [1] assertNotNull(queue); assertEquals(6, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertSame(Operators.DOT, queue.pop()); assertVariable("c", queue.pop()); assertGroup(Operators.BRACKETS, 1, queue.pop()); assertFunction(queue.pop()); } @Test public void testFunctionBrackets2() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("a[b].c"); // a b [1] c . assertNotNull(queue); assertEquals(6, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertGroup(Operators.BRACKETS, 1, queue.pop()); assertFunction(queue.pop()); assertVariable("c", queue.pop()); assertSame(Operators.DOT, queue.pop()); } @Test public void testFunctionBrackets3() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("a.b[c].d"); // a b . c [1] d . assertNotNull(queue); assertEquals(8, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertSame(Operators.DOT, queue.pop()); assertVariable("c", queue.pop()); assertGroup(Operators.BRACKETS, 1, queue.pop()); assertFunction(queue.pop()); assertVariable("d", queue.pop()); assertSame(Operators.DOT, queue.pop()); } @Test public void testFunctionBrackets4() { final List operators = Operators.standardList(); final Operator atOp = new Operator("@", 2, Associativity.LEFT, 100); operators.add(atOp); final ExpressionParser parser = new ExpressionParser(operators); final LinkedList queue = parser.parsePostfix("a@b[c]@d"); // a b @ c [1] d @ assertNotNull(queue); assertEquals(8, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertSame(atOp, queue.pop()); assertVariable("c", queue.pop()); assertGroup(Operators.BRACKETS, 1, queue.pop()); assertVariable("d", queue.pop()); assertSame(atOp, queue.pop()); assertFunction(queue.pop()); } @Test public void testFunctionBraces1() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("a.b{c}"); // a b . c {1} assertNotNull(queue); assertEquals(6, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertSame(Operators.DOT, queue.pop()); assertVariable("c", queue.pop()); assertGroup(Operators.BRACES, 1, queue.pop()); assertFunction(queue.pop()); } @Test public void testFunctionBraces2() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("a{b}.c"); // a b {1} c . assertNotNull(queue); assertEquals(6, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertGroup(Operators.BRACES, 1, queue.pop()); assertFunction(queue.pop()); assertVariable("c", queue.pop()); assertSame(Operators.DOT, queue.pop()); } @Test public void testFunctionBraces3() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("a.b{c}.d"); // a b . c {1} d . assertNotNull(queue); assertEquals(8, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertSame(Operators.DOT, queue.pop()); assertVariable("c", queue.pop()); assertGroup(Operators.BRACES, 1, queue.pop()); assertFunction(queue.pop()); assertVariable("d", queue.pop()); assertSame(Operators.DOT, queue.pop()); } @Test public void testFunctionBraces4() { final List operators = Operators.standardList(); final Operator atOp = new Operator("@", 2, Associativity.LEFT, 100); operators.add(atOp); final ExpressionParser parser = new ExpressionParser(operators); final LinkedList queue = parser.parsePostfix("a@b{c}@d"); // a b @ c {1} d @ assertNotNull(queue); assertEquals(8, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertSame(atOp, queue.pop()); assertVariable("c", queue.pop()); assertGroup(Operators.BRACES, 1, queue.pop()); assertVariable("d", queue.pop()); assertSame(atOp, queue.pop()); assertFunction(queue.pop()); } @Test public void testMixedFunctions() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("a[b(c), d{3:4}, 2]"); // a b c (1) d 3 4 : {1} 2 [3] assertNotNull(queue); assertEquals(14, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertVariable("c", queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertFunction(queue.pop()); assertVariable("d", queue.pop()); assertNumber(3, queue.pop()); assertNumber(4, queue.pop()); assertSame(Operators.COLON, queue.pop()); assertGroup(Operators.BRACES, 1, queue.pop()); assertFunction(queue.pop()); assertNumber(2, queue.pop()); assertGroup(Operators.BRACKETS, 3, queue.pop()); assertFunction(queue.pop()); } @Test public void testParameterSyntax() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix( "(choices={'quick brown fox', 'lazy dog'}, persist=false, value=5)"); // choices 'quick brown fox' 'lazy dog' {2} = persist false = value 5 = (3) assertNotNull(queue); assertVariable("choices", queue.pop()); assertString("quick brown fox", queue.pop()); assertString("lazy dog", queue.pop()); assertGroup(Operators.BRACES, 2, queue.pop()); assertSame(Operators.ASSIGN, queue.pop()); assertVariable("persist", queue.pop()); assertSame(false, queue.pop()); assertSame(Operators.ASSIGN, queue.pop()); assertVariable("value", queue.pop()); assertNumber(5, queue.pop()); assertSame(Operators.ASSIGN, queue.pop()); assertGroup(Operators.PARENS, 3, queue.pop()); } @Test public void testNonFunctionGroup() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("f+(g)"); // f g (1) + assertNotNull(queue); assertEquals(4, queue.size()); assertVariable("f", queue.pop()); assertVariable("g", queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertSame(Operators.ADD, queue.pop()); } @Test public void testOperatorPrecedence() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("a+b*c^d.^e'"); // a b c d e .^ ^ ' * + assertNotNull(queue); assertEquals(10, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertVariable("c", queue.pop()); assertVariable("d", queue.pop()); assertVariable("e", queue.pop()); assertSame(Operators.DOT_POW, queue.pop()); assertSame(Operators.POW, queue.pop()); assertSame(Operators.TRANSPOSE, queue.pop()); assertSame(Operators.MUL, queue.pop()); assertSame(Operators.ADD, queue.pop()); } @Test public void testOperatorAssociativity() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("(a/b/c)+(a^b^c)"); // a b / c / (1) a b c ^ ^ (1) + assertNotNull(queue); assertEquals(13, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertSame(Operators.DIV, queue.pop()); assertVariable("c", queue.pop()); assertSame(Operators.DIV, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertVariable("c", queue.pop()); assertSame(Operators.POW, queue.pop()); assertSame(Operators.POW, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertSame(Operators.ADD, queue.pop()); } /** Tests multiple semicolon-separated statements. */ @Test public void testMultipleStatements() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("a=1 ; b=2 ; c=3"); // a 1 = b 2 = c 3 = assertNotNull(queue); assertEquals(9, queue.size()); assertVariable("a", queue.pop()); assertNumber(1, queue.pop()); assertSame(Operators.ASSIGN, queue.pop()); assertVariable("b", queue.pop()); assertNumber(2, queue.pop()); assertSame(Operators.ASSIGN, queue.pop()); assertVariable("c", queue.pop()); assertNumber(3, queue.pop()); assertSame(Operators.ASSIGN, queue.pop()); } /** Tests custom separator. */ @Test public void testCustomElementSeparator() { final ExpressionParser parser = new ExpressionParser( // Operators.standardList(), "@@", ";"); final LinkedList queue = parser.parsePostfix("f(2 @@ 3 @@ 4) + (5 @@ 6)"); // f 2 3 4 (3) 5 6 (2) + assertNotNull(queue); assertEquals(10, queue.size()); assertVariable("f", queue.pop()); assertNumber(2, queue.pop()); assertNumber(3, queue.pop()); assertNumber(4, queue.pop()); assertGroup(Operators.PARENS, 3, queue.pop()); assertFunction(queue.pop()); assertNumber(5, queue.pop()); assertNumber(6, queue.pop()); assertGroup(Operators.PARENS, 2, queue.pop()); assertSame(Operators.ADD, queue.pop()); } /** Tests multiple statements with custom separator. */ @Test public void testCustomGroupSeparator() { final ExpressionParser parser = new ExpressionParser( // Operators.standardList(), ",", "@@@"); final LinkedList queue = parser.parsePostfix("x=1 @@@ x += 3 @@@ y = x^2"); // x 1 = x 3 += y x 2 ^ = assertNotNull(queue); assertEquals(11, queue.size()); assertVariable("x", queue.pop()); assertNumber(1, queue.pop()); assertSame(Operators.ASSIGN, queue.pop()); assertVariable("x", queue.pop()); assertNumber(3, queue.pop()); assertSame(Operators.ADD_ASSIGN, queue.pop()); assertVariable("y", queue.pop()); assertVariable("x", queue.pop()); assertNumber(2, queue.pop()); assertSame(Operators.POW, queue.pop()); assertSame(Operators.ASSIGN, queue.pop()); } /** Tests customized {@link ParseOperation} function. */ @Test public void testCustomParseOperation() { final ExpressionParser parser = new ExpressionParser( // (p, expression) -> new ParseOperation(p, expression) { @Override protected Object parseLiteral() { // The number five is the best number! return super.parseLiteral() == null ? null : 5; } }); final LinkedList queue = parser.parsePostfix("('hello', 1 + 2 * 3)"); // 5 5 5 5 * + (2) assertNotNull(queue); assertEquals(7, queue.size()); assertNumber(5, queue.pop()); assertNumber(5, queue.pop()); assertNumber(5, queue.pop()); assertNumber(5, queue.pop()); assertSame(Operators.MUL, queue.pop()); assertSame(Operators.ADD, queue.pop()); assertGroup(Operators.PARENS, 2, queue.pop()); } /** A more complex test of {@link ExpressionParser#parsePostfix}. */ @Test public void testParsePostfix() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("a*b-c*a/func(quick,brown,fox)^foo^bar"); // a b * c a * func quick brown fox (3) foo bar ^ ^ / - assertNotNull(queue); assertEquals(18, queue.size()); assertVariable("a", queue.pop()); assertVariable("b", queue.pop()); assertSame(Operators.MUL, queue.pop()); assertVariable("c", queue.pop()); assertVariable("a", queue.pop()); assertSame(Operators.MUL, queue.pop()); assertVariable("func", queue.pop()); assertVariable("quick", queue.pop()); assertVariable("brown", queue.pop()); assertVariable("fox", queue.pop()); assertGroup(Operators.PARENS, 3, queue.pop()); assertFunction(queue.pop()); assertVariable("foo", queue.pop()); assertVariable("bar", queue.pop()); assertSame(Operators.POW, queue.pop()); assertSame(Operators.POW, queue.pop()); assertSame(Operators.DIV, queue.pop()); assertSame(Operators.SUB, queue.pop()); } /** Tests empty expressions. */ @Test public void testEmpty() { final ExpressionParser parser = new ExpressionParser(); assertTrue(parser.parsePostfix("").isEmpty()); } /** Tests expression ending with a unary minus. */ @Test public void testTrailingUnaryMinus() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("-"); assertNotNull(queue); assertEquals(1, queue.size()); assertSame(Operators.NEG, queue.pop()); } /** Tests expression ending with a binary minus. */ @Test public void testTrailingBinaryMinus() { final ExpressionParser parser = new ExpressionParser(); final LinkedList queue = parser.parsePostfix("1-"); assertNotNull(queue); assertEquals(2, queue.size()); assertSame(1, queue.pop()); assertSame(Operators.SUB, queue.pop()); } /** Tests expressions with extra whitespace. */ @Test public void testWhitespace() { final ExpressionParser parser = new ExpressionParser(); assertTrue(parser.parsePostfix(" ").isEmpty()); assertTrue(parser.parsePostfix(" \t \n \r").isEmpty()); final LinkedList queue = parser.parsePostfix(" 3 "); assertNotNull(queue); assertEquals(1, queue.size()); assertSame(3, queue.pop()); } /** Tests that parsing an invalid expression fails appropriately. */ @Test public void testInvalid() { final ExpressionParser parser = new ExpressionParser(); assertInvalid(parser, "a a", "Invalid character at index 2"); assertInvalid(parser, "func(,)", "Invalid character at index 5"); assertInvalid(parser, "foo,bar", "Misplaced separator or mismatched groups at index 4"); assertInvalid(parser, "(", "Mismatched groups at index 1"); assertInvalid(parser, ")", "Mismatched group terminator ')' at index 1"); assertInvalid(parser, ")(", "Mismatched group terminator ')' at index 1"); assertInvalid(parser, "(()", "Mismatched groups at index 3"); } // -- Helper methods -- private void assertUnary(final ExpressionParser parser, final String var, final Operator op, final String expression) { final LinkedList queue = parser.parsePostfix(expression); assertNotNull(queue); assertEquals(2, queue.size()); assertVariable(var, queue.pop()); assertSame(op, queue.pop()); } private void assertBinary(final ExpressionParser parser, final String var1, final Operator op, final String var2, final String expression) { final LinkedList queue = parser.parsePostfix(expression); assertNotNull(queue); assertEquals(3, queue.size()); assertVariable(var1, queue.pop()); assertVariable(var2, queue.pop()); assertSame(op, queue.pop()); } private void assertInvalid(final ExpressionParser parser, final String expression, final String message) { try { parser.parsePostfix(expression); fail("Expected IllegalArgumentException"); } catch (final IllegalArgumentException exc) { assertEquals(message, exc.getMessage()); } } } parsington-parsington-3.1.0/src/test/java/org/scijava/parsington/LiteralsTest.java000066400000000000000000000273301440242745700304430ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.fail; import java.math.BigInteger; import org.junit.jupiter.api.Test; /** * Tests {@link Literals}. * * @author Curtis Rueden */ public class LiteralsTest extends AbstractTest { @Test public void testParseBoolean() { assertSame(Boolean.FALSE, Literals.parseBoolean("false")); assertSame(Boolean.TRUE, Literals.parseBoolean("true")); assertNull(Literals.parseBoolean("zfalse")); assertNull(Literals.parseBoolean("zfalsez")); assertNull(Literals.parseBoolean("ztrue")); assertNull(Literals.parseBoolean("ztruez")); final Position pos = new Position(); pos.set(0); assertSame(Boolean.FALSE, Literals.parseBoolean("false-", pos)); assertEquals(5, pos.get()); pos.set(0); assertSame(Boolean.TRUE, Literals.parseBoolean("true-", pos)); assertEquals(4, pos.get()); } @Test public void testParseString() { assertEquals("hello world", Literals.parseString("'hello world'")); // Test escape sequences. assertEquals("a\b\t\n\f\r\"\\z", Literals .parseString("\"a\\b\\t\\n\\f\\r\\\"\\\\z\"")); assertEquals("\t\\\t\\\\\t", Literals .parseString("\"\\t\\\\\\t\\\\\\\\\\t\"")); // Test Unicode escape sequences. assertEquals("\u9654", Literals.parseString("\"\u9654\"")); assertEquals("xyz\u9654abc", Literals.parseString("\"xyz\\u9654abc\"")); // Test octal escape sequences. assertEquals("\0", Literals.parseString("\"\\0\"")); assertEquals("\00", Literals.parseString("\"\\00\"")); assertEquals("\000", Literals.parseString("\"\\000\"")); assertEquals("\12", Literals.parseString("\"\\12\"")); assertEquals("\123", Literals.parseString("\"\\123\"")); assertEquals("\377", Literals.parseString("\"\\377\"")); assertEquals("\1234", Literals.parseString("\"\\1234\"")); // Test position final Position pos = new Position(); pos.set(2); assertEquals("cde", Literals.parseString("ab'cde'fg", pos)); assertEquals(7, pos.get()); } @Test public void testParseStringInvalid() { // Test non-string tokens. assertNull(Literals.parseString("")); assertNull(Literals.parseString("1234")); assertNull(Literals.parseString("foo")); assertNull(Literals.parseString("a'b'c")); // Test malformed string literals. try { Literals.parseString("'"); fail("IllegalArgumentException expected"); } catch (final IllegalArgumentException exc) { assertEquals("Unclosed string literal at index 0", exc.getMessage()); } } @Test public void testParseHexInteger() { assertNumber(0x123, Literals.parseHex("0x123")); // Test explicit long. assertNumber(0x123L, Literals.parseHex("0x123L")); // Test implicit long. assertNumber(0x123456789abcdefL, Literals.parseHex("0x123456789abcdef")); // Test BigInteger. final String big = "123456789abcdeffedcba987654321"; final Number bigNum = Literals.parseHex("0x" + big); assertNumber(new BigInteger(big, 16), bigNum); } @Test public void testParseHexNegativeInteger() { assertNumber(-0x123, Literals.parseHex("-0x123")); // Test explicit long. assertNumber(-0x123L, Literals.parseHex("-0x123L")); // Test implicit long. assertNumber(-0x123456789abcdefL, Literals.parseHex("-0x123456789abcdef")); // Test BigInteger. final String big = "123456789abcdeffedcba987654321"; final Number bigNum = Literals.parseHex("-0x" + big); assertNumber(new BigInteger("-" + big, 16), bigNum); } @Test public void testParseHexFloat() { assertNumber(0xfedcba.98765432P-10f, // Literals.parseHex("0xfedcba.98765432P-10f")); assertNumber(0x1.fffffffffffffP+1023, // Literals.parseHex("0x1.fffffffffffffP+1023")); assertNumber(0xff.fP0F, Literals.parseHex("0xff.fP0F")); assertNumber(0x1P+1023, Literals.parseHex("0x1P+1023")); assertNumber(0xfP102, Literals.parseHex("0xfP102")); assertNumber(0xfP-102, Literals.parseHex("0xfP-102")); assertNumber(0xffP-102, Literals.parseHex("0xffP-102")); assertNumber(0x123.456P1, Literals.parseHex("0x123.456P1")); assertNumber(0x123.456P0f, Literals.parseHex("0x123.456P0f")); assertNumber(0x123.456P0d, Literals.parseHex("0x123.456P0d")); assertNumber(0x123.456P-10d, Literals.parseHex("0x123.456P-10d")); assertNumber(0x123.456P+009, Literals.parseHex("0x123.456P+009")); } @Test public void testParseHexNegativeFloat() { assertNumber(-0xfedcba.98765432P-10f, // Literals.parseHex("-0xfedcba.98765432P-10f")); assertNumber(-0x1.fffffffffffffP+1023, // Literals.parseHex("-0x1.fffffffffffffP+1023")); assertNumber(-0xff.fP0F, Literals.parseHex("-0xff.fP0F")); assertNumber(-0x1P+1023, Literals.parseHex("-0x1P+1023")); assertNumber(-0xfP102, Literals.parseHex("-0xfP102")); assertNumber(-0xfP-102, Literals.parseHex("-0xfP-102")); assertNumber(-0xffP-102, Literals.parseHex("-0xffP-102")); assertNumber(-0x123.456P1, Literals.parseHex("-0x123.456P1")); assertNumber(-0x123.456P0f, Literals.parseHex("-0x123.456P0f")); assertNumber(-0x123.456P0d, Literals.parseHex("-0x123.456P0d")); assertNumber(-0x123.456P-10d, Literals.parseHex("-0x123.456P-10d")); assertNumber(-0x123.456P+009, Literals.parseHex("-0x123.456P+009")); } @Test public void testParseBinary() { // NB: "0b..." syntax is only supported starting with Java 7. assertNumber(33, Literals.parseBinary("0b100001")); // Test explicit long. assertNumber(33L, Literals.parseBinary("0b100001L")); // Test implicit long. assertNumber(194588677707L, Literals.parseBinary( "0b10110101001110011000111001011001001011")); // Test BigInteger. final String big = "10110011100011110000111110000011111100000011111110000000" + "111111110000000011111111100000000011111111110000000000"; final Number bigNum = Literals.parseBinary("0b" + big); assertNumber(new BigInteger(big, 2), bigNum); } @Test public void testParseBinaryNegative() { // NB: "0b..." syntax is only supported starting with Java 7. assertNumber(-33, Literals.parseBinary("-0b100001")); // Test explicit long. assertNumber(-33L, Literals.parseBinary("-0b100001L")); // Test implicit long. assertNumber(-194588677707L, Literals.parseBinary( "-0b10110101001110011000111001011001001011")); // Test BigInteger. final String big = "10110011100011110000111110000011111100000011111110000000" + "111111110000000011111111100000000011111111110000000000"; final Number bigNum = Literals.parseBinary("-0b" + big); assertNumber(new BigInteger("-" + big, 2), bigNum); } @Test public void testParseOctal() { assertNumber(01234567, Literals.parseOctal("01234567")); // Test explicit long. assertNumber(01234567L, Literals.parseOctal("01234567L")); // Test implicit long. assertNumber(012345677654321L, Literals.parseOctal("012345677654321")); // Test BigInteger. final String big = "1234567765432112345677654321"; final Number bigNum = Literals.parseOctal("0" + big); assertNumber(new BigInteger(big, 8), bigNum); } @Test public void testParseOctalNegative() { assertNumber(-01234567, Literals.parseOctal("-01234567")); // Test explicit long. assertNumber(-01234567L, Literals.parseOctal("-01234567L")); // Test implicit long. assertNumber(-012345677654321L, Literals.parseOctal("-012345677654321")); // Test BigInteger. final String big = "1234567765432112345677654321"; final Number bigNum = Literals.parseOctal("-0" + big); assertNumber(new BigInteger("-" + big, 8), bigNum); } @Test public void testParseDecimal() { assertNumber(123456789, Literals.parseDecimal("123456789")); // Test explicit long. assertNumber(123456789L, Literals.parseDecimal("123456789L")); // Test implicit long. assertNumber(123456787654321L, Literals.parseDecimal("123456787654321")); // Test BigInteger. final String bigI = "1234567898765432123456789"; final Number bigInt = Literals.parseDecimal(bigI); assertNumber(new BigInteger(bigI), bigInt); // Test explicit float. assertNumber(1f, Literals.parseDecimal("1f")); // Test explicit double. assertNumber(1d, Literals.parseDecimal("1d")); // Test implicit double. assertNumber(1.0, Literals.parseDecimal("1.0")); assertNumber(1., Literals.parseDecimal("1.")); // Test scientific notation. assertNumber(1e2, Literals.parseDecimal("1e2")); assertNumber(1.2e3, Literals.parseDecimal("1.2e3")); assertNumber(4.5e-6, Literals.parseDecimal("4.5e-6")); assertNumber(1.2e3f, Literals.parseDecimal("1.2e3f")); assertNumber(4.5e-6f, Literals.parseDecimal("4.5e-6f")); } @Test public void testParseDecimalNegative() { assertNumber(-123456789, Literals.parseDecimal("-123456789")); // Test explicit long. assertNumber(-123456789L, Literals.parseDecimal("-123456789L")); // Test implicit long. assertNumber(-123456787654321L, Literals.parseDecimal("-123456787654321")); // Test BigInteger. final String bigI = "-1234567898765432123456789"; final Number bigInt = Literals.parseDecimal(bigI); assertNumber(new BigInteger(bigI), bigInt); // Test explicit float. assertNumber(-1f, Literals.parseDecimal("-1f")); // Test explicit double. assertNumber(-1d, Literals.parseDecimal("-1d")); // Test implicit double. assertNumber(-1.0, Literals.parseDecimal("-1.0")); assertNumber(-1., Literals.parseDecimal("-1.")); // Test scientific notation. assertNumber(-1e2, Literals.parseDecimal("-1e2")); assertNumber(-1.2e3, Literals.parseDecimal("-1.2e3")); assertNumber(-4.5e-6, Literals.parseDecimal("-4.5e-6")); assertNumber(-1.2e3f, Literals.parseDecimal("-1.2e3f")); assertNumber(-4.5e-6f, Literals.parseDecimal("-4.5e-6f")); } @Test public void testParseNumber() { final Position pos = new Position(); assertNumber(0, Literals.parseNumber("0", pos)); assertEquals(1, pos.get()); pos.set(1); assertNumber(5.7, Literals.parseNumber("a5.7a", pos)); assertEquals(4, pos.get()); pos.set(2); assertNumber(-11, Literals.parseNumber("bb-11bb", pos)); assertEquals(5, pos.get()); pos.set(3); assertNumber(0x123L, Literals.parseNumber("ccc0x123Lccc", pos)); assertEquals(9, pos.get()); } @Test public void testParseLiteral() { assertSame(Boolean.FALSE, Literals.parseLiteral("false")); assertSame(Boolean.TRUE, Literals.parseLiteral("true")); assertEquals("fubar", Literals.parseLiteral("'fubar'")); assertNumber(0, Literals.parseLiteral("0")); } } parsington-parsington-3.1.0/src/test/java/org/scijava/parsington/SubSequenceTest.java000066400000000000000000000077151440242745700311130ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Test; /** * Test {@link SubSequence}. * * @author Curtis Rueden */ public class SubSequenceTest { private static String PHRASE = "The quick brown fox jumped over the lazy dogs."; @Test public void testLength() { for (int off = 0; off < PHRASE.length(); off++) { for (int len = 0; len < PHRASE.length() - off; len++) { assertEquals(len, sub(off, len).length()); } } } @Test public void testCharAt() { for (int off = 0; off < PHRASE.length(); off++) { for (int len = 0; len < PHRASE.length() - off; len++) { final SubSequence sub = sub(off, len); for (int c = 0; c < sub.length(); c++) { assertEquals(PHRASE.charAt(off + c), sub.charAt(c)); } } } } @Test public void testSubSequence() { final SubSequence sub = sub(4, 15); // quick brown fox final SubSequence subSub = sub.subSequence(6, 11); // brown assertEquals(5, subSub.length()); assertEquals('b', subSub.charAt(0)); assertEquals('r', subSub.charAt(1)); assertEquals('o', subSub.charAt(2)); assertEquals('w', subSub.charAt(3)); assertEquals('n', subSub.charAt(4)); } @Test public void testToString() { assertEquals("quick brown fox", sub(4, 15).toString()); } @Test public void testNegativeOffset() { try { sub(-1, 1); fail("Expected IndexOutOfBoundsException"); } catch (final IndexOutOfBoundsException exc) { assertEquals("Offset -1 < 0", exc.getMessage()); } } @Test public void testNegativeLength() { try { sub(1, -1); fail("Expected IndexOutOfBoundsException"); } catch (final IndexOutOfBoundsException exc) { assertEquals("Length -1 < 0", exc.getMessage()); } } @Test public void testOffsetTooLarge() { final int len = PHRASE.length(); final int off = len + 1; try { sub(off, 1); fail("Expected IndexOutOfBoundsException"); } catch (final IndexOutOfBoundsException exc) { assertEquals("Offset " + off + " > " + len, exc.getMessage()); } } @Test public void testLengthTooLong() { final int len = PHRASE.length(); try { sub(1, len); fail("Expected IndexOutOfBoundsException"); } catch (final IndexOutOfBoundsException exc) { assertEquals("Offset 1 + length " + len + " > " + len, exc.getMessage()); } } // -- Helper methods -- private SubSequence sub(final int offset, final int length) { return new SubSequence(PHRASE, offset, length); } } parsington-parsington-3.1.0/src/test/java/org/scijava/parsington/SyntaxTreeTest.java000066400000000000000000000125671440242745700310000ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import java.util.Arrays; import java.util.LinkedList; import org.junit.jupiter.api.Test; /** * Tests {@link SyntaxTree}. * * @author Curtis Rueden */ public class SyntaxTreeTest extends AbstractTest { @Test public void testSimple() { // infix: a + b * c // postfix: a b c * + final LinkedList queue = queue(var("a"), var("b"), var("c"), Operators.MUL, Operators.ADD); // NB: SyntaxTree constructor consumes the queue, so save a copy. final LinkedList expected = new LinkedList<>(queue); final SyntaxTree tree = new SyntaxTree(queue); assertNotNull(tree); assertSame(Operators.ADD, tree.token()); assertBinary(tree); assertVariable("a", tree.child(0).token()); assertSame(Operators.MUL, tree.child(1).token()); assertBinary(tree.child(1)); assertVariable("b", tree.child(1).child(0).token()); assertVariable("c", tree.child(1).child(1).token()); assertSameLists(expected, tree.postfix()); } @Test public void testComplicated() { // infix: a / b * c + a ^ b ^ c - f(d, c, b, a) // postfix: a b / c * a b c ^ ^ + f d c b a (4) - final LinkedList queue = queue(var("a"), var("b"), Operators.DIV, var("c"), Operators.MUL, var("a"), var("b"), var("c"), Operators.POW, Operators.POW, Operators.ADD, var("f"), var("d"), var("c"), var("b"), var("a"), group(Operators.PARENS, 4), func(), Operators.SUB); // NB: SyntaxTree constructor consumes the queue, so save a copy. final LinkedList expected = new LinkedList<>(queue); final SyntaxTree tree = new SyntaxTree(queue); assertNotNull(tree); assertSame(Operators.SUB, token(tree)); assertSame(Operators.ADD, token(tree, 0)); assertSame(Operators.MUL, token(tree, 0, 0)); assertSame(Operators.DIV, token(tree, 0, 0, 0)); assertVariable("a", token(tree, 0, 0, 0, 0)); assertVariable("b", token(tree, 0, 0, 0, 1)); assertVariable("c", token(tree, 0, 0, 1)); assertSame(Operators.POW, token(tree, 0, 1)); assertVariable("a", token(tree, 0, 1, 0)); assertSame(Operators.POW, token(tree, 0, 1, 1)); assertVariable("b", token(tree, 0, 1, 1, 0)); assertVariable("c", token(tree, 0, 1, 1, 1)); assertFunction(token(tree, 1)); assertVariable("f", token(tree, 1, 0)); assertGroup(Operators.PARENS, 4, token(tree, 1, 1)); assertVariable("d", token(tree, 1, 1, 0)); assertVariable("c", token(tree, 1, 1, 1)); assertVariable("b", token(tree, 1, 1, 2)); assertVariable("a", token(tree, 1, 1, 3)); assertSameLists(expected, tree.postfix()); } @Test public void testNullaryFunction() { // infix: f() // postfix: f (0) final LinkedList queue = queue(var("f"), group(Operators.PARENS, 0), func()); // NB: SyntaxTree constructor consumes the queue, so save a copy. final LinkedList expected = new LinkedList<>(queue); final SyntaxTree tree = new SyntaxTree(queue); assertNotNull(tree); assertFunction(token(tree)); assertVariable("f", token(tree, 0)); assertGroup(Operators.PARENS, 0, token(tree, 1)); assertSameLists(expected, tree.postfix()); } // -- Helper methods -- private LinkedList queue(final Object... args) { return new LinkedList<>(Arrays.asList(args)); } private Variable var(final String token) { return new Variable(token); } private Group group(final Group g, final int arity) { final Group group = g.instance(); for (int a = 0; a < arity; a++) group.incArity(); return group; } private Function func() { return new Function(0); // NB: Precedence does not matter here. } private Object token(final SyntaxTree tree, final int... indices) { if (indices.length == 0) return tree.token(); final int[] subIndices = new int[indices.length - 1]; System.arraycopy(indices, 1, subIndices, 0, subIndices.length); return token(tree.child(indices[0]), subIndices); } } parsington-parsington-3.1.0/src/test/java/org/scijava/parsington/TestExamples.java000066400000000000000000000313071440242745700304410ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.scijava.parsington.Operator.Associativity; import org.scijava.parsington.eval.DefaultTreeEvaluator; import org.scijava.parsington.eval.Evaluator; /** * Working examples of Parsington in action. * * @author Curtis Rueden */ public class TestExamples extends AbstractTest { /** * An example of the expression parser with the standard operators. See also * {@link ExpressionParserTest} for many more simple examples. */ @Test public void standardOperators() { final ExpressionParser parser = new ExpressionParser(); final String expression = "(-b + sqrt(b^2 - 4*a*c)) / (2*a)"; final SyntaxTree quadraticFormula = parser.parseTree(expression); // Traverse the syntax tree to see if the nodes match what we expect. // The top level of the tree is a fraction. assertSame(Operators.DIV, quadraticFormula.token()); assertBinary(quadraticFormula); final SyntaxTree numer = quadraticFormula.child(0); final SyntaxTree denom = quadraticFormula.child(1); // Because the numerator is in parentheses, it is parsed as a unary group. assertUnary(numer); assertGroup(Operators.PARENS, 1, numer.token()); // The numerator (inside the parentheses) is the sum of two expressions. final SyntaxTree innerNumer = numer.child(0); assertSame(Operators.ADD, innerNumer.token()); assertBinary(innerNumer); // The numerator's first expression, -b, is a unary negation. final SyntaxTree minusB = innerNumer.child(0); assertSame(Operators.NEG, minusB.token()); assertUnary(minusB); assertVariable("b", minusB.child(0).token()); // The numerator's second expression, sqrt(b^2 - 4*a*c), is a function call. final SyntaxTree sqrtFn = innerNumer.child(1); assertFunction(sqrtFn.token()); assertBinary(sqrtFn); final SyntaxTree sqrtVar = sqrtFn.child(0); assertVariable("sqrt", sqrtVar.token()); // The sqrt function is applied to a unary group. final SyntaxTree sqrtTarget = sqrtFn.child(1); assertUnary(sqrtTarget); assertGroup(Operators.PARENS, 1, sqrtTarget.token()); // The expression inside the sqrt function has two terms, subtracted. final SyntaxTree sqrtExpr = sqrtTarget.child(0); assertSame(Operators.SUB, sqrtExpr.token()); assertBinary(sqrtExpr); // The first term, b^2, is exponentiation. final SyntaxTree bSquared = sqrtExpr.child(0); assertSame(Operators.POW, bSquared.token()); assertBinary(bSquared); assertVariable("b", bSquared.child(0).token()); assertEquals(2, bSquared.child(1).token()); // The second term, 4*a*c, goes one level deeper. final SyntaxTree fourAC = sqrtExpr.child(1); assertSame(Operators.MUL, fourAC.token()); assertBinary(fourAC); final SyntaxTree fourA = fourAC.child(0); assertSame(Operators.MUL, fourA.token()); assertBinary(fourA); assertEquals(4, fourA.child(0).token()); assertVariable("a", fourA.child(1).token()); assertVariable("c", fourAC.child(1).token()); // Because the denominator is in parentheses, it is parsed as a unary group. assertUnary(denom); assertGroup(Operators.PARENS, 1, denom.token()); // The denominator (inside the parentheses) is a product of two terms. final SyntaxTree innerDenom = denom.child(0); assertBinary(innerDenom); assertSame(Operators.MUL, innerDenom.token()); assertEquals(2, innerDenom.child(0).token()); assertVariable("a", innerDenom.child(1).token()); // Whew! That's the whole tree! } // -- Customizing the parser -- /** An example of custom operators approximating some POSIX shell syntax. */ @Test public void posixShellSyntax() { final Operator substringLeft = new Operator("%", 2, Associativity.LEFT, 10); final Operator substringRight = new Operator("#", 2, Associativity.LEFT, 10); final Operator substringLeftGreedy = new Operator("%%", 2, Associativity.LEFT, 10); final Operator substringRightGreedy = new Operator("##", 2, Associativity.LEFT, 10); final List operators = Arrays.asList(Operators.ASSIGN, Operators.BRACES, substringLeft, substringRight, substringLeftGreedy, substringRightGreedy); final ExpressionParser parser = new ExpressionParser(operators); final LinkedList queue = parser.parsePostfix( "logpath='/var/log/syslog'; dir={logpath%'/*'}; name={logpath##'*/'}"); // logpath "/var/log/syslog" = dir logpath "/*" % {1} = name logpath "*/" ## {1} = assertNotNull(queue); assertEquals(15, queue.size()); assertVariable("logpath", queue.pop()); assertString("/var/log/syslog", queue.pop()); assertSame(Operators.ASSIGN, queue.pop()); assertVariable("dir", queue.pop()); assertVariable("logpath", queue.pop()); assertString("/*", queue.pop()); assertSame(substringLeft, queue.pop()); assertGroup(Operators.BRACES, 1, queue.pop()); assertSame(Operators.ASSIGN, queue.pop()); assertVariable("name", queue.pop()); assertVariable("logpath", queue.pop()); assertString("*/", queue.pop()); assertSame(substringRightGreedy, queue.pop()); assertGroup(Operators.BRACES, 1, queue.pop()); assertSame(Operators.ASSIGN, queue.pop()); } /** An example which parses all literals as {@link String}s. */ @Test public void parseLiteralsAsStrings() { final ExpressionParser parser = new ExpressionParser( // (p, expression) -> new ParseOperation(p, expression) { @Override protected Object parseLiteral() { // No variables! Treat all identifiers as literal strings. final int length = parseIdentifier(); return length == 0 ? null : parseToken(length); } }); final LinkedList queue = parser.parsePostfix( "quick && brown && fox || lazy && dog"); // quick brown && fox && lazy dog && || assertNotNull(queue); assertEquals(Arrays.asList("quick", "brown", Operators.LOGICAL_AND, "fox", Operators.LOGICAL_AND, "lazy", "dog", Operators.LOGICAL_AND, Operators.LOGICAL_OR), queue); } @Test public void allowDotsInVariableNames() { // Create a parser that allows dot characters as part of identifiers. // // NB: In this example: // - The dot character is allowed WITHIN an identifier, but not STARTING it. // - Multiple dot characters in a row are allowed. To allow only single dot // characters in a row, you would need to override the parseIdentifier // method instead to be more intelligent. A regex might come in handy. final ExpressionParser parser = new ExpressionParser( // (p, expression) -> new ParseOperation(p, expression) { @Override protected boolean isIdentifierPart(char c) { return c == '.' || super.isIdentifierPart(c); } }); final LinkedList queue = parser.parsePostfix( "shape.length * shape.width"); // shape.length shape.width * assertNotNull(queue); assertEquals(3, queue.size()); assertVariable("shape.length", queue.pop()); assertVariable("shape.width", queue.pop()); assertSame(Operators.MUL, queue.pop()); } @Test public void forbidLeadingUnderscoreInVariableNames() { // Create a parser that forbids variables from starting with an underscore. final ExpressionParser parser = new ExpressionParser( // (p, expression) -> new ParseOperation(p, expression) { @Override protected boolean isIdentifierStart(char c) { return c != '_' && super.isIdentifierStart(c); } }); Assertions.assertThrows(IllegalArgumentException.class, // () -> parser.parsePostfix("_x / _y")); } @Test public void expressionsEnclosedInBracketsAreVariables() { // Create a parser that treats anything in square brackets as a variable. // Create the list of available operators... but WITHOUT square brackets. // Otherwise, the square bracket operator will influence the parsing. final List operators = Operators.standardList(); operators.remove(Operators.BRACKETS); // Now create the parser, with overridden identifier parsing logic. final ExpressionParser parser = new ExpressionParser(operators, ",", ";", // (p, expression) -> new ParseOperation(p, expression) { @Override protected int parseIdentifier() { // Only accept an identifier in the appropriate context. if (infix) return 0; // Check for special bracketed variable syntax. // A variable can be *anything* enclosed in brackets! if (currentChar() == '[') { int length = 1; while (true) { final char next = futureChar(length++); if (next == '\0') return 0; if (next == ']') return length; } } // Check for the usual identifier syntax. return super.parseIdentifier(); } }); final LinkedList queue = parser.parsePostfix( "2 * [check it out: aa.bb + cc.dd] / ([z-y-x] + x)"); // 2 "[check it out: aa.bb + cc.dd]" * "[z-y-x]" x + (1) / assertNotNull(queue); assertEquals(8, queue.size()); assertEquals(2, queue.pop()); assertVariable("[check it out: aa.bb + cc.dd]", queue.pop()); assertSame(Operators.MUL, queue.pop()); assertVariable("[z-y-x]", queue.pop()); assertVariable("x", queue.pop()); assertSame(Operators.ADD, queue.pop()); assertGroup(Operators.PARENS, 1, queue.pop()); assertSame(Operators.DIV, queue.pop()); } // -- Customizing the evaluator -- @Test public void dotOperatorJoinsVariables() { // Create an evaluator whose dot operator implementation joins variables // into a longer-named variable. final Evaluator e = new DefaultTreeEvaluator() { @Override public Object dot(Object a, Object b) { if (a instanceof Variable && b instanceof Variable) { Variable va = (Variable) a; Variable vb = (Variable) b; return new Variable(va.getToken() + "." + vb.getToken()); } return super.dot(a, b); } }; // Evaluate an expression with dotted variable names. e.set(new Variable("shape.length"), 13); e.set(new Variable("shape.width"), 17); final Object result = e.evaluate("shape.length * shape.width"); final int expected = 13 * 17; assertEquals(expected, result); } @Test public void dollarSignPrefixedVariablesAreSpecial() { // Create an expression parser with an additional $ unary operator. final List operators = new ArrayList<>(); operators.addAll(Operators.standardList()); final Operator dollar = new Operator("$", 1, Associativity.RIGHT, 100); operators.add(dollar); final ExpressionParser parser = new ExpressionParser(operators); // Create an evaluator that replaces $-prefixed variables // with environment variables from the system. final Evaluator e = new DefaultTreeEvaluator(parser) { @Override public Object execute(final Operator op, final SyntaxTree tree) { if (op == dollar) { assert tree.count() == 1; final Variable v = var(evaluate(tree.child(0))); final String value = System.getenv(v.getToken()); return value == null ? "" : value; // Treat undefined as empty string. } return super.execute(op, tree); } }; // Evaluate an expression using the ubiquitous $PATH. final Object result = e.evaluate("'/etc:' + $PATH + ':/var/tmp'"); final String expected = "/etc:" + System.getenv("PATH") + ":/var/tmp"; assertEquals(expected, result); } } parsington-parsington-3.1.0/src/test/java/org/scijava/parsington/eval/000077500000000000000000000000001440242745700261035ustar00rootroot00000000000000AbstractStandardEvaluatorTest.java000066400000000000000000000731151440242745700346450ustar00rootroot00000000000000parsington-parsington-3.1.0/src/test/java/org/scijava/parsington/eval/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington.eval; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.scijava.parsington.AbstractTest; import org.scijava.parsington.Operators; import org.scijava.parsington.SyntaxTree; import org.scijava.parsington.Variable; /** Abstract base class for testing {@link StandardEvaluator} implementations. */ public abstract class AbstractStandardEvaluatorTest extends AbstractTest { protected StandardEvaluator e; @BeforeEach public void setUp() { e = createEvaluator(); } public abstract StandardEvaluator createEvaluator(); /** Tests {@link Evaluator#evaluate(String)}. */ @Test public void testEvaluate() { assertEquals(26, e.evaluate("(2*3)+(4*5)")); } /** Tests strict mode; see {@link Evaluator#setStrict(boolean)}. */ @Test public void testStrict() { assertThrows(IllegalArgumentException.class, () -> e.evaluate("foo=bar")); } /** Tests non-strict mode; see {@link Evaluator#setStrict(boolean)}. */ @Test public void testNonStrict() { e.setStrict(false); e.evaluate("foo=bar"); final Object bar = e.get(new Variable("foo")); assertTrue(bar instanceof Unresolved); assertEquals("bar", ((Unresolved) bar).getToken()); } /** * Tests that {@link Evaluator#evaluate(String)} handles the built-in * {@code postfix} and {@code tree} functions as expected. */ @Test public void testBuiltIns() { final Object[] o = {1, 2, 3, Operators.MUL, Operators.ADD}; final List postfix = Arrays.asList(o); assertEquals(postfix, e.evaluate("postfix('1+2*3')")); final SyntaxTree tree = new SyntaxTree(new LinkedList<>(postfix)); assertEquals(tree, e.evaluate("tree('1+2*3')")); } // -- function -- /** Tests {@link StandardEvaluator#function(Object, Object)}. */ @Test public void testFunction() { // test list access final Variable v = new Variable("v"); e.set(v, Arrays.asList("a", "b", "c")); assertEquals("a", e.function(v, Arrays.asList(0))); assertEquals("b", e.function(v, Arrays.asList(1))); assertEquals("c", e.function(v, Arrays.asList(2))); assertNull(e.function(o(0), o(1))); } // -- dot -- /** Tests {@link StandardEvaluator#dot(Object, Object)}. */ @Test public void testDot() { assertNull(e.dot(o(0), o(1))); } // -- groups -- /** Tests {@link StandardEvaluator#parens(Object[])}. */ @Test public void testParens() { final Object[] o = {1, 2, 3}; assertEquals(Arrays.asList(o), e.parens(o)); // test empty parentheses assertEquals(Collections.emptyList(), e.parens()); // test collapse of single elements assertEquals(4, e.parens(4)); } /** Tests {@link StandardEvaluator#brackets(Object[])}. */ @Test public void testBrackets() { final Object[] o = {1, 2, 3}; assertEquals(Arrays.asList(o), e.brackets(o)); // test empty brackets assertEquals(Collections.emptyList(), e.brackets()); } /** Tests {@link StandardEvaluator#braces(Object[])}. */ @Test public void testBraces() { final Object[] o = {1, 2, 3}; assertEquals(Arrays.asList(o), e.braces(o)); // test empty braces assertEquals(Collections.emptyList(), e.braces()); } // -- transpose, power -- /** Tests {@link StandardEvaluator#transpose(Object)}. */ @Test public void testTranspose() { assertNull(e.transpose(o(0))); } /** Tests {@link StandardEvaluator#dotTranspose(Object)}. */ @Test public void testDotTranspose() { assertNull(e.dotTranspose(o(0))); } /** Tests {@link StandardEvaluator#pow(Object, Object)}. */ @Test public void testPow() { assertNumber(15.625d, e.pow(o(2.5d), o(3d))); assertNumber(bi(15625), e.pow(o(bi(5)), o(6))); assertNumber(bd(15.625), e.pow(o(bd(2.5d)), o(3))); } /** Tests {@link StandardEvaluator#dotPow(Object, Object)}. */ @Test public void testDotPow() { assertNull(e.dotPow(o(0), o(0))); } // -- postfix -- /** Tests {@link StandardEvaluator#postInc(Object)}. */ @Test public void testPostInc() { final Variable v = new Variable("v"); e.set(v, 2); assertEquals(2, e.postInc(v)); assertEquals(3, e.get(v)); e.set(v, 4L); assertEquals(4L, e.postInc(v)); assertEquals(5L, e.get(v)); e.set(v, 6f); assertEquals(6f, e.postInc(v)); assertEquals(7f, e.get(v)); e.set(v, 8d); assertEquals(8d, e.postInc(v)); assertEquals(9d, e.get(v)); e.set(v, bi(10)); assertEquals(bi(10), e.postInc(v)); assertEquals(bi(11), e.get(v)); e.set(v, bd(12)); assertEquals(bd(12), e.postInc(v)); assertEquals(bd(13), e.get(v)); } /** Tests {@link StandardEvaluator#postDec(Object)}. */ @Test public void testPostDec() { final Variable v = new Variable("v"); e.set(v, 2); assertEquals(2, e.postDec(v)); assertEquals(1, e.get(v)); e.set(v, 4L); assertEquals(4L, e.postDec(v)); assertEquals(3L, e.get(v)); e.set(v, 6f); assertEquals(6f, e.postDec(v)); assertEquals(5f, e.get(v)); e.set(v, 8d); assertEquals(8d, e.postDec(v)); assertEquals(7d, e.get(v)); e.set(v, bi(10)); assertEquals(bi(10), e.postDec(v)); assertEquals(bi(9), e.get(v)); e.set(v, bd(12)); assertEquals(bd(12), e.postDec(v)); assertEquals(bd(11), e.get(v)); } // -- unary -- /** Tests {@link StandardEvaluator#preInc(Object)}. */ @Test public void testPreInc() { final Variable v = new Variable("v"); e.set(v, 2); assertEquals(3, e.preInc(v)); assertEquals(3, e.get(v)); e.set(v, 4L); assertEquals(5L, e.preInc(v)); assertEquals(5L, e.get(v)); e.set(v, 6f); assertEquals(7f, e.preInc(v)); assertEquals(7f, e.get(v)); e.set(v, 8d); assertEquals(9d, e.preInc(v)); assertEquals(9d, e.get(v)); e.set(v, bi(10)); assertEquals(bi(11), e.preInc(v)); assertEquals(bi(11), e.get(v)); e.set(v, bd(12)); assertEquals(bd(13), e.preInc(v)); assertEquals(bd(13), e.get(v)); } /** Tests {@link StandardEvaluator#preDec(Object)}. */ @Test public void testPreDec() { final Variable v = new Variable("v"); e.set(v, 2); assertEquals(1, e.preDec(v)); assertEquals(1, e.get(v)); e.set(v, 4L); assertEquals(3L, e.preDec(v)); assertEquals(3L, e.get(v)); e.set(v, 6f); assertEquals(5f, e.preDec(v)); assertEquals(5f, e.get(v)); e.set(v, 8d); assertEquals(7d, e.preDec(v)); assertEquals(7d, e.get(v)); e.set(v, bi(10)); assertEquals(bi(9), e.preDec(v)); assertEquals(bi(9), e.get(v)); e.set(v, bd(12)); assertEquals(bd(11), e.preDec(v)); assertEquals(bd(11), e.get(v)); } /** Tests {@link StandardEvaluator#pos(Object)}. */ @Test public void testPos() { assertNumber(7, e.pos(o(7))); assertNumber(7L, e.pos(o(7L))); assertNumber(7.8d, e.pos(o(7.8d))); assertNumber(7.8f, e.pos(o(7.8f))); assertSame(BigInteger.ZERO, e.pos(BigInteger.ZERO)); assertSame(BigInteger.ONE, e.pos(BigInteger.ONE)); assertSame(BigInteger.TEN, e.pos(BigInteger.TEN)); assertSame(BigDecimal.ZERO, e.pos(BigDecimal.ZERO)); assertSame(BigDecimal.ONE, e.pos(BigDecimal.ONE)); assertSame(BigDecimal.TEN, e.pos(BigDecimal.TEN)); } /** Tests {@link StandardEvaluator#neg(Object)}. */ @Test public void testNeg() { assertNumber(7, e.neg(o(-7))); assertNumber(7L, e.neg(o(-7L))); assertNumber(7.8f, e.neg(o(-7.8f))); assertNumber(7.8d, e.neg(o(-7.8d))); assertNumber(bi(7), e.neg(o(bi(-7)))); assertNumber(bd(7.8), e.neg(o(bd(-7.8)))); assertNumber(-7, e.neg(o(7))); assertNumber(-7L, e.neg(o(7L))); assertNumber(-7.8f, e.neg(o(7.8f))); assertNumber(-7.8d, e.neg(o(7.8d))); assertNumber(bi(-7), e.neg(o(bi(7)))); assertNumber(bd(-7.8), e.neg(o(bd(7.8)))); } /** Tests {@link StandardEvaluator#complement(Object)}. */ @Test public void testComplement() { assertNumber(0x35014541, e.complement(o(0xcafebabe))); assertNumber(0x2152350141104541L, e.complement(o(0xdeadcafebeefbabeL))); } /** Tests {@link StandardEvaluator#not(Object)}. */ @Test public void testNot() { assertSame(false, e.not(o(true))); assertSame(true, e.not(o(false))); } // -- multiplicative -- /** Tests {@link StandardEvaluator#mul(Object, Object)}. */ @Test public void testMul() { assertNumber(24, e.mul(o(4), o(6))); assertNumber(24L, e.mul(o(4L), o(6L))); assertNumber(8.75f, e.mul(o(2.5f), o(3.5f))); assertNumber(8.75d, e.mul(o(2.5d), o(3.5d))); assertNumber(bi(24), e.mul(o(bi(4)), o(bi(6)))); assertNumber(bd(8.75), e.mul(o(bd(2.5)), o(bd(3.5)))); } /** Tests {@link StandardEvaluator#div(Object, Object)}. */ @Test public void testDiv() { assertNumber(4, e.div(o(27), o(6))); assertNumber(4L, e.div(o(27L), o(6L))); assertNumber(2.5f, e.div(o(8.75f), o(3.5f))); assertNumber(2.5d, e.div(o(8.75d), o(3.5d))); assertNumber(bi(4), e.div(o(bi(27)), o(bi(6)))); assertNumber(bd(2.5), e.div(o(bd(8.75)), o(bd(3.5)))); } /** Tests {@link StandardEvaluator#mod(Object, Object)}. */ @Test public void testMod() { assertNumber(3, e.mod(o(27), o(6))); assertNumber(3L, e.mod(o(27L), o(6L))); assertNumber(1.75f, e.mod(o(8.75f), o(3.5f))); assertNumber(1.75d, e.mod(o(8.75d), o(3.5d))); assertNumber(bi(3), e.mod(o(bi(27)), o(bi(6)))); assertNumber(bd(1.75), e.mod(o(bd(8.75)), o(bd(3.5)))); } /** Tests {@link StandardEvaluator#rightDiv(Object, Object)}. */ @Test public void testRightDiv() { assertNull(e.rightDiv(o(0), o(0))); } /** Tests {@link StandardEvaluator#dotDiv(Object, Object)}. */ @Test public void testDotDiv() { assertNull(e.dotDiv(o(0), o(0))); } /** Tests {@link StandardEvaluator#dotRightDiv(Object, Object)}. */ @Test public void testDotRightDiv() { assertNull(e.dotRightDiv(o(0), o(0))); } // -- additive -- /** Tests {@link StandardEvaluator#add(Object, Object)}. */ @Test public void testAdd() { assertEquals("Hello, world", e.add(o("Hello,"), o(" world"))); assertNumber(10, e.add(o(4), o(6))); assertNumber(10L, e.add(o(4L), o(6L))); assertNumber(3.6f, e.add(o(1.5f), o(2.1f))); assertNumber(3.6d, e.add(o(1.5d), o(2.1d))); assertNumber(bi(10), e.add(o(bi(4)), o(bi(6)))); assertNumber(bd(3.6), e.add(o(bd(1.5)), o(bd(2.1)))); } /** Tests {@link StandardEvaluator#sub(Object, Object)}. */ @Test public void testSub() { assertNumber(4, e.sub(o(10), o(6))); assertNumber(4L, e.sub(o(10L), o(6L))); assertNumber(1.5f, e.sub(o(3.6f), o(2.1f))); assertNumber(1.5d, e.sub(o(3.6d), o(2.1d))); assertNumber(bi(4), e.sub(o(bi(10)), o(bi(6)))); assertNumber(bd(1.5), e.sub(o(bd(3.6)), o(bd(2.1)))); } // -- shift -- /** Tests {@link StandardEvaluator#leftShift(Object, Object)}. */ @Test public void testLeftShift() { assertNumber(0xafebabe0, e.leftShift(o(0xcafebabe), o(4))); assertNumber(0xdcafebeefbabe000L, e.leftShift(o(0xdeadcafebeefbabeL), o(12))); assertNumber(bi(7296), e.leftShift(o(bi(57)), o(7))); } /** Tests {@link StandardEvaluator#rightShift(Object, Object)}. */ @Test public void testRightShift() { assertNumber(0xfcafebab, e.rightShift(o(0xcafebabe), o(4))); assertNumber(0xfffdeadcafebeefbL, e.rightShift(o(0xdeadcafebeefbabeL), o(12))); assertNumber(bi(278), e.rightShift(o(bi(8920)), o(5))); } /** Tests {@link StandardEvaluator#unsignedRightShift(Object, Object)}. */ @Test public void testUnsignedRightShift() { assertNumber(0x0cafebab, e.unsignedRightShift(o(0xcafebabe), o(4))); assertNumber(0x000deadcafebeefbL, e.unsignedRightShift(o(0xdeadcafebeefbabeL), o(12))); } // -- relational -- /** Tests {@link StandardEvaluator#lessThan(Object, Object)}. */ @Test public void testLessThan() { assertSame(true, e.lessThan(o(false), o(true))); assertSame(false, e.lessThan(o(false), o(false))); assertSame(false, e.lessThan(o(true), o(false))); assertSame(true, e.lessThan(o("hello"), o("world"))); assertSame(false, e.lessThan(o("hello"), o("hello"))); assertSame(false, e.lessThan(o("young"), o("world"))); assertSame(true, e.lessThan(o(2), o(3))); assertSame(false, e.lessThan(o(2), o(2))); assertSame(false, e.lessThan(o(2), o(1))); assertSame(true, e.lessThan(o(5L), o(6L))); assertSame(false, e.lessThan(o(5L), o(5L))); assertSame(false, e.lessThan(o(5L), o(4L))); assertSame(true, e.lessThan(o(8f), o(9f))); assertSame(false, e.lessThan(o(8f), o(8f))); assertSame(false, e.lessThan(o(8f), o(7f))); assertSame(true, e.lessThan(o(11d), o(12d))); assertSame(false, e.lessThan(o(11d), o(11d))); assertSame(false, e.lessThan(o(11d), o(10d))); assertSame(true, e.lessThan(o(bi(14)), o(bi(15)))); assertSame(false, e.lessThan(o(bi(14)), o(bi(14)))); assertSame(false, e.lessThan(o(bi(14)), o(bi(13)))); assertSame(true, e.lessThan(o(bd(17)), o(bd(18)))); assertSame(false, e.lessThan(o(bd(17)), o(bd(17)))); assertSame(false, e.lessThan(o(bd(17)), o(bd(16)))); } /** Tests {@link StandardEvaluator#greaterThan(Object, Object)}. */ @Test public void testGreaterThan() { assertSame(false, e.greaterThan(o(false), o(true))); assertSame(false, e.greaterThan(o(false), o(false))); assertSame(true, e.greaterThan(o(true), o(false))); assertSame(false, e.greaterThan(o("hello"), o("world"))); assertSame(false, e.greaterThan(o("hello"), o("hello"))); assertSame(true, e.greaterThan(o("young"), o("world"))); assertSame(false, e.greaterThan(o(2), o(3))); assertSame(false, e.greaterThan(o(2), o(2))); assertSame(true, e.greaterThan(o(2), o(1))); assertSame(false, e.greaterThan(o(5L), o(6L))); assertSame(false, e.greaterThan(o(5L), o(5L))); assertSame(true, e.greaterThan(o(5L), o(4L))); assertSame(false, e.greaterThan(o(8f), o(9f))); assertSame(false, e.greaterThan(o(8f), o(8f))); assertSame(true, e.greaterThan(o(8f), o(7f))); assertSame(false, e.greaterThan(o(11d), o(12d))); assertSame(false, e.greaterThan(o(11d), o(11d))); assertSame(true, e.greaterThan(o(11d), o(10d))); assertSame(false, e.greaterThan(o(bi(14)), o(bi(15)))); assertSame(false, e.greaterThan(o(bi(14)), o(bi(14)))); assertSame(true, e.greaterThan(o(bi(14)), o(bi(13)))); assertSame(false, e.greaterThan(o(bd(17)), o(bd(18)))); assertSame(false, e.greaterThan(o(bd(17)), o(bd(17)))); assertSame(true, e.greaterThan(o(bd(17)), o(bd(16)))); } /** Tests {@link StandardEvaluator#lessThanOrEqual(Object, Object)}. */ @Test public void testLessThanOrEqual() { assertSame(true, e.lessThanOrEqual(o(false), o(true))); assertSame(true, e.lessThanOrEqual(o(false), o(false))); assertSame(false, e.lessThanOrEqual(o(true), o(false))); assertSame(true, e.lessThanOrEqual(o("hello"), o("world"))); assertSame(true, e.lessThanOrEqual(o("hello"), o("hello"))); assertSame(false, e.lessThanOrEqual(o("young"), o("world"))); assertSame(true, e.lessThanOrEqual(o(2), o(3))); assertSame(true, e.lessThanOrEqual(o(2), o(2))); assertSame(false, e.lessThanOrEqual(o(2), o(1))); assertSame(true, e.lessThanOrEqual(o(5L), o(6L))); assertSame(true, e.lessThanOrEqual(o(5L), o(5L))); assertSame(false, e.lessThanOrEqual(o(5L), o(4L))); assertSame(true, e.lessThanOrEqual(o(8f), o(9f))); assertSame(true, e.lessThanOrEqual(o(8f), o(8f))); assertSame(false, e.lessThanOrEqual(o(8f), o(7f))); assertSame(true, e.lessThanOrEqual(o(11d), o(12d))); assertSame(true, e.lessThanOrEqual(o(11d), o(11d))); assertSame(false, e.lessThanOrEqual(o(11d), o(10d))); assertSame(true, e.lessThanOrEqual(o(bi(14)), o(bi(15)))); assertSame(true, e.lessThanOrEqual(o(bi(14)), o(bi(14)))); assertSame(false, e.lessThanOrEqual(o(bi(14)), o(bi(13)))); assertSame(true, e.lessThanOrEqual(o(bd(17)), o(bd(18)))); assertSame(true, e.lessThanOrEqual(o(bd(17)), o(bd(17)))); assertSame(false, e.lessThanOrEqual(o(bd(17)), o(bd(16)))); } /** Tests {@link StandardEvaluator#greaterThanOrEqual(Object, Object)}. */ @Test public void testGreaterThanOrEqual() { assertSame(false, e.greaterThanOrEqual(o(false), o(true))); assertSame(true, e.greaterThanOrEqual(o(false), o(false))); assertSame(true, e.greaterThanOrEqual(o(true), o(false))); assertSame(false, e.greaterThanOrEqual(o("hello"), o("world"))); assertSame(true, e.greaterThanOrEqual(o("hello"), o("hello"))); assertSame(true, e.greaterThanOrEqual(o("young"), o("world"))); assertSame(false, e.greaterThanOrEqual(o(2), o(3))); assertSame(true, e.greaterThanOrEqual(o(2), o(2))); assertSame(true, e.greaterThanOrEqual(o(2), o(1))); assertSame(false, e.greaterThanOrEqual(o(5L), o(6L))); assertSame(true, e.greaterThanOrEqual(o(5L), o(5L))); assertSame(true, e.greaterThanOrEqual(o(5L), o(4L))); assertSame(false, e.greaterThanOrEqual(o(8f), o(9f))); assertSame(true, e.greaterThanOrEqual(o(8f), o(8f))); assertSame(true, e.greaterThanOrEqual(o(8f), o(7f))); assertSame(false, e.greaterThanOrEqual(o(11d), o(12d))); assertSame(true, e.greaterThanOrEqual(o(11d), o(11d))); assertSame(true, e.greaterThanOrEqual(o(11d), o(10d))); assertSame(false, e.greaterThanOrEqual(o(bi(14)), o(bi(15)))); assertSame(true, e.greaterThanOrEqual(o(bi(14)), o(bi(14)))); assertSame(true, e.greaterThanOrEqual(o(bi(14)), o(bi(13)))); assertSame(false, e.greaterThanOrEqual(o(bd(17)), o(bd(18)))); assertSame(true, e.greaterThanOrEqual(o(bd(17)), o(bd(17)))); assertSame(true, e.greaterThanOrEqual(o(bd(17)), o(bd(16)))); } /** Tests {@link StandardEvaluator#instanceOf(Object, Object)}. */ @Test public void testInstanceOf() { assertNull(e.instanceOf(o(0), o(0))); } // -- equality -- /** Tests {@link StandardEvaluator#equal(Object, Object)}. */ @Test public void testEqual() { assertSame(false, e.equal(o(false), o(true))); assertSame(true, e.equal(o(false), o(false))); assertSame(false, e.equal(o(true), o(false))); assertSame(false, e.equal(o("hello"), o("world"))); assertSame(true, e.equal(o("hello"), o("hello"))); assertSame(false, e.equal(o("young"), o("world"))); assertSame(false, e.equal(o(2), o(3))); assertSame(true, e.equal(o(2), o(2))); assertSame(false, e.equal(o(2), o(1))); assertSame(false, e.equal(o(5L), o(6L))); assertSame(true, e.equal(o(5L), o(5L))); assertSame(false, e.equal(o(5L), o(4L))); assertSame(false, e.equal(o(8f), o(9f))); assertSame(true, e.equal(o(8f), o(8f))); assertSame(false, e.equal(o(8f), o(7f))); assertSame(false, e.equal(o(11d), o(12d))); assertSame(true, e.equal(o(11d), o(11d))); assertSame(false, e.equal(o(11d), o(10d))); assertSame(false, e.equal(o(bi(14)), o(bi(15)))); assertSame(true, e.equal(o(bi(14)), o(bi(14)))); assertSame(false, e.equal(o(bi(14)), o(bi(13)))); assertSame(false, e.equal(o(bd(17)), o(bd(18)))); assertSame(true, e.equal(o(bd(17)), o(bd(17)))); assertSame(false, e.equal(o(bd(17)), o(bd(16)))); } /** Tests {@link StandardEvaluator#notEqual(Object, Object)}. */ @Test public void testNotEqual() { assertSame(true, e.notEqual(o(false), o(true))); assertSame(false, e.notEqual(o(false), o(false))); assertSame(true, e.notEqual(o(true), o(false))); assertSame(true, e.notEqual(o("hello"), o("world"))); assertSame(false, e.notEqual(o("hello"), o("hello"))); assertSame(true, e.notEqual(o("young"), o("world"))); assertSame(true, e.notEqual(o(2), o(3))); assertSame(false, e.notEqual(o(2), o(2))); assertSame(true, e.notEqual(o(2), o(1))); assertSame(true, e.notEqual(o(5L), o(6L))); assertSame(false, e.notEqual(o(5L), o(5L))); assertSame(true, e.notEqual(o(5L), o(4L))); assertSame(true, e.notEqual(o(8f), o(9f))); assertSame(false, e.notEqual(o(8f), o(8f))); assertSame(true, e.notEqual(o(8f), o(7f))); assertSame(true, e.notEqual(o(11d), o(12d))); assertSame(false, e.notEqual(o(11d), o(11d))); assertSame(true, e.notEqual(o(11d), o(10d))); assertSame(true, e.notEqual(o(bi(14)), o(bi(15)))); assertSame(false, e.notEqual(o(bi(14)), o(bi(14)))); assertSame(true, e.notEqual(o(bi(14)), o(bi(13)))); assertSame(true, e.notEqual(o(bd(17)), o(bd(18)))); assertSame(false, e.notEqual(o(bd(17)), o(bd(17)))); assertSame(true, e.notEqual(o(bd(17)), o(bd(16)))); } // -- bitwise -- /** Tests {@link StandardEvaluator#bitwiseAnd(Object, Object)}. */ @Test public void testBitwiseAnd() { assertNumber(0xcaacbaae, e.bitwiseAnd(o(0xcafebabe), o(0xdeadbeef))); assertNumber(0L, e.bitwiseAnd(o(0x0d0e0a0d0c0a0f0eL), o(0xb0e0e0f0b0a0b0e0L))); assertNumber(bi(0xcaacbaae), e.bitwiseAnd(o(bi(0xcafebabe)), o(bi(0xdeadbeef)))); } /** Tests {@link StandardEvaluator#bitwiseOr(Object, Object)}. */ @Test public void testBitwiseOr() { assertNumber(0xdeffbeff, e.bitwiseOr(o(0xcafebabe), o(0xdeadbeef))); assertNumber(0xbdeeeafdbcaabfeeL, e.bitwiseOr(o(0x0d0e0a0d0c0a0f0eL), o(0xb0e0e0f0b0a0b0e0L))); assertNumber(bi(0xdeffbeff), e.bitwiseOr(o(bi(0xcafebabe)), o(bi(0xdeadbeef)))); } // -- logical -- /** Tests {@link StandardEvaluator#logicalAnd(Object, Object)}. */ @Test public void testLogicalAnd() { assertSame(false, e.logicalAnd(o(false), o(false))); assertSame(false, e.logicalAnd(o(false), o(true))); assertSame(false, e.logicalAnd(o(true), o(false))); assertSame(true, e.logicalAnd(o(true), o(true))); } /** Tests {@link StandardEvaluator#logicalOr(Object, Object)}. */ @Test public void testLogicalOr() { assertSame(false, e.logicalOr(o(false), o(false))); assertSame(true, e.logicalOr(o(false), o(true))); assertSame(true, e.logicalOr(o(true), o(false))); assertSame(true, e.logicalOr(o(true), o(true))); } // -- ternary -- /** Tests {@link StandardEvaluator#question(Object, Object)}. */ @Test public void testQuestion() { assertNull(e.question(o(0), o(0))); } /** Tests {@link StandardEvaluator#colon(Object, Object)}. */ @Test public void testColon() { assertNull(e.colon(o(0), o(0))); } // -- assignment -- /** Tests {@link StandardEvaluator#assign(Object, Object)}. */ @Test public void testAssign() { final Variable v = new Variable("v"); assertAssigned(true, v, e.assign(v, true)); assertAssigned("hello", v, e.assign(v, "hello")); assertAssigned(1, v, e.assign(v, 1)); assertAssigned(2L, v, e.assign(v, 2L)); assertAssigned(3f, v, e.assign(v, 3f)); assertAssigned(4d, v, e.assign(v, 4d)); assertAssigned(bi(5), v, e.assign(v, bi(5))); assertAssigned(bd(6), v, e.assign(v, bd(6))); } /** Tests {@link StandardEvaluator#powAssign(Object, Object)}. */ @Test public void testPowAssign() { final Variable v = new Variable("v"); e.set(v, 2.5d); assertAssigned(15.625d, v, e.powAssign(v, 3)); e.set(v, bi(5)); assertAssigned(bi(15625), v, e.powAssign(v, 6)); e.set(v, bd(2.5)); assertAssigned(bd(15.625), v, e.powAssign(v, 3)); } /** Tests {@link StandardEvaluator#dotPowAssign(Object, Object)}. */ @Test public void testDotPowAssign() { // NB: Nothing to test; dotPow is unimplemented. } /** Tests {@link StandardEvaluator#mulAssign(Object, Object)}. */ @Test public void testMulAssign() { final Variable v = new Variable("v"); e.set(v, 4); assertAssigned(24, v, e.mulAssign(v, 6)); e.set(v, 4L); assertAssigned(24L, v, e.mulAssign(v, 6L)); e.set(v, 2.5f); assertAssigned(8.75f, v, e.mulAssign(v, 3.5f)); e.set(v, 2.5d); assertAssigned(8.75d, v, e.mulAssign(v, 3.5d)); e.set(v, bi(4)); assertAssigned(bi(24), v, e.mulAssign(v, bi(6))); e.set(v, bd(2.5)); assertAssigned(bd(8.75), v, e.mulAssign(v, bd(3.5))); } /** Tests {@link StandardEvaluator#divAssign(Object, Object)}. */ @Test public void testDivAssign() { final Variable v = new Variable("v"); e.set(v, 27); assertAssigned(4, v, e.divAssign(v, 6)); e.set(v, 27L); assertAssigned(4L, v, e.divAssign(v, 6L)); e.set(v, 8.75f); assertAssigned(2.5f, v, e.divAssign(v, 3.5f)); e.set(v, 8.75d); assertAssigned(2.5d, v, e.divAssign(v, 3.5d)); e.set(v, bi(27)); assertAssigned(bi(4), v, e.divAssign(v, bi(6))); e.set(v, bd(8.75)); assertAssigned(bd(2.5), v, e.divAssign(v, bd(3.5))); } /** Tests {@link StandardEvaluator#modAssign(Object, Object)}. */ @Test public void testModAssign() { final Variable v = new Variable("v"); e.set(v, 27); assertAssigned(3, v, e.modAssign(v, 6)); e.set(v, 27L); assertAssigned(3L, v, e.modAssign(v, 6L)); e.set(v, 8.75f); assertAssigned(1.75f, v, e.modAssign(v, 3.5f)); e.set(v, 8.75d); assertAssigned(1.75d, v, e.modAssign(v, 3.5d)); e.set(v, bi(27)); assertAssigned(bi(3), v, e.modAssign(v, bi(6))); e.set(v, bd(8.75)); assertAssigned(bd(1.75), v, e.modAssign(v, bd(3.5))); } /** Tests {@link StandardEvaluator#rightDivAssign(Object, Object)}. */ @Test public void testRightDivAssign() { // NB: Nothing to test; rightDiv is unimplemented. } /** Tests {@link StandardEvaluator#dotDivAssign(Object, Object)}. */ @Test public void testDotDivAssign() { // NB: Nothing to test; dotDiv is unimplemented. } /** Tests {@link StandardEvaluator#dotRightDivAssign(Object, Object)}. */ @Test public void testDotRightDivAssign() { // NB: Nothing to test; dotRightDiv is unimplemented. } /** Tests {@link StandardEvaluator#addAssign(Object, Object)}. */ @Test public void testAddAssign() { final Variable v = new Variable("v"); e.set(v, "Hello,"); assertAssigned("Hello, world", v, e.addAssign(v, " world")); e.set(v, 4); assertAssigned(10, v, e.addAssign(v, 6)); e.set(v, 4L); assertAssigned(10L, v, e.addAssign(v, 6L)); e.set(v, 1.5f); assertAssigned(3.6f, v, e.addAssign(v, 2.1f)); e.set(v, 1.5d); assertAssigned(3.6d, v, e.addAssign(v, 2.1d)); e.set(v, bi(4)); assertAssigned(bi(10), v, e.addAssign(v, bi(6))); e.set(v, bd(1.5)); assertAssigned(bd(3.6), v, e.addAssign(v, bd(2.1))); } /** Tests {@link StandardEvaluator#subAssign(Object, Object)}. */ @Test public void testSubAssign() { final Variable v = new Variable("v"); e.set(v, 10); assertAssigned(4, v, e.subAssign(v, 6)); e.set(v, 10L); assertAssigned(4L, v, e.subAssign(v, 6L)); e.set(v, 3.6f); assertAssigned(1.5f, v, e.subAssign(v, 2.1f)); e.set(v, 3.6d); assertAssigned(1.5d, v, e.subAssign(v, 2.1d)); e.set(v, bi(10)); assertAssigned(bi(4), v, e.subAssign(v, bi(6))); e.set(v, bd(3.6)); assertAssigned(bd(1.5), v, e.subAssign(v, bd(2.1))); } /** Tests {@link StandardEvaluator#andAssign(Object, Object)}. */ @Test public void testAndAssign() { final Variable v = new Variable("v"); e.set(v, 0xcafebabe); assertAssigned(0xcaacbaae, v, e.andAssign(v, 0xdeadbeef)); e.set(v, 0x0d0e0a0d0c0a0f0eL); assertAssigned(0L, v, e.andAssign(v, 0xb0e0e0f0b0a0b0e0L)); e.set(v, bi(0xcafebabeL)); assertAssigned(bi(0xcaacbaaeL), v, e.andAssign(v, bi(0xdeadbeefL))); } /** Tests {@link StandardEvaluator#orAssign(Object, Object)}. */ @Test public void testOrAssign() { final Variable v = new Variable("v"); e.set(v, 0xcafebabe); assertAssigned(0xdeffbeff, v, e.orAssign(v, 0xdeadbeef)); e.set(v, 0x0d0e0a0d0c0a0f0eL); assertAssigned(0xbdeeeafdbcaabfeeL, v, e.orAssign(v, 0xb0e0e0f0b0a0b0e0L)); e.set(v, bi(0xcafebabeL)); assertAssigned(bi(0xdeffbeffL), v, e.orAssign(v, bi(0xdeadbeefL))); } /** Tests {@link StandardEvaluator#leftShiftAssign(Object, Object)}. */ @Test public void testLeftShiftAssign() { final Variable v = new Variable("v"); e.set(v, 0xcafebabe); assertAssigned(0xafebabe0, v, e.leftShiftAssign(v, 4)); e.set(v, 0xdeadcafebeefbabeL); assertAssigned(0xdcafebeefbabe000L, v, e.leftShiftAssign(v, 12)); e.set(v, bi(57)); assertAssigned(bi(7296), v, e.leftShiftAssign(v, 7)); } /** Tests {@link StandardEvaluator#rightShiftAssign(Object, Object)}. */ @Test public void testRightShiftAssign() { final Variable v = new Variable("v"); e.set(v, 0xcafebabe); assertAssigned(0xfcafebab, v, e.rightShiftAssign(v, 4)); e.set(v, 0xdeadcafebeefbabeL); assertAssigned(0xfffdeadcafebeefbL, v, e.rightShiftAssign(v, 12)); e.set(v, bi(8920)); assertAssigned(bi(278), v, e.rightShiftAssign(v, 5)); } /** Tests {@link StandardEvaluator#unsignedRightShiftAssign(Object, Object)}. */ @Test public void testUnsignedRightShiftAssign() { final Variable v = new Variable("v"); e.set(v, 0xcafebabe); assertAssigned(0x0cafebab, v, e.unsignedRightShiftAssign(v, 4)); e.set(v, 0xdeadcafebeefbabeL); assertAssigned(0x000deadcafebeefbL, v, e.unsignedRightShiftAssign(v, 12)); } // -- Helper methods -- /** Widens the given object to an {@link Object}. */ private Object o(final Object o) { return o; } private BigInteger bi(final Object o) { return new BigInteger(o.toString()); } private BigDecimal bd(final Object o) { return new BigDecimal(o.toString()); } private void assertAssigned(final Object expected, final Variable v, final Object result) { assertSame(v, result); assertEquals(expected, e.get(v)); } } parsington-parsington-3.1.0/src/test/java/org/scijava/parsington/eval/DefaultStackEvaluatorTest.java000066400000000000000000000060361440242745700340500ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington.eval; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import java.util.List; import org.junit.jupiter.api.Test; /** Tests {@link DefaultStackEvaluator}. */ public class DefaultStackEvaluatorTest extends AbstractStandardEvaluatorTest { @Override public StandardEvaluator createEvaluator() { return new DefaultStackEvaluator(); } @Test public void testNonShortCircuitingAnd() { final Object result = e.evaluate("(x = 1, (++x > 2) && (++x > 3))"); // Both sides of the above AND expression evaluate to false. // But for short circuiting AND, only the left side would // execute, leaving the value of x afterward at 2, not 3. final Object v = ((List) result).get(0); // First element of tuple. assertEquals(3, e.value(v)); } @Test public void testNonShortCircuitingOr() { final Object result = e.evaluate("(x = 1, (++x == 2) || (++x == 3))"); // Both sides of the above OR expression evaluate to true. // But for short circuiting OR, only the left side would // execute, leaving the value of x afterward at 2, not 3. final Object v = ((List) result).get(0); // First element of tuple. assertEquals(3, e.value(v)); } @Test public void testUnimplementedTernary() { try { e.evaluate("2 < 3 ? 'yes' : 'no'"); fail("Evaluation of ternary expression erroneously succeeded"); } catch (final IllegalArgumentException exc) { assertTrue(exc.getMessage().equals("Unsupported binary operator: :")); } } } parsington-parsington-3.1.0/src/test/java/org/scijava/parsington/eval/DefaultTreeEvaluatorTest.java000066400000000000000000000054471440242745700337070ustar00rootroot00000000000000/* * #%L * Parsington: the SciJava mathematical expression parser. * %% * Copyright (C) 2015 - 2023 Board of Regents of the University of * Wisconsin-Madison. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package org.scijava.parsington.eval; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.List; import org.junit.jupiter.api.Test; /** Tests {@link DefaultTreeEvaluator}. */ public class DefaultTreeEvaluatorTest extends AbstractStandardEvaluatorTest { @Override public StandardEvaluator createEvaluator() { return new DefaultTreeEvaluator(); } @Test public void testShortCircuitingAnd() { final Object result = e.evaluate("(x = 1, (++x > 2) && (++x > 3))"); // Both sides of the above AND expression evaluate to false. // But for short circuiting AND, only the left side should // execute, leaving the value of x afterward at 2, not 3. final Object v = ((List) result).get(0); // First element of tuple. assertEquals(2, e.value(v)); } @Test public void testShortCircuitingOr() { final Object result = e.evaluate("(x = 1, (++x == 2) || (++x == 3))"); // Both sides of the above OR expression evaluate to true. // But for short circuiting OR, only the left side should // execute, leaving the value of x afterward at 2, not 3. final Object v = ((List) result).get(0); // First element of tuple. assertEquals(2, e.value(v)); } @Test public void testShortCircuitingTernary() { final Object result = e.evaluate("2 < 3 ? (x = 'yes') : (x += 'no')"); assertEquals("yes", e.value(result)); } }