Add hacks for scala classlib. Add DOM method for convenience. Improve

Scala example
This commit is contained in:
Alexey Andreev 2015-10-04 14:29:34 +03:00
parent 219fab22ef
commit a4e41fc6be
10 changed files with 627 additions and 111 deletions

View File

@ -50,5 +50,6 @@ public class JCLPlugin implements TeaVMPlugin {
host.add(new MethodReference(LambdaMetafactory.class, "metafactory", MethodHandles.Lookup.class, host.add(new MethodReference(LambdaMetafactory.class, "metafactory", MethodHandles.Lookup.class,
String.class, MethodType.class, MethodType.class, MethodHandle.class, MethodType.class, String.class, MethodType.class, MethodType.class, MethodHandle.class, MethodType.class,
CallSite.class), new LambdaMetafactorySubstitutor()); CallSite.class), new LambdaMetafactorySubstitutor());
host.add(new ScalaHacks());
} }
} }

View File

@ -0,0 +1,81 @@
package org.teavm.classlib.impl;
import java.util.List;
import java.util.Properties;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.BasicBlock;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.FieldHolder;
import org.teavm.model.Instruction;
import org.teavm.model.MethodHolder;
import org.teavm.model.Program;
import org.teavm.model.emit.ProgramEmitter;
import org.teavm.model.instructions.ConstructInstruction;
import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.PutFieldInstruction;
/**
*
* @author Alexey Andreev
*/
public class ScalaHacks implements ClassHolderTransformer {
private static final String ATTR_NAME_CLASS = "java.util.jar.Attributes$Name";
@Override
public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) {
switch (cls.getName()) {
case "scala.util.PropertiesTrait$class":
transformPropertiesTrait(cls, innerSource);
break;
case "scala.util.Properties$":
transformProperties(cls);
break;
}
}
private void transformPropertiesTrait(ClassHolder cls, ClassReaderSource innerSource) {
for (MethodHolder method : cls.getMethods()) {
if (method.getName().equals("scalaProps")) {
ProgramEmitter pe = ProgramEmitter.create(method, innerSource);
pe.construct(Properties.class).returnValue();
}
}
}
private void transformProperties(ClassHolder cls) {
for (FieldHolder field : cls.getFields().toArray(new FieldHolder[0])) {
if (field.getName().equals("ScalaCompilerVersion")) {
cls.removeField(field);
}
}
for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
if (method.getName().equals("ScalaCompilerVersion")) {
cls.removeMethod(method);
} else if (method.getName().equals("<init>")) {
Program program = method.getProgram();
for (int i = 0; i < program.basicBlockCount(); ++i) {
BasicBlock block = program.basicBlockAt(i);
List<Instruction> instructions = block.getInstructions();
for (int j = 0; j < instructions.size(); ++j) {
Instruction insn = instructions.get(j);
if (insn instanceof InvokeInstruction) {
if (((InvokeInstruction) insn).getMethod().getClassName().equals(ATTR_NAME_CLASS)) {
instructions.remove(j--);
}
} else if (insn instanceof PutFieldInstruction) {
if (((PutFieldInstruction) insn).getField().getFieldName().equals("ScalaCompilerVersion")) {
instructions.remove(j--);
}
} else if (insn instanceof ConstructInstruction) {
ConstructInstruction cons = (ConstructInstruction) insn;
if (cons.getType().equals(ATTR_NAME_CLASS)) {
cons.setType("java.lang.Object");
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,383 @@
/*
* Copyright 2015 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util;
import org.teavm.classlib.java.io.TBufferedInputStream;
import org.teavm.classlib.java.io.TIOException;
import org.teavm.classlib.java.io.TInputStream;
import org.teavm.classlib.java.io.TOutputStream;
import org.teavm.classlib.java.io.TOutputStreamWriter;
import org.teavm.classlib.java.io.TPrintStream;
import org.teavm.classlib.java.io.TWriter;
import org.teavm.classlib.java.lang.TString;
/**
*
* @author Alexey Andreev
*/
public class TProperties extends THashtable<Object, Object> {
/**
* The default values for keys not found in this {@code Properties}
* instance.
*/
protected TProperties defaults;
private static final int NONE = 0, SLASH = 1, UNICODE = 2, CONTINUE = 3, KEY_DONE = 4, IGNORE = 5;
public TProperties() {
super();
}
public TProperties(TProperties properties) {
defaults = properties;
}
private void dumpString(StringBuilder buffer, String string, boolean isKey) {
int index = 0, length = string.length();
if (!isKey && index < length && string.charAt(index) == ' ') {
buffer.append("\\ "); //$NON-NLS-1$
index++;
}
for (; index < length; index++) {
char ch = string.charAt(index);
switch (ch) {
case '\t':
buffer.append("\\t");
break;
case '\n':
buffer.append("\\n");
break;
case '\f':
buffer.append("\\f");
break;
case '\r':
buffer.append("\\r");
break;
default:
if ("\\#!=:".indexOf(ch) >= 0 || (isKey && ch == ' ')) {
buffer.append('\\');
}
if (ch >= ' ' && ch <= '~') {
buffer.append(ch);
} else {
buffer.append(toHexaDecimal(ch));
}
}
}
}
private char[] toHexaDecimal(final int ch) {
char[] hexChars = { '\\', 'u', '0', '0', '0', '0' };
int hexChar, index = hexChars.length, copyOfCh = ch;
do {
hexChar = copyOfCh & 15;
if (hexChar > 9) {
hexChar = hexChar - 10 + 'A';
} else {
hexChar += '0';
}
hexChars[--index] = (char) hexChar;
} while ((copyOfCh >>>= 4) != 0);
return hexChars;
}
public String getProperty(String name) {
Object result = super.get(name);
String property = result instanceof String ? (String) result : null;
if (property == null && defaults != null) {
property = defaults.getProperty(name);
}
return property;
}
public String getProperty(String name, String defaultValue) {
Object result = super.get(name);
String property = result instanceof String ? (String) result : null;
if (property == null && defaults != null) {
property = defaults.getProperty(name);
}
if (property == null) {
return defaultValue;
}
return property;
}
public void list(TPrintStream out) {
if (out == null) {
throw new NullPointerException();
}
StringBuilder buffer = new StringBuilder(80);
TEnumeration<?> keys = propertyNames();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
buffer.append(key);
buffer.append('=');
String property = (String) super.get(key);
TProperties def = defaults;
while (property == null) {
property = (String) def.get(key);
def = def.defaults;
}
if (property.length() > 40) {
buffer.append(property.substring(0, 37));
buffer.append("..."); //$NON-NLS-1$
} else {
buffer.append(property);
}
out.println(TString.wrap(buffer.toString()));
buffer.setLength(0);
}
}
@SuppressWarnings("fallthrough")
public synchronized void load(TInputStream in) throws TIOException {
if (in == null) {
throw new NullPointerException();
}
int mode = NONE, unicode = 0, count = 0;
char nextChar, buf[] = new char[40];
int offset = 0, keyLength = -1, intVal;
boolean firstChar = true;
TBufferedInputStream bis = new TBufferedInputStream(in);
while (true) {
intVal = bis.read();
if (intVal == -1) {
// if mode is UNICODE but has less than 4 hex digits, should
// throw an IllegalArgumentException
if (mode == UNICODE && count < 4) {
throw new IllegalArgumentException("Invalid Unicode sequence: expected format \\uxxxx");
}
// if mode is SLASH and no data is read, should append '\u0000'
// to buf
if (mode == SLASH) {
buf[offset++] = '\u0000';
}
break;
}
nextChar = (char) (intVal & 0xff);
if (offset == buf.length) {
char[] newBuf = new char[buf.length * 2];
System.arraycopy(buf, 0, newBuf, 0, offset);
buf = newBuf;
}
if (mode == UNICODE) {
int digit = Character.digit(nextChar, 16);
if (digit >= 0) {
unicode = (unicode << 4) + digit;
if (++count < 4) {
continue;
}
} else if (count <= 4) {
throw new IllegalArgumentException("Invalid Unicode sequence: illegal character");
}
mode = NONE;
buf[offset++] = (char) unicode;
if (nextChar != '\n') {
continue;
}
}
if (mode == SLASH) {
mode = NONE;
switch (nextChar) {
case '\r':
mode = CONTINUE; // Look for a following \n
continue;
case '\n':
mode = IGNORE; // Ignore whitespace on the next line
continue;
case 'b':
nextChar = '\b';
break;
case 'f':
nextChar = '\f';
break;
case 'n':
nextChar = '\n';
break;
case 'r':
nextChar = '\r';
break;
case 't':
nextChar = '\t';
break;
case 'u':
mode = UNICODE;
unicode = count = 0;
continue;
}
} else {
switch (nextChar) {
case '#':
case '!':
if (firstChar) {
while (true) {
intVal = bis.read();
if (intVal == -1) {
break;
}
// & 0xff not required
nextChar = (char) intVal;
if (nextChar == '\r' || nextChar == '\n') {
break;
}
}
continue;
}
break;
case '\n':
if (mode == CONTINUE) { // Part of a \r\n sequence
mode = IGNORE; // Ignore whitespace on the next line
continue;
}
// fall into the next case
case '\r':
mode = NONE;
firstChar = true;
if (offset > 0 || (offset == 0 && keyLength == 0)) {
if (keyLength == -1) {
keyLength = offset;
}
String temp = new String(buf, 0, offset);
put(temp.substring(0, keyLength), temp
.substring(keyLength));
}
keyLength = -1;
offset = 0;
continue;
case '\\':
if (mode == KEY_DONE) {
keyLength = offset;
}
mode = SLASH;
continue;
case ':':
case '=':
if (keyLength == -1) { // if parsing the key
mode = NONE;
keyLength = offset;
continue;
}
break;
}
if (nextChar < 256 && Character.isWhitespace(nextChar)) {
if (mode == CONTINUE) {
mode = IGNORE;
}
// if key length == 0 or value length == 0
if (offset == 0 || offset == keyLength || mode == IGNORE) {
continue;
}
if (keyLength == -1) { // if parsing the key
mode = KEY_DONE;
continue;
}
}
if (mode == IGNORE || mode == CONTINUE) {
mode = NONE;
}
}
firstChar = false;
if (mode == KEY_DONE) {
keyLength = offset;
mode = NONE;
}
buf[offset++] = nextChar;
}
if (keyLength == -1 && offset > 0) {
keyLength = offset;
}
if (keyLength >= 0) {
String temp = new String(buf, 0, offset);
put(temp.substring(0, keyLength), temp.substring(keyLength));
}
}
public TEnumeration<?> propertyNames() {
THashtable<Object, Object> selected = new THashtable<>();
selectProperties(selected);
return selected.keys();
}
private void selectProperties(THashtable<Object, Object> selected) {
if(defaults != null) {
defaults.selectProperties(selected);
}
selected.putAll(this);
}
@Deprecated
public void save(TOutputStream out, String comment) {
try {
store(out, comment);
} catch (TIOException e) {
}
}
public Object setProperty(String name, String value) {
return put(name, value);
}
public synchronized void store(TOutputStream out, String comments) throws TIOException {
TOutputStreamWriter writer = new TOutputStreamWriter(out, "ISO8859_1");
if (comments != null) {
writeComments(writer, comments);
}
writer.write('#');
writer.write(new TDate().toString());
writer.write("\n");
StringBuilder buffer = new StringBuilder(200);
for (TIterator<TMap.Entry<Object, Object>> iter = entrySet().iterator(); iter.hasNext();) {
TMap.Entry<Object, Object> entry = iter.next();
String key = (String) entry.getKey();
dumpString(buffer, key, true);
buffer.append('=');
dumpString(buffer, (String) entry.getValue(), false);
buffer.append("\n");
writer.write(buffer.toString());
buffer.setLength(0);
}
writer.flush();
}
private void writeComments(TWriter writer, String comments) throws TIOException {
writer.write('#');
char[] chars = comments.toCharArray();
for (int index = 0; index < chars.length; index++) {
if (chars[index] == '\r' || chars[index] == '\n') {
int indexPlusOne = index + 1;
if (chars[index] == '\r' && indexPlusOne < chars.length
&& chars[indexPlusOne] == '\n') {
// "\r\n"
continue;
}
writer.write("\n");
if (indexPlusOne < chars.length
&& (chars[indexPlusOne] == '#' || chars[indexPlusOne] == '!')) {
// return char with either '#' or '!' afterward
continue;
}
writer.write('#');
} else {
writer.write(chars[index]);
}
}
writer.write("\n");
}
}

View File

@ -15,6 +15,7 @@
*/ */
package org.teavm.jso.dom.html; package org.teavm.jso.dom.html;
import java.util.function.Consumer;
import org.teavm.jso.JSProperty; import org.teavm.jso.JSProperty;
import org.teavm.jso.browser.Window; import org.teavm.jso.browser.Window;
import org.teavm.jso.dom.events.EventTarget; import org.teavm.jso.dom.events.EventTarget;
@ -32,6 +33,12 @@ public interface HTMLDocument extends Document, EventTarget {
@Override @Override
HTMLElement createElement(String tagName); HTMLElement createElement(String tagName);
default HTMLElement createElement(String tagName, Consumer<HTMLElement> consumer) {
HTMLElement result = createElement(tagName);
consumer.accept(result);
return result;
}
@Override @Override
HTMLElement getElementById(String elementId); HTMLElement getElementById(String elementId);

View File

@ -15,6 +15,7 @@
*/ */
package org.teavm.jso.dom.html; package org.teavm.jso.dom.html;
import java.util.function.Consumer;
import org.teavm.jso.JSProperty; import org.teavm.jso.JSProperty;
import org.teavm.jso.dom.css.ElementCSSInlineStyle; import org.teavm.jso.dom.css.ElementCSSInlineStyle;
import org.teavm.jso.dom.events.EventTarget; import org.teavm.jso.dom.events.EventTarget;
@ -110,4 +111,34 @@ public interface HTMLElement extends Element, ElementCSSInlineStyle, EventTarget
void setInnerHTML(String content); void setInnerHTML(String content);
TextRectangle getBoundingClientRect(); TextRectangle getBoundingClientRect();
default HTMLElement withAttr(String name, String value) {
setAttribute(name, value);
return this;
}
default HTMLElement withChild(String tagName) {
HTMLElement result = getOwnerDocument().createElement(tagName);
appendChild(result);
return result;
}
default HTMLElement withChild(String tagName, Consumer<HTMLElement> consumer) {
HTMLElement result = getOwnerDocument().createElement(tagName);
appendChild(result);
consumer.accept(result);
return result;
}
default HTMLElement clear() {
while (getLastChild() != null) {
removeChild(getLastChild());
}
return this;
}
default HTMLElement withText(String content) {
clear().appendChild(getOwnerDocument().createTextNode(content));
return this;
}
} }

View File

@ -103,4 +103,10 @@ public interface Node extends JSObject {
@JSProperty @JSProperty
Document getOwnerDocument(); Document getOwnerDocument();
default void delete() {
if (getParentNode() != null) {
getParentNode().removeChild(this);
}
}
} }

View File

@ -0,0 +1,59 @@
package org.teavm.samples.scala
import org.teavm.samples.scala.Grammar._
object Calculator {
def eval(expr : Expr) : BigInt = expr match {
case Add(a, b) => eval(a) + eval(b)
case Subtract(a, b) => eval(a) - eval(b)
case Multiply(a, b) => eval(a) * eval(b)
case Divide(a, b) => eval(a) * eval(b)
case Negate(n) => -eval(n)
case Number(v) => v
}
def print(expr : Expr) : String = expr match {
case Add(a, b) => "(" + print(a) + " + " + print(b) + ")"
case Subtract(a, b) => "(" + print(a) + " - " + print(b) + ")"
case Multiply(a, b) => "(" + print(a) + " * " + print(b) + ")"
case Divide(a, b) => "(" + print(a) + " / " + print(b) + ")"
case Negate(n) => "-" + print(n)
case Number(v) => v.toString()
}
def parse(str : Seq[Char]) = additive.parse(str)
def additive = multiplicative ~ ((keyword("+") | keyword("-")) ~ multiplicative).* >> {
case (h, t) => t.foldLeft(h) {
case (left, ("+", right)) => Add(left, right)
case (left, ("-", right)) => Subtract(left, right)
}
}
def multiplicative = unary ~ ((keyword("*") | keyword("/")) ~ unary).* >> {
case (h, t) => t.foldLeft(h) {
case (left, ("*", right)) => Multiply(left, right)
case (left, ("/", right)) => Divide(left, right)
}
}
def unary : Rule[Expr] = keyword("-").? ~ primitive >> {
case (Some(_), v) => Negate(v)
case (None, v) => v
}
def primitive : Rule[Expr] = Rule.firstOf(number >> Number, group)
def group : Rule[Expr] = keyword("(") ~ additive ~ keyword(")") >> { case ((_, result), _) => result }
def number : Rule[Int] = range('1', '9') ~ range('0', '9').* ~ ws >> {
case ((h, t), _) => t.foldLeft(h - '0')((n, c) => n * 10 + (c - '0'))
}
def keyword(str : String) = s(str) ~ ws >> { case (s, _) => s }
def ws = s(" ").* >> { case _ => Unit }
}
sealed abstract class Expr
case class Number(value : Int) extends Expr
case class Add(left : Expr, right : Expr) extends Expr
case class Subtract(left : Expr, right : Expr) extends Expr
case class Multiply(left : Expr, right : Expr) extends Expr
case class Divide(left : Expr, right : Expr) extends Expr
case class Negate(argument : Expr) extends Expr

View File

@ -1,86 +1,30 @@
package org.teavm.samples.scala package org.teavm.samples.scala
import org.teavm.samples.scala.DOM._
import org.teavm.samples.scala.Calculator.parse
import org.teavm.samples.scala.Calculator.print
import org.teavm.samples.scala.Calculator.eval
import org.teavm.jso.browser.Window import org.teavm.jso.browser.Window
import org.teavm.jso.dom.html._ import org.teavm.jso.dom.html._
import org.teavm.jso.dom.events._ import org.teavm.jso.dom.events._
object Client extends Grammar { object Client {
def main(args: Array[String]) { def main(args: Array[String]) {
var document = HTMLDocument.current var doc = HTMLDocument.current
var exprElem = document.getElementById("expr").asInstanceOf[HTMLInputElement] var exprElem = doc.getElementById("expr").asInstanceOf[HTMLInputElement]
var calcElem = document.getElementById("calculate") var calcElem = doc.getElementById("calculate")
var resultList = document.getElementById("result-list"); var resultList = doc.getElementById("result-list");
calcElem.addEventListener("click", new EventListener[MouseEvent]() { calcElem.addEventListener("click", (e : MouseEvent) => {
def handleEvent(e : MouseEvent) { parse(exprElem.getValue().toSeq) match {
additive.parse(exprElem.getValue().toSeq) match {
case (None, _) => Window.alert("Error parsing expression"); case (None, _) => Window.alert("Error parsing expression");
case (Some(x), Nil) => { case (Some(x), Nil) => {
val resultElem = document.createElement("div") resultList.insertBefore(doc.createElement("div", (elem : HTMLElement) => {
var exprSpan = document.createElement("span"); elem.withChild("span").withAttr("class", "plan").withText(print(x) + " = ")
exprSpan.setAttribute("class", "plan") elem.withChild("span").withAttr("class", "result").withText(eval(x).toString)
exprSpan.appendChild(document.createTextNode(print(x) + " = ")) }), resultList.getFirstChild)
var resultSpan = document.createElement("span")
resultSpan.setAttribute("class", "result")
resultSpan.appendChild(document.createTextNode(eval(x).toString()))
resultElem.appendChild(exprSpan)
resultElem.appendChild(resultSpan)
resultList.insertBefore(resultElem, resultList.getFirstChild)
} }
case (_, err) => Window.alert("Error parsing expression: " + err); case (_, err) => Window.alert("Error parsing expression: " + err);
} }
}
}) })
} }
def eval(expr : Expr) : BigInt = expr match {
case Add(a, b) => eval(a) + eval(b)
case Subtract(a, b) => eval(a) - eval(b)
case Multiply(a, b) => eval(a) * eval(b)
case Divide(a, b) => eval(a) * eval(b)
case Negate(n) => -eval(n)
case Number(v) => v
} }
def print(expr : Expr) : String = expr match {
case Add(a, b) => "(" + print(a) + " + " + print(b) + ")"
case Subtract(a, b) => "(" + print(a) + " - " + print(b) + ")"
case Multiply(a, b) => "(" + print(a) + " * " + print(b) + ")"
case Divide(a, b) => "(" + print(a) + " / " + print(b) + ")"
case Negate(n) => "-" + print(n)
case Number(v) => v.toString()
}
def additive = multiplicative ~ ((keyword("+") | keyword("-")) ~ multiplicative).* >> {
case (h, t) => t.foldLeft(h) {
case (left, ("+", right)) => Add(left, right)
case (left, ("-", right)) => Subtract(left, right)
}
}
def multiplicative = primitive ~ ((keyword("*") | keyword("/")) ~ primitive).* >> {
case (h, t) => t.foldLeft(h) {
case (left, ("*", right)) => Multiply(left, right)
case (left, ("/", right)) => Divide(left, right)
}
}
def unary : Rule[Expr] = keyword("-").? ~ primitive >> {
case (Some(_), v) => Negate(v)
case (None, v) => v
}
def primitive : Rule[Expr] = Rule.firstOf(number >> Number, group)
def group : Rule[Expr] = keyword("(") ~ additive ~ keyword(")") >> { case ((_, result), _) => result }
def number : Rule[Int] = range('1', '9') ~ range('0', '9').* ~ ws >> {
case ((h, t), _) => t.foldLeft(h - '0')((n, c) => n * 10 + (c - '0'))
}
def keyword(str : String) = s(str) ~ ws >> { case (s, _) => s }
def ws = s(" ").* >> { case _ => Unit }
}
sealed abstract class Expr
case class Number(value : Int) extends Expr
case class Add(left : Expr, right : Expr) extends Expr
case class Subtract(left : Expr, right : Expr) extends Expr
case class Multiply(left : Expr, right : Expr) extends Expr
case class Divide(left : Expr, right : Expr) extends Expr
case class Negate(argument : Expr) extends Expr

View File

@ -0,0 +1,18 @@
package org.teavm.samples.scala
import org.teavm.jso.dom.events._
import java.util.function.Consumer
object DOM {
implicit def toEventListener[T <: Event](f : T => Any) : EventListener[T] = {
new EventListener[T]() {
def handleEvent(e : T) = f(e)
}
}
implicit def toConsumer[T](f : T => Any) : Consumer[T] = {
new Consumer[T]() {
def accept(e : T) = f(e)
}
}
}

View File

@ -1,6 +1,6 @@
package org.teavm.samples.scala package org.teavm.samples.scala
trait Grammar { object Grammar {
def rule[T](f : Seq[Char] => (Option[T], Seq[Char])) : Rule[T] = Rule.rule(f) def rule[T](f : Seq[Char] => (Option[T], Seq[Char])) : Rule[T] = Rule.rule(f)
def s(str : String) : Rule[String] = Rule.expect(str) def s(str : String) : Rule[String] = Rule.expect(str)
def range(first : Char, last : Char) : Rule[Char] = Rule.range(first, last) def range(first : Char, last : Char) : Rule[Char] = Rule.range(first, last)
@ -23,21 +23,16 @@ trait Rule[T] {
} }
object Rule { object Rule {
def rule[T](f : Seq[Char] => (Option[T], Seq[Char])) : Rule[T] = { def rule[T](f : Seq[Char] => (Option[T], Seq[Char])) : Rule[T] = new Rule[T]() {
new Rule[T]() {
def parse(chars : Seq[Char]) : (Option[T], Seq[Char]) = f(chars) def parse(chars : Seq[Char]) : (Option[T], Seq[Char]) = f(chars)
} }
}
def concat[S, T](left : Rule[S], right : => Rule[T]) : Rule[(S, T)] = { def concat[S, T](left : Rule[S], right : => Rule[T]) : Rule[(S, T)] = {
lazy val right2 = right rule(chars => left.parse(chars) match {
rule(chars => { case (Some(leftResult), rem) => right.parse(rem) match {
left.parse(chars) match {
case (Some(leftResult), rem) => right2.parse(rem) match {
case (Some(rightResult), rem2) => (Some((leftResult, rightResult)), rem2) case (Some(rightResult), rem2) => (Some((leftResult, rightResult)), rem2)
case (None, rem2) => (None, rem) case (None, rem2) => (None, rem)
} }
case (None, rem) => (None, rem) case (None, rem) => (None, rem)
}
}) })
} }
def unlimited[T](inner : Rule[T]) : Rule[List[T]] = { def unlimited[T](inner : Rule[T]) : Rule[List[T]] = {
@ -56,24 +51,19 @@ object Rule {
}) })
} }
def firstOf[T](first : Rule[T], second : => Rule[T]) : Rule[T] = { def firstOf[T](first : Rule[T], second : => Rule[T]) : Rule[T] = {
lazy val second2 = second rule(chars => first.parse(chars) match {
rule(chars => {
first.parse(chars) match {
case (Some(result), rem) => (Some(result), rem) case (Some(result), rem) => (Some(result), rem)
case (None, _) => second2.parse(chars) match { case (None, _) => second.parse(chars) match {
case (Some(result), rem) => (Some(result), rem) case (Some(result), rem) => (Some(result), rem)
case (None, _) => (None, chars) case (None, _) => (None, chars)
} }
}
}) })
} }
def optional[T](inner : Rule[T]) : Rule[Option[T]] = { def optional[T](inner : Rule[T]) : Rule[Option[T]] = {
rule { rule(chars => inner.parse(chars) match {
chars => inner.parse(chars) match {
case (Some(result), rem) => (Some(Some(result)), rem) case (Some(result), rem) => (Some(Some(result)), rem)
case (None, rem) => (Some(None), chars) case (None, rem) => (Some(None), chars)
} })
}
} }
implicit def expect(str : String) : Rule[String] = { implicit def expect(str : String) : Rule[String] = {
def iter(chars : Seq[Char], index : Int) : (Boolean, Seq[Char]) = { def iter(chars : Seq[Char], index : Int) : (Boolean, Seq[Char]) = {
@ -94,19 +84,15 @@ object Rule {
}) })
} }
def andThen[T, S](inner : Rule[T], f : T => S) : Rule[S] = { def andThen[T, S](inner : Rule[T], f : T => S) : Rule[S] = {
rule(chars => { rule(chars => inner.parse(chars) match {
inner.parse(chars) match {
case (Some(result), rem) => (Some(f(result)), rem) case (Some(result), rem) => (Some(f(result)), rem)
case (None, rem) => (None, rem) case (None, rem) => (None, rem)
}
}) })
} }
def range(first : Char, last : Char) : Rule[Char] = { def range(first : Char, last : Char) : Rule[Char] = {
rule(chars => { rule(chars => chars match {
chars match {
case c +: rem if c >= first && c <= last => (Some(c), rem) case c +: rem if c >= first && c <= last => (Some(c), rem)
case _ => (None, chars) case _ => (None, chars)
}
}) })
} }
} }