mirror of
https://github.com/i-net-software/JWebAssembly.git
synced 2025-03-25 07:27:52 +01:00
write methods with Export annotation as exported.
This commit is contained in:
parent
ec93b799b9
commit
7add526da1
@ -1,234 +1,270 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Volker Berlin (i-net software)
|
* Copyright 2017 Volker Berlin (i-net software)
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package de.inetsoftware.jwebassembly.binary;
|
package de.inetsoftware.jwebassembly.binary;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import de.inetsoftware.jwebassembly.module.ModuleWriter;
|
|
||||||
import de.inetsoftware.jwebassembly.module.ValueType;
|
import de.inetsoftware.jwebassembly.module.ModuleWriter;
|
||||||
|
import de.inetsoftware.jwebassembly.module.ValueType;
|
||||||
/**
|
|
||||||
* Module Writer for binary format. http://webassembly.org/docs/binary-encoding/
|
/**
|
||||||
*
|
* Module Writer for binary format. http://webassembly.org/docs/binary-encoding/
|
||||||
* @author Volker Berlin
|
*
|
||||||
*/
|
* @author Volker Berlin
|
||||||
public class BinaryModuleWriter extends ModuleWriter implements InstructionOpcodes {
|
*/
|
||||||
|
public class BinaryModuleWriter extends ModuleWriter implements InstructionOpcodes {
|
||||||
private static final byte[] WASM_BINARY_MAGIC = { 0, 'a', 's', 'm' };
|
|
||||||
|
private static final byte[] WASM_BINARY_MAGIC = { 0, 'a', 's', 'm' };
|
||||||
private static final int WASM_BINARY_VERSION = 1;
|
|
||||||
|
private static final int WASM_BINARY_VERSION = 1;
|
||||||
private WasmOutputStream wasm;
|
|
||||||
|
private WasmOutputStream wasm;
|
||||||
private WasmOutputStream codeStream = new WasmOutputStream();
|
|
||||||
|
private WasmOutputStream codeStream = new WasmOutputStream();
|
||||||
private WasmOutputStream functionsStream = new WasmOutputStream();
|
|
||||||
|
private WasmOutputStream functionsStream = new WasmOutputStream();
|
||||||
private List<FunctionType> functionTypes = new ArrayList<>();
|
|
||||||
|
private List<FunctionType> functionTypes = new ArrayList<>();
|
||||||
private Map<String, Function> functions = new LinkedHashMap<>();
|
|
||||||
|
private Map<String, Function> functions = new LinkedHashMap<>();
|
||||||
private Function function;
|
|
||||||
|
private Map<String, String> exports = new LinkedHashMap<>();
|
||||||
private FunctionType functionType;
|
|
||||||
|
private Function function;
|
||||||
/**
|
|
||||||
* Create new instance.
|
private FunctionType functionType;
|
||||||
*
|
|
||||||
* @param output
|
/**
|
||||||
* the target for the module data.
|
* Create new instance.
|
||||||
* @throws IOException
|
*
|
||||||
* if any I/O error occur
|
* @param output
|
||||||
*/
|
* the target for the module data.
|
||||||
public BinaryModuleWriter( OutputStream output ) throws IOException {
|
* @throws IOException
|
||||||
wasm = new WasmOutputStream( output );
|
* if any I/O error occur
|
||||||
}
|
*/
|
||||||
|
public BinaryModuleWriter( OutputStream output ) throws IOException {
|
||||||
/**
|
wasm = new WasmOutputStream( output );
|
||||||
* {@inheritDoc}
|
}
|
||||||
*/
|
|
||||||
@Override
|
/**
|
||||||
public void close() throws IOException {
|
* {@inheritDoc}
|
||||||
wasm.write( WASM_BINARY_MAGIC );
|
*/
|
||||||
wasm.writeInt32( WASM_BINARY_VERSION );
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
writeTypeSection();
|
wasm.write( WASM_BINARY_MAGIC );
|
||||||
writeFunctionSection();
|
wasm.writeInt32( WASM_BINARY_VERSION );
|
||||||
writeCodeSection();
|
|
||||||
|
writeTypeSection();
|
||||||
wasm.close();
|
writeFunctionSection();
|
||||||
}
|
writeExportSection();
|
||||||
|
writeCodeSection();
|
||||||
/**
|
|
||||||
* Write the type section to the output. This section contains the signatures of the functions.
|
wasm.close();
|
||||||
*
|
}
|
||||||
* @throws IOException
|
|
||||||
* if any I/O error occur
|
/**
|
||||||
*/
|
* Write the type section to the output. This section contains the signatures of the functions.
|
||||||
private void writeTypeSection() throws IOException {
|
*
|
||||||
int count = functionTypes.size();
|
* @throws IOException
|
||||||
if( count > 0 ) {
|
* if any I/O error occur
|
||||||
WasmOutputStream stream = new WasmOutputStream();
|
*/
|
||||||
stream.writeVaruint32( count );
|
private void writeTypeSection() throws IOException {
|
||||||
for( FunctionType type : functionTypes ) {
|
int count = functionTypes.size();
|
||||||
stream.writeVarint32( ValueType.func.getCode() );
|
if( count > 0 ) {
|
||||||
stream.writeVaruint32( type.params.size() );
|
WasmOutputStream stream = new WasmOutputStream();
|
||||||
for( ValueType valueType : type.params ) {
|
stream.writeVaruint32( count );
|
||||||
stream.writeVarint32( valueType.getCode() );
|
for( FunctionType type : functionTypes ) {
|
||||||
}
|
stream.writeVarint32( ValueType.func.getCode() );
|
||||||
if( type.result == null ) {
|
stream.writeVaruint32( type.params.size() );
|
||||||
stream.writeVaruint32( 0 );
|
for( ValueType valueType : type.params ) {
|
||||||
} else {
|
stream.writeVarint32( valueType.getCode() );
|
||||||
stream.writeVaruint32( 1 );
|
}
|
||||||
stream.writeVarint32( type.result.getCode() );
|
if( type.result == null ) {
|
||||||
}
|
stream.writeVaruint32( 0 );
|
||||||
}
|
} else {
|
||||||
wasm.writeSection( SectionType.Type, stream, null );
|
stream.writeVaruint32( 1 );
|
||||||
}
|
stream.writeVarint32( type.result.getCode() );
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
wasm.writeSection( SectionType.Type, stream, null );
|
||||||
* Write the function section to the output. This section contains a mapping from the function index to the type signature index.
|
}
|
||||||
*
|
}
|
||||||
* @throws IOException
|
|
||||||
* if any I/O error occur
|
/**
|
||||||
*/
|
* Write the function section to the output. This section contains a mapping from the function index to the type signature index.
|
||||||
private void writeFunctionSection() throws IOException {
|
*
|
||||||
int count = functions.size();
|
* @throws IOException
|
||||||
if( count > 0 ) {
|
* if any I/O error occur
|
||||||
WasmOutputStream stream = new WasmOutputStream();
|
*/
|
||||||
stream.writeVaruint32( count );
|
private void writeFunctionSection() throws IOException {
|
||||||
for( Function func : functions.values() ) {
|
int count = functions.size();
|
||||||
stream.writeVaruint32( func.typeId );
|
if( count > 0 ) {
|
||||||
}
|
WasmOutputStream stream = new WasmOutputStream();
|
||||||
wasm.writeSection( SectionType.Function, stream, null );
|
stream.writeVaruint32( count );
|
||||||
}
|
for( Function func : functions.values() ) {
|
||||||
}
|
stream.writeVaruint32( func.typeId );
|
||||||
|
}
|
||||||
/**
|
wasm.writeSection( SectionType.Function, stream, null );
|
||||||
* Write the code section to the output. This section contains the byte code.
|
}
|
||||||
*
|
}
|
||||||
* @throws IOException
|
|
||||||
* if any I/O error occur
|
/**
|
||||||
*/
|
* Write the export section to the output. This section contains a mapping from the external index to the type signature index.
|
||||||
private void writeCodeSection() throws IOException {
|
*
|
||||||
int size = functions.size();
|
* @throws IOException
|
||||||
if( size == 0 ) {
|
* if any I/O error occur
|
||||||
return;
|
*/
|
||||||
}
|
private void writeExportSection() throws IOException {
|
||||||
WasmOutputStream stream = new WasmOutputStream();
|
int count = exports.size();
|
||||||
stream.writeVaruint32( size );
|
if( count > 0 ) {
|
||||||
functionsStream.writeTo( stream );
|
WasmOutputStream stream = new WasmOutputStream();
|
||||||
wasm.writeSection( SectionType.Code, stream, null );
|
stream.writeVaruint32( count );
|
||||||
}
|
for( Map.Entry<String,String> entry : exports.entrySet() ) {
|
||||||
|
String exportName = entry.getKey();
|
||||||
/**
|
byte[] bytes = exportName.getBytes( StandardCharsets.UTF_8 );
|
||||||
* {@inheritDoc}
|
stream.writeVaruint32( bytes.length );
|
||||||
*/
|
stream.write( bytes );
|
||||||
@Override
|
stream.writeVaruint32( ExternalKind.Function.ordinal() );
|
||||||
protected void writeMethodStart( String name ) throws IOException {
|
int id = functions.get( entry.getValue() ).id;
|
||||||
function = new Function();
|
stream.writeVaruint32( id );
|
||||||
function.id = functions.size();
|
}
|
||||||
functions.put( name, function );
|
wasm.writeSection( SectionType.Export, stream, null );
|
||||||
functionType = new FunctionType();
|
}
|
||||||
codeStream.reset();
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Write the code section to the output. This section contains the byte code.
|
||||||
* {@inheritDoc}
|
*
|
||||||
*/
|
* @throws IOException
|
||||||
@Override
|
* if any I/O error occur
|
||||||
protected void writeMethodParam( String kind, ValueType valueType ) throws IOException {
|
*/
|
||||||
switch( kind ) {
|
private void writeCodeSection() throws IOException {
|
||||||
case "param":
|
int size = functions.size();
|
||||||
functionType.params.add( valueType );
|
if( size == 0 ) {
|
||||||
return;
|
return;
|
||||||
case "return":
|
}
|
||||||
functionType.result = valueType;
|
WasmOutputStream stream = new WasmOutputStream();
|
||||||
return;
|
stream.writeVaruint32( size );
|
||||||
}
|
functionsStream.writeTo( stream );
|
||||||
}
|
wasm.writeSection( SectionType.Code, stream, null );
|
||||||
|
}
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
/**
|
||||||
*/
|
* {@inheritDoc}
|
||||||
@Override
|
*/
|
||||||
protected void writeMethodFinish( List<ValueType> locals ) throws IOException {
|
@Override
|
||||||
// TODO optimize and search for duplicates
|
protected void writeExport( String methodName, String exportName ) throws IOException {
|
||||||
function.typeId = functionTypes.size();
|
exports.put( exportName, methodName );
|
||||||
functionTypes.add( functionType );
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
WasmOutputStream localsStream = new WasmOutputStream();
|
* {@inheritDoc}
|
||||||
localsStream.writeVaruint32( locals.size() );
|
*/
|
||||||
for( ValueType valueType : locals ) {
|
@Override
|
||||||
localsStream.writeVaruint32( 1 ); // TODO optimize, write the count of same types.
|
protected void writeMethodStart( String name ) throws IOException {
|
||||||
localsStream.writeVarint32( valueType.getCode() );
|
function = new Function();
|
||||||
}
|
function.id = functions.size();
|
||||||
functionsStream.writeVaruint32( localsStream.size() + codeStream.size() + 1 );
|
functions.put( name, function );
|
||||||
localsStream.writeTo( functionsStream );
|
functionType = new FunctionType();
|
||||||
codeStream.writeTo( functionsStream );
|
codeStream.reset();
|
||||||
functionsStream.write( END );
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* {@inheritDoc}
|
||||||
* {@inheritDoc}
|
*/
|
||||||
*/
|
@Override
|
||||||
@Override
|
protected void writeMethodParam( String kind, ValueType valueType ) throws IOException {
|
||||||
protected void writeConstInt( int value ) throws IOException {
|
switch( kind ) {
|
||||||
codeStream.write( I32_CONST );
|
case "param":
|
||||||
codeStream.writeVarint32( value );
|
functionType.params.add( valueType );
|
||||||
}
|
return;
|
||||||
|
case "return":
|
||||||
/**
|
functionType.result = valueType;
|
||||||
* {@inheritDoc}
|
return;
|
||||||
*/
|
}
|
||||||
@Override
|
}
|
||||||
protected void writeLoad( int idx ) throws IOException {
|
|
||||||
codeStream.write( GET_LOCAL );
|
/**
|
||||||
codeStream.writeVaruint32( idx );
|
* {@inheritDoc}
|
||||||
}
|
*/
|
||||||
|
@Override
|
||||||
/**
|
protected void writeMethodFinish( List<ValueType> locals ) throws IOException {
|
||||||
* {@inheritDoc}
|
// TODO optimize and search for duplicates
|
||||||
*/
|
function.typeId = functionTypes.size();
|
||||||
@Override
|
functionTypes.add( functionType );
|
||||||
protected void writeStore( int idx ) throws IOException {
|
|
||||||
codeStream.write( SET_LOCAL );
|
|
||||||
codeStream.writeVaruint32( idx );
|
WasmOutputStream localsStream = new WasmOutputStream();
|
||||||
}
|
localsStream.writeVaruint32( locals.size() );
|
||||||
|
for( ValueType valueType : locals ) {
|
||||||
/**
|
localsStream.writeVaruint32( 1 ); // TODO optimize, write the count of same types.
|
||||||
* {@inheritDoc}
|
localsStream.writeVarint32( valueType.getCode() );
|
||||||
*/
|
}
|
||||||
@Override
|
functionsStream.writeVaruint32( localsStream.size() + codeStream.size() + 1 );
|
||||||
protected void writeAddInt() throws IOException {
|
localsStream.writeTo( functionsStream );
|
||||||
codeStream.write( I32_ADD );
|
codeStream.writeTo( functionsStream );
|
||||||
}
|
functionsStream.write( END );
|
||||||
|
}
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
/**
|
||||||
*/
|
* {@inheritDoc}
|
||||||
@Override
|
*/
|
||||||
protected void writeReturn() throws IOException {
|
@Override
|
||||||
codeStream.write( RETURN );
|
protected void writeConstInt( int value ) throws IOException {
|
||||||
}
|
codeStream.write( I32_CONST );
|
||||||
}
|
codeStream.writeVarint32( value );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void writeLoad( int idx ) throws IOException {
|
||||||
|
codeStream.write( GET_LOCAL );
|
||||||
|
codeStream.writeVaruint32( idx );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void writeStore( int idx ) throws IOException {
|
||||||
|
codeStream.write( SET_LOCAL );
|
||||||
|
codeStream.writeVaruint32( idx );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void writeAddInt() throws IOException {
|
||||||
|
codeStream.write( I32_ADD );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void writeReturn() throws IOException {
|
||||||
|
codeStream.write( RETURN );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,300 +1,336 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Volker Berlin (i-net software)
|
* Copyright 2017 Volker Berlin (i-net software)
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package de.inetsoftware.jwebassembly.module;
|
package de.inetsoftware.jwebassembly.module;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import javax.annotation.Nonnegative;
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnegative;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import de.inetsoftware.classparser.ClassFile;
|
|
||||||
import de.inetsoftware.classparser.Code;
|
import de.inetsoftware.classparser.Annotations;
|
||||||
import de.inetsoftware.classparser.CodeInputStream;
|
import de.inetsoftware.classparser.ClassFile;
|
||||||
import de.inetsoftware.classparser.LineNumberTable;
|
import de.inetsoftware.classparser.Code;
|
||||||
import de.inetsoftware.classparser.MethodInfo;
|
import de.inetsoftware.classparser.CodeInputStream;
|
||||||
import de.inetsoftware.jwebassembly.WasmException;
|
import de.inetsoftware.classparser.LineNumberTable;
|
||||||
|
import de.inetsoftware.classparser.MethodInfo;
|
||||||
/**
|
import de.inetsoftware.jwebassembly.WasmException;
|
||||||
* Module Writer for text format with S-expressions.
|
|
||||||
*
|
/**
|
||||||
* @author Volker Berlin
|
* Module Writer for text format with S-expressions.
|
||||||
*/
|
*
|
||||||
public abstract class ModuleWriter implements Closeable {
|
* @author Volker Berlin
|
||||||
|
*/
|
||||||
private int paramCount;
|
public abstract class ModuleWriter implements Closeable {
|
||||||
|
|
||||||
private ArrayList<ValueType> locals = new ArrayList<>();
|
private int paramCount;
|
||||||
|
|
||||||
/**
|
private ArrayList<ValueType> locals = new ArrayList<>();
|
||||||
* Write the content of the class to the
|
|
||||||
*
|
/**
|
||||||
* @param classFile
|
* Write the content of the class to the
|
||||||
* the class file
|
*
|
||||||
* @throws IOException
|
* @param classFile
|
||||||
* if any I/O error occur
|
* the class file
|
||||||
* @throws WasmException
|
* @throws IOException
|
||||||
* if some Java code can't converted
|
* if any I/O error occur
|
||||||
*/
|
* @throws WasmException
|
||||||
public void write( ClassFile classFile ) throws IOException, WasmException {
|
* if some Java code can't converted
|
||||||
MethodInfo[] methods = classFile.getMethods();
|
*/
|
||||||
for( MethodInfo method : methods ) {
|
public void write( ClassFile classFile ) throws IOException, WasmException {
|
||||||
Code code = method.getCode();
|
MethodInfo[] methods = classFile.getMethods();
|
||||||
if( method.getName().equals( "<init>" ) && method.getDescription().equals( "()V" )
|
for( MethodInfo method : methods ) {
|
||||||
&& code.isSuperInitReturn( classFile.getSuperClass() ) ) {
|
Code code = method.getCode();
|
||||||
continue; //default constructor
|
if( method.getName().equals( "<init>" ) && method.getDescription().equals( "()V" )
|
||||||
}
|
&& code.isSuperInitReturn( classFile.getSuperClass() ) ) {
|
||||||
writeMethod( method );
|
continue; //default constructor
|
||||||
}
|
}
|
||||||
}
|
writeMethod( method );
|
||||||
|
}
|
||||||
/**
|
}
|
||||||
* Write the content of a method.
|
|
||||||
*
|
/**
|
||||||
* @param method
|
* Write the content of a method.
|
||||||
* the method
|
*
|
||||||
* @throws IOException
|
* @param method
|
||||||
* if any I/O error occur
|
* the method
|
||||||
* @throws WasmException
|
* @throws IOException
|
||||||
* if some Java code can't converted
|
* if any I/O error occur
|
||||||
*/
|
* @throws WasmException
|
||||||
private void writeMethod( MethodInfo method ) throws IOException, WasmException {
|
* if some Java code can't converted
|
||||||
Code code = method.getCode();
|
*/
|
||||||
if( code != null ) { // abstract methods and interface methods does not have code
|
private void writeMethod( MethodInfo method ) throws IOException, WasmException {
|
||||||
writeMethodStart( method.getName() );
|
Code code = method.getCode();
|
||||||
writeMethodSignature( method );
|
if( code != null ) { // abstract methods and interface methods does not have code
|
||||||
locals.clear();
|
String methodName = method.getName(); // TODO naming conversion rule
|
||||||
LineNumberTable lineNumberTable = code.getLineNumberTable();
|
writeExport( methodName, method );
|
||||||
if( lineNumberTable != null ) {
|
writeMethodStart( methodName );
|
||||||
int lineNumber;
|
writeMethodSignature( method );
|
||||||
for( int i = 0; i < lineNumberTable.size(); i++ ) {
|
locals.clear();
|
||||||
lineNumber = lineNumberTable.getLineNumber( i );
|
LineNumberTable lineNumberTable = code.getLineNumberTable();
|
||||||
int offset = lineNumberTable.getStartOffset( i );
|
if( lineNumberTable != null ) {
|
||||||
int nextOffset =
|
int lineNumber;
|
||||||
i + 1 == lineNumberTable.size() ? code.getCodeSize()
|
for( int i = 0; i < lineNumberTable.size(); i++ ) {
|
||||||
: lineNumberTable.getStartOffset( i + 1 );
|
lineNumber = lineNumberTable.getLineNumber( i );
|
||||||
CodeInputStream byteCode = code.getByteCode( offset, nextOffset - offset );
|
int offset = lineNumberTable.getStartOffset( i );
|
||||||
writeCodeChunk( byteCode, lineNumber );
|
int nextOffset =
|
||||||
}
|
i + 1 == lineNumberTable.size() ? code.getCodeSize()
|
||||||
} else {
|
: lineNumberTable.getStartOffset( i + 1 );
|
||||||
CodeInputStream byteCode = code.getByteCode();
|
CodeInputStream byteCode = code.getByteCode( offset, nextOffset - offset );
|
||||||
writeCodeChunk( byteCode, -1 );
|
writeCodeChunk( byteCode, lineNumber );
|
||||||
}
|
}
|
||||||
for( int i = Math.min( paramCount, locals.size() ); i > 0; i-- ) {
|
} else {
|
||||||
locals.remove( 0 );
|
CodeInputStream byteCode = code.getByteCode();
|
||||||
}
|
writeCodeChunk( byteCode, -1 );
|
||||||
writeMethodFinish( locals );
|
}
|
||||||
}
|
for( int i = Math.min( paramCount, locals.size() ); i > 0; i-- ) {
|
||||||
}
|
locals.remove( 0 );
|
||||||
|
}
|
||||||
/**
|
writeMethodFinish( locals );
|
||||||
* Write the method header.
|
}
|
||||||
*
|
}
|
||||||
* @param name
|
|
||||||
* the method name
|
/**
|
||||||
* @throws IOException
|
* Look for a Export annotation and if there write an export directive.
|
||||||
* if any I/O error occur
|
* @param methodName
|
||||||
*/
|
* @param method
|
||||||
protected abstract void writeMethodStart( String name ) throws IOException;
|
* @throws IOException
|
||||||
|
*/
|
||||||
/**
|
private void writeExport( String methodName, MethodInfo method ) throws IOException {
|
||||||
* Write the parameter and return signatures
|
Annotations annotations = method.getRuntimeInvisibleAnnotations();
|
||||||
*
|
if( annotations != null ) {
|
||||||
* @param method
|
Map<String,Object> export = annotations.get( "org.webassembly.annotation.Export" );
|
||||||
* the method
|
if( export != null ) {
|
||||||
* @throws IOException
|
String exportName = (String)export.get( "name" );
|
||||||
* if any I/O error occur
|
if( exportName == null ) {
|
||||||
* @throws WasmException
|
exportName = methodName;
|
||||||
* if some Java code can't converted
|
}
|
||||||
*/
|
writeExport( methodName, exportName );
|
||||||
private void writeMethodSignature( MethodInfo method ) throws IOException, WasmException {
|
}
|
||||||
String signature = method.getDescription();
|
}
|
||||||
String kind = "param";
|
}
|
||||||
int paramCount = 0;
|
|
||||||
for( int i = 1; i < signature.length(); i++ ) {
|
/**
|
||||||
paramCount++;
|
* Write an export directive
|
||||||
String javaType;
|
*
|
||||||
switch( signature.charAt( i ) ) {
|
* @param methodName
|
||||||
case '[': // array
|
* the method name
|
||||||
javaType = "array";
|
* @param exportName
|
||||||
break;
|
* the export name, if null then the same like the method name
|
||||||
case 'L':
|
* @throws IOException
|
||||||
javaType = "object";
|
* if any I/O error occur
|
||||||
break;
|
*/
|
||||||
case 'B': // byte
|
protected abstract void writeExport( String methodName, String exportName ) throws IOException;
|
||||||
javaType = "byte";
|
|
||||||
break;
|
/**
|
||||||
case 'C': // char
|
* Write the method header.
|
||||||
javaType = "char";
|
*
|
||||||
break;
|
* @param name
|
||||||
case 'D': // double
|
* the method name
|
||||||
writeMethodParam( kind, ValueType.f64 );
|
* @throws IOException
|
||||||
continue;
|
* if any I/O error occur
|
||||||
case 'F': // float
|
*/
|
||||||
writeMethodParam( kind, ValueType.f32 );
|
protected abstract void writeMethodStart( String name ) throws IOException;
|
||||||
continue;
|
|
||||||
case 'I': // int
|
/**
|
||||||
writeMethodParam( kind, ValueType.i32 );
|
* Write the parameter and return signatures
|
||||||
continue;
|
*
|
||||||
case 'J': // long
|
* @param method
|
||||||
writeMethodParam( kind, ValueType.i64 );
|
* the method
|
||||||
continue;
|
* @throws IOException
|
||||||
case 'V': // void
|
* if any I/O error occur
|
||||||
continue;
|
* @throws WasmException
|
||||||
case ')':
|
* if some Java code can't converted
|
||||||
this.paramCount = paramCount - 1;
|
*/
|
||||||
kind = "return";
|
private void writeMethodSignature( MethodInfo method ) throws IOException, WasmException {
|
||||||
continue;
|
String signature = method.getDescription();
|
||||||
default:
|
String kind = "param";
|
||||||
javaType = signature.substring( i, i + 1 );
|
int paramCount = 0;
|
||||||
}
|
for( int i = 1; i < signature.length(); i++ ) {
|
||||||
int lineNumber = method.getCode().getFirstLineNr();
|
paramCount++;
|
||||||
throw new WasmException( "Not supported Java data type in method signature: " + javaType, lineNumber );
|
String javaType;
|
||||||
}
|
switch( signature.charAt( i ) ) {
|
||||||
}
|
case '[': // array
|
||||||
|
javaType = "array";
|
||||||
/**
|
break;
|
||||||
* Write a method parameter.
|
case 'L':
|
||||||
*
|
javaType = "object";
|
||||||
* @param kind
|
break;
|
||||||
* "param", "return" or "local"
|
case 'B': // byte
|
||||||
* @param valueType
|
javaType = "byte";
|
||||||
* the data type of the parameter
|
break;
|
||||||
* @throws IOException
|
case 'C': // char
|
||||||
* if any I/O error occur
|
javaType = "char";
|
||||||
*/
|
break;
|
||||||
protected abstract void writeMethodParam( String kind, ValueType valueType ) throws IOException;
|
case 'D': // double
|
||||||
|
writeMethodParam( kind, ValueType.f64 );
|
||||||
/**
|
continue;
|
||||||
* Complete the method
|
case 'F': // float
|
||||||
*
|
writeMethodParam( kind, ValueType.f32 );
|
||||||
* @param locals
|
continue;
|
||||||
* a list with types of local variables
|
case 'I': // int
|
||||||
*
|
writeMethodParam( kind, ValueType.i32 );
|
||||||
* @throws IOException
|
continue;
|
||||||
* if any I/O error occur
|
case 'J': // long
|
||||||
*/
|
writeMethodParam( kind, ValueType.i64 );
|
||||||
protected abstract void writeMethodFinish( List<ValueType> locals ) throws IOException;
|
continue;
|
||||||
|
case 'V': // void
|
||||||
/**
|
continue;
|
||||||
* Write a chunk of byte code.
|
case ')':
|
||||||
*
|
this.paramCount = paramCount - 1;
|
||||||
* @param byteCode
|
kind = "return";
|
||||||
* a stream of byte code
|
continue;
|
||||||
* @param lineNumber
|
default:
|
||||||
* the current line number
|
javaType = signature.substring( i, i + 1 );
|
||||||
* @throws WasmException
|
}
|
||||||
* if some Java code can't converted
|
int lineNumber = method.getCode().getFirstLineNr();
|
||||||
*/
|
throw new WasmException( "Not supported Java data type in method signature: " + javaType, lineNumber );
|
||||||
private void writeCodeChunk( CodeInputStream byteCode, int lineNumber ) throws WasmException {
|
}
|
||||||
try {
|
}
|
||||||
while( byteCode.available() > 0 ) {
|
|
||||||
int op = byteCode.readUnsignedByte();
|
/**
|
||||||
switch( op ) {
|
* Write a method parameter.
|
||||||
case 4: // iconst_1
|
*
|
||||||
writeConstInt( 1 );
|
* @param kind
|
||||||
break;
|
* "param", "return" or "local"
|
||||||
case 26: // iload_0
|
* @param valueType
|
||||||
case 27: // iload_1
|
* the data type of the parameter
|
||||||
case 28: // iload_2
|
* @throws IOException
|
||||||
case 29: // iload_3
|
* if any I/O error occur
|
||||||
writeLoadStore( true, ValueType.i32, op - 26 );
|
*/
|
||||||
break;
|
protected abstract void writeMethodParam( String kind, ValueType valueType ) throws IOException;
|
||||||
case 59: // istore_0
|
|
||||||
case 60: // istore_1
|
/**
|
||||||
case 61: // istore_2
|
* Complete the method
|
||||||
case 62: // istore_3
|
*
|
||||||
writeLoadStore( false, ValueType.i32, op - 59 );
|
* @param locals
|
||||||
break;
|
* a list with types of local variables
|
||||||
case 96: // iadd
|
*
|
||||||
writeAddInt();
|
* @throws IOException
|
||||||
break;
|
* if any I/O error occur
|
||||||
case 177: // return void
|
*/
|
||||||
writeReturn();
|
protected abstract void writeMethodFinish( List<ValueType> locals ) throws IOException;
|
||||||
break;
|
|
||||||
default:
|
/**
|
||||||
throw new WasmException( "Unimplemented byte code operation: " + op, lineNumber );
|
* Write a chunk of byte code.
|
||||||
}
|
*
|
||||||
}
|
* @param byteCode
|
||||||
} catch( Exception ex ) {
|
* a stream of byte code
|
||||||
throw WasmException.create( ex, lineNumber );
|
* @param lineNumber
|
||||||
}
|
* the current line number
|
||||||
}
|
* @throws WasmException
|
||||||
|
* if some Java code can't converted
|
||||||
/**
|
*/
|
||||||
* Write a constant integer value
|
private void writeCodeChunk( CodeInputStream byteCode, int lineNumber ) throws WasmException {
|
||||||
*
|
try {
|
||||||
* @param value
|
while( byteCode.available() > 0 ) {
|
||||||
* the value
|
int op = byteCode.readUnsignedByte();
|
||||||
* @throws IOException
|
switch( op ) {
|
||||||
* if any I/O error occur
|
case 4: // iconst_1
|
||||||
*/
|
writeConstInt( 1 );
|
||||||
protected abstract void writeConstInt( int value ) throws IOException;
|
break;
|
||||||
|
case 26: // iload_0
|
||||||
private void writeLoadStore( boolean load, @Nonnull ValueType valueType, @Nonnegative int idx ) throws WasmException, IOException {
|
case 27: // iload_1
|
||||||
while( locals.size() <= idx ) {
|
case 28: // iload_2
|
||||||
locals.add( null );
|
case 29: // iload_3
|
||||||
}
|
writeLoadStore( true, ValueType.i32, op - 26 );
|
||||||
ValueType oldType = locals.get( idx );
|
break;
|
||||||
if( oldType != null && oldType != valueType ) {
|
case 59: // istore_0
|
||||||
throw new WasmException( "Redefine local variable type from " + oldType + " to " + valueType, -1 );
|
case 60: // istore_1
|
||||||
}
|
case 61: // istore_2
|
||||||
locals.set( idx, valueType );
|
case 62: // istore_3
|
||||||
if( load ) {
|
writeLoadStore( false, ValueType.i32, op - 59 );
|
||||||
writeLoad( idx );
|
break;
|
||||||
} else {
|
case 96: // iadd
|
||||||
writeStore( idx );
|
writeAddInt();
|
||||||
}
|
break;
|
||||||
}
|
case 177: // return void
|
||||||
|
writeReturn();
|
||||||
/**
|
break;
|
||||||
* Write a variable load.
|
default:
|
||||||
*
|
throw new WasmException( "Unimplemented byte code operation: " + op, lineNumber );
|
||||||
* @param idx
|
}
|
||||||
* the index of the parameter variable
|
}
|
||||||
* @throws IOException
|
} catch( Exception ex ) {
|
||||||
* if any I/O error occur
|
throw WasmException.create( ex, lineNumber );
|
||||||
*/
|
}
|
||||||
protected abstract void writeLoad( int idx ) throws IOException;
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write a variable store.
|
* Write a constant integer value
|
||||||
*
|
*
|
||||||
* @param idx
|
* @param value
|
||||||
* the index of the parameter variable
|
* the value
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* if any I/O error occur
|
* if any I/O error occur
|
||||||
*/
|
*/
|
||||||
protected abstract void writeStore( int idx ) throws IOException;
|
protected abstract void writeConstInt( int value ) throws IOException;
|
||||||
|
|
||||||
/**
|
private void writeLoadStore( boolean load, @Nonnull ValueType valueType, @Nonnegative int idx ) throws WasmException, IOException {
|
||||||
* Write a add operator
|
while( locals.size() <= idx ) {
|
||||||
*
|
locals.add( null );
|
||||||
* @throws IOException
|
}
|
||||||
* if any I/O error occur
|
ValueType oldType = locals.get( idx );
|
||||||
*/
|
if( oldType != null && oldType != valueType ) {
|
||||||
protected abstract void writeAddInt() throws IOException;
|
throw new WasmException( "Redefine local variable type from " + oldType + " to " + valueType, -1 );
|
||||||
|
}
|
||||||
/**
|
locals.set( idx, valueType );
|
||||||
* Write a return
|
if( load ) {
|
||||||
*
|
writeLoad( idx );
|
||||||
* @throws IOException
|
} else {
|
||||||
* if any I/O error occur
|
writeStore( idx );
|
||||||
*/
|
}
|
||||||
protected abstract void writeReturn() throws IOException;
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Write a variable load.
|
||||||
|
*
|
||||||
|
* @param idx
|
||||||
|
* the index of the parameter variable
|
||||||
|
* @throws IOException
|
||||||
|
* if any I/O error occur
|
||||||
|
*/
|
||||||
|
protected abstract void writeLoad( int idx ) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a variable store.
|
||||||
|
*
|
||||||
|
* @param idx
|
||||||
|
* the index of the parameter variable
|
||||||
|
* @throws IOException
|
||||||
|
* if any I/O error occur
|
||||||
|
*/
|
||||||
|
protected abstract void writeStore( int idx ) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a add operator
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* if any I/O error occur
|
||||||
|
*/
|
||||||
|
protected abstract void writeAddInt() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a return
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* if any I/O error occur
|
||||||
|
*/
|
||||||
|
protected abstract void writeReturn() throws IOException;
|
||||||
|
}
|
||||||
|
@ -1,154 +1,163 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Volker Berlin (i-net software)
|
* Copyright 2017 Volker Berlin (i-net software)
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package de.inetsoftware.jwebassembly.text;
|
package de.inetsoftware.jwebassembly.text;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.inetsoftware.jwebassembly.module.ModuleWriter;
|
import de.inetsoftware.jwebassembly.module.ModuleWriter;
|
||||||
import de.inetsoftware.jwebassembly.module.ValueType;
|
import de.inetsoftware.jwebassembly.module.ValueType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module Writer for text format with S-expressions.
|
* Module Writer for text format with S-expressions.
|
||||||
*
|
*
|
||||||
* @author Volker Berlin
|
* @author Volker Berlin
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class TextModuleWriter extends ModuleWriter {
|
public class TextModuleWriter extends ModuleWriter {
|
||||||
|
|
||||||
private Appendable output;
|
private Appendable output;
|
||||||
|
|
||||||
private StringBuilder methodOutput = new StringBuilder();
|
private StringBuilder methodOutput = new StringBuilder();
|
||||||
|
|
||||||
private int inset;
|
private int inset;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new instance.
|
* Create a new instance.
|
||||||
*
|
*
|
||||||
* @param output
|
* @param output
|
||||||
* target for the result
|
* target for the result
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* if any I/O error occur
|
* if any I/O error occur
|
||||||
*/
|
*/
|
||||||
public TextModuleWriter( Appendable output ) throws IOException {
|
public TextModuleWriter( Appendable output ) throws IOException {
|
||||||
this.output = output;
|
this.output = output;
|
||||||
output.append( "(module" );
|
output.append( "(module" );
|
||||||
inset++;
|
inset++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
inset--;
|
inset--;
|
||||||
newline( output );
|
newline( output );
|
||||||
output.append( ')' );
|
output.append( ')' );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void writeMethodStart( String name ) throws IOException {
|
protected void writeExport( String methodName, String exportName ) throws IOException {
|
||||||
newline( output );
|
newline( output );
|
||||||
output.append( "(func $" );
|
output.append( "(export \"" ).append( exportName ).append( "\" (func $" ).append( methodName ).append( "))" );
|
||||||
output.append( name );
|
}
|
||||||
inset++;
|
|
||||||
methodOutput.setLength( 0 );
|
/**
|
||||||
}
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
/**
|
@Override
|
||||||
* {@inheritDoc}
|
protected void writeMethodStart( String name ) throws IOException {
|
||||||
*/
|
newline( output );
|
||||||
@Override
|
output.append( "(func $" );
|
||||||
protected void writeMethodParam( String kind, ValueType valueType ) throws IOException {
|
output.append( name );
|
||||||
output.append( " (" ).append( kind ).append( ' ' ).append( valueType.toString() ).append( ')' );
|
inset++;
|
||||||
}
|
methodOutput.setLength( 0 );
|
||||||
|
}
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
/**
|
||||||
*/
|
* {@inheritDoc}
|
||||||
@Override
|
*/
|
||||||
protected void writeMethodFinish( List<ValueType> locals ) throws IOException {
|
@Override
|
||||||
for( ValueType valueType : locals ) {
|
protected void writeMethodParam( String kind, ValueType valueType ) throws IOException {
|
||||||
output.append( " (local " ).append( valueType.toString() ).append( ')' );
|
output.append( " (" ).append( kind ).append( ' ' ).append( valueType.toString() ).append( ')' );
|
||||||
}
|
}
|
||||||
output.append( methodOutput );
|
|
||||||
inset--;
|
/**
|
||||||
newline( output );
|
* {@inheritDoc}
|
||||||
output.append( ')' );
|
*/
|
||||||
}
|
@Override
|
||||||
|
protected void writeMethodFinish( List<ValueType> locals ) throws IOException {
|
||||||
/**
|
for( ValueType valueType : locals ) {
|
||||||
* {@inheritDoc}
|
output.append( " (local " ).append( valueType.toString() ).append( ')' );
|
||||||
*/
|
}
|
||||||
@Override
|
output.append( methodOutput );
|
||||||
protected void writeConstInt( int value ) throws IOException {
|
inset--;
|
||||||
newline( methodOutput );
|
newline( output );
|
||||||
methodOutput.append( "i32.const " ).append( Integer.toString( value ) );
|
output.append( ')' );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void writeLoad( int idx ) throws IOException {
|
protected void writeConstInt( int value ) throws IOException {
|
||||||
newline( methodOutput );
|
newline( methodOutput );
|
||||||
methodOutput.append( "get_local " ).append( Integer.toString( idx ) );
|
methodOutput.append( "i32.const " ).append( Integer.toString( value ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void writeStore( int idx ) throws IOException {
|
protected void writeLoad( int idx ) throws IOException {
|
||||||
newline( methodOutput );
|
newline( methodOutput );
|
||||||
methodOutput.append( "set_local " ).append( Integer.toString( idx ) );
|
methodOutput.append( "get_local " ).append( Integer.toString( idx ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void writeAddInt() throws IOException {
|
protected void writeStore( int idx ) throws IOException {
|
||||||
newline( methodOutput );
|
newline( methodOutput );
|
||||||
methodOutput.append( "i32.add" );
|
methodOutput.append( "set_local " ).append( Integer.toString( idx ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void writeReturn() throws IOException {
|
protected void writeAddInt() throws IOException {
|
||||||
newline( methodOutput );
|
newline( methodOutput );
|
||||||
methodOutput.append( "return" );
|
methodOutput.append( "i32.add" );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a newline the insets.
|
* {@inheritDoc}
|
||||||
*
|
*/
|
||||||
* @throws IOException
|
@Override
|
||||||
* if any I/O error occur
|
protected void writeReturn() throws IOException {
|
||||||
*/
|
newline( methodOutput );
|
||||||
private void newline( Appendable output ) throws IOException {
|
methodOutput.append( "return" );
|
||||||
output.append( '\n' );
|
}
|
||||||
for( int i = 0; i < inset; i++ ) {
|
|
||||||
output.append( ' ' );
|
/**
|
||||||
output.append( ' ' );
|
* Add a newline the insets.
|
||||||
}
|
*
|
||||||
}
|
* @throws IOException
|
||||||
}
|
* if any I/O error occur
|
||||||
|
*/
|
||||||
|
private void newline( Appendable output ) throws IOException {
|
||||||
|
output.append( '\n' );
|
||||||
|
for( int i = 0; i < inset; i++ ) {
|
||||||
|
output.append( ' ' );
|
||||||
|
output.append( ' ' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user