Add WatParser and tests

This commit is contained in:
Volker Berlin 2018-11-11 11:00:52 +01:00
parent 101b759acb
commit 0119a2bbc2
4 changed files with 353 additions and 2 deletions

View File

@ -99,8 +99,8 @@ public abstract class WasmCodeBuilder {
* the code position/offset in the Java method
*/
@Nonnull
protected void addLoadStoreInstruction( boolean load, @Nonnegative int wasmIdx, int javaCodePos ) {
instructions.add( new WasmLoadStoreInstruction( load, wasmIdx, null, javaCodePos ) );
protected void addLocalInstruction( boolean load, @Nonnegative int wasmIdx, int javaCodePos ) {
instructions.add( new WasmLocalInstruction( load, wasmIdx, javaCodePos ) );
}
/**

View File

@ -0,0 +1,77 @@
/*
Copyright 2018 Volker Berlin (i-net software)
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 de.inetsoftware.jwebassembly.module;
import java.io.IOException;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
/**
* WasmInstruction for load and store local variables.
*
* @author Volker Berlin
*
*/
class WasmLocalInstruction extends WasmInstruction {
private boolean load;
private int idx;
/**
* Create an instance of a load/store instruction
*
* @param load
* true: if load
* @param idx
* the memory/slot idx of the variable
* @param javaCodePos
* the code position/offset in the Java method
*/
WasmLocalInstruction( boolean load, @Nonnegative int idx, int javaCodePos ) {
super( javaCodePos );
this.load = load;
this.idx = idx;
}
/**
* {@inheritDoc}
*/
public void writeTo( @Nonnull ModuleWriter writer ) throws IOException {
if( load ) {
writer.writeLoad( idx );
} else {
writer.writeStore( idx );
}
}
/**
* {@inheritDoc}
*/
ValueType getPushValueType() {
return null;
}
/**
* {@inheritDoc}
*/
@Override
int getPopCount() {
return load ? 0 : 1;
}
}

View File

@ -0,0 +1,151 @@
/*
Copyright 2018 Volker Berlin (i-net software)
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 de.inetsoftware.jwebassembly.watparser;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import de.inetsoftware.jwebassembly.WasmException;
import de.inetsoftware.jwebassembly.module.NumericOperator;
import de.inetsoftware.jwebassembly.module.ValueType;
import de.inetsoftware.jwebassembly.module.ValueTypeConvertion;
import de.inetsoftware.jwebassembly.module.WasmBlockOperator;
import de.inetsoftware.jwebassembly.module.WasmCodeBuilder;
/**
* Parser for text format of a function.
*
* @author Volker Berlin
*/
public class WatParser extends WasmCodeBuilder {
public WatParser() {
}
/**
* Parse the given wasm text format and generate a list of WasmInstuctions
*
* @param wat
* the text format content of a function
*/
public void parse( String wat, int lineNumber ) {
try {
reset();
List<String> tokens = splitTokens( wat );
for( int i = 0; i < tokens.size(); i++ ) {
int javaCodePos = i;
String tok = tokens.get( i );
switch( tok ) {
case "get_local":
addLocalInstruction( true, getInt( tokens, ++i), javaCodePos );
break;
case "set_local":
addLocalInstruction( false, getInt( tokens, ++i), javaCodePos );
break;
// case "get_global":
// addGlobalInstruction( true, ref, javaCodePos );
// break;
case "i32.const":
addConstInstruction( getInt( tokens, ++i), ValueType.i32, javaCodePos );
break;
case "i32.add":
addNumericInstruction( NumericOperator.add, ValueType.i32, javaCodePos );
break;
case "i64.extend_s/i32":
addConvertInstruction( ValueTypeConvertion.i2l, javaCodePos );
break;
// case "call":
// addCallInstruction( method, javaCodePos );
// break;
case "return":
addBlockInstruction( WasmBlockOperator.RETURN, null, javaCodePos );
break;
}
}
} catch( Exception ex ) {
throw WasmException.create( ex, lineNumber );
}
}
/**
* Get the token at given position as int.
*
* @param tokens
* the token list
* @param idx
* the position in the tokens
* @return the int value
*/
private int getInt( List<String> tokens, @Nonnegative int idx ) {
return Integer.parseInt( get( tokens, idx ) );
}
/**
* Get the token at given position
*
* @param tokens
* the token list
* @param idx
* the position in the tokens
* @return the token
*/
@Nonnull
private String get( List<String> tokens, @Nonnegative int idx ) {
if( idx >= tokens.size() ) {
String previous = tokens.get( Math.min( idx, tokens.size() ) - 1 );
throw new WasmException( "Missing Token in wasm text format after token: " + previous, -1 );
}
return tokens.get( idx );
}
/**
* Split the string in tokens.
*
* @param wat
* string with wasm text format
* @return the token list.
*/
private List<String> splitTokens( @Nullable String wat ) {
ArrayList<String> tokens = new ArrayList<>();
int count = wat.length();
int off = 0;
for( int i = 0; i < count; i++ ) {
char ch = wat.charAt( i );
switch( ch ) {
case ' ':
case '\n':
case '\r':
case '\t':
if( off + 1 < i ) {
tokens.add( wat.substring( off, i ) );
}
off = i + 1;
break;
}
}
if( off < count ) {
tokens.add( wat.substring( off, count ) );
}
return tokens;
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright 2018 Volker Berlin (i-net software)
*
* 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 de.inetsoftware.jwebassembly.module;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.HashMap;
import javax.annotation.Nullable;
import org.junit.Test;
import de.inetsoftware.jwebassembly.WasmException;
import de.inetsoftware.jwebassembly.text.TextModuleWriter;
import de.inetsoftware.jwebassembly.watparser.WatParser;
/**
* @author Volker Berlin
*/
public class WatParserTest {
private void test( String wat ) throws IOException {
WatParser parser = new WatParser();
parser.parse( wat, 100 );
WasmCodeBuilder codeBuilder = parser;
StringBuilder builder = new StringBuilder();
ModuleWriter writer = new TextModuleWriter( builder, new HashMap<>() );
for( WasmInstruction instruction : codeBuilder.getInstructions() ) {
instruction.writeTo( writer );
}
writer.writeMethodFinish();
String expected = normalize( "(module " + wat + " )" );
String actual = normalize( builder );
assertEquals( expected, actual );
}
private String normalize( @Nullable CharSequence str ) {
boolean wasSpace = false;
StringBuilder builder = new StringBuilder();
for( int i = 0; i < str.length(); i++ ) {
char ch = str.charAt( i );
switch( ch ) {
case ' ':
case '\n':
case '\r':
case '\t':
if( !wasSpace ) {
builder.append( ' ' );
wasSpace = true;
}
break;
default:
builder.append( ch );
wasSpace = false;
}
}
return builder.toString();
}
private void testError( String wat, String errorMessage ) throws IOException {
try {
test( wat );
fail( "Exception expected with message: " + errorMessage );
} catch( WasmException ex ) {
String error = ex.getMessage();
int newlineIdx = error.indexOf( '\n' );
if( newlineIdx > 0 ) {
error = error.substring( 0, newlineIdx );
}
assertEquals( errorMessage, error );
}
}
@Test
public void getLocal() throws IOException {
test( "get_local 1" );
}
@Test
public void setLocal() throws IOException {
test( "set_local 2" );
}
@Test
public void i32_add() throws IOException {
test( "i32.add" );
}
@Test
public void i32_const() throws IOException {
test( " i32.const -7 " );
}
@Test
public void i64_extend_s_i32() throws IOException {
test( "i64.extend_s/i32" );
}
@Test
public void return_() throws IOException {
test( "return\n" );
}
@Test
public void errorMissingToken() throws IOException {
testError( "i32.const", "Missing Token in wasm text format after token: i32.const" );
}
}