next step for source map #6

This commit is contained in:
Volker Berlin 2019-03-31 10:39:59 +02:00
parent e5036cc053
commit 0b1ff00ae5
7 changed files with 465 additions and 14 deletions

View File

@ -34,6 +34,7 @@ import de.inetsoftware.jwebassembly.module.FunctionName;
import de.inetsoftware.jwebassembly.module.ModuleWriter;
import de.inetsoftware.jwebassembly.module.ValueTypeConvertion;
import de.inetsoftware.jwebassembly.module.WasmTarget;
import de.inetsoftware.jwebassembly.sourcemap.SourceMapWriter;
import de.inetsoftware.jwebassembly.wasm.AnyType;
import de.inetsoftware.jwebassembly.wasm.ArrayOperator;
import de.inetsoftware.jwebassembly.wasm.NamedStorageType;
@ -82,6 +83,8 @@ public class BinaryModuleWriter extends ModuleWriter implements InstructionOpcod
private int exceptionSignatureIndex = -1;
private String javaSourceFile;
/**
* Create new instance.
*
@ -202,12 +205,16 @@ public class BinaryModuleWriter extends ModuleWriter implements InstructionOpcod
if( size == 0 ) {
return;
}
SourceMapWriter sourceMap = new SourceMapWriter();
WasmOutputStream stream = new WasmOutputStream();
stream.writeVaruint32( size );
for( Function func : functions.values() ) {
func.functionsStream.writeTo( stream );
}
wasm.writeSection( SectionType.Code, stream );
if( createSourceMap ) {
sourceMap.generate( target.getSourceMapOutput() );
}
}
/**
@ -358,9 +365,7 @@ public class BinaryModuleWriter extends ModuleWriter implements InstructionOpcod
@Override
protected void writeMethodStart( FunctionName name, String sourceFile ) throws IOException {
function = getFunction( name );
if( createSourceMap ) {
function.sourceFile = sourceFile;
}
this.javaSourceFile = sourceFile;
functionType = new FunctionTypeEntry();
codeStream.reset();
locals.clear();
@ -409,7 +414,7 @@ public class BinaryModuleWriter extends ModuleWriter implements InstructionOpcod
@Override
protected void markCodePosition( int javaCodePosition ) {
if( createSourceMap ) {
function.markCodePosition( codeStream.size(), javaCodePosition );
function.markCodePosition( codeStream.size(), javaCodePosition, javaSourceFile );
}
}

View File

@ -16,8 +16,11 @@
package de.inetsoftware.jwebassembly.binary;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import de.inetsoftware.jwebassembly.sourcemap.SourceMapping;
/**
* An entry in the function section of the WebAssembly.
*
@ -25,15 +28,15 @@ import java.util.List;
*/
class Function extends SectionEntry {
int id;
int id;
int typeId;
int typeId;
List<String> paramNames;
List<String> paramNames;
WasmOutputStream functionsStream;
WasmOutputStream functionsStream;
String sourceFile;
ArrayList<SourceMapping> sourceMappings;
/**
* {@inheritDoc}
@ -48,12 +51,15 @@ class Function extends SectionEntry {
*
* @param streamPosition
* the position in the function stream
* @param javaCodePosition
* @param javaSourceLine
* the position in the Java Source file
* @param sourceFileName
* the name of the Java source file
*/
void markCodePosition( int streamPosition, int javaCodePosition ) {
if( sourceFile != null ) {
// TODO Auto-generated method stub
void markCodePosition( int streamPosition, int javaSourceLine, String sourceFileName ) {
if( sourceMappings == null ) {
sourceMappings = new ArrayList<>();
}
sourceMappings.add( new SourceMapping( streamPosition, javaSourceLine, sourceFileName ) );
}
}

View File

@ -21,6 +21,9 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import javax.annotation.Nonnull;
@ -33,6 +36,8 @@ public class WasmTarget implements Closeable {
private OutputStream output;
private Writer sourceMap;
/**
* Create a target with a file.
*
@ -68,6 +73,21 @@ public class WasmTarget implements Closeable {
return output;
}
/**
* Get the source map OutputStream
*
* @return the stream
* @throws IOException
* if any I/O error occur
*/
@Nonnull
public Writer getSourceMapOutput() throws IOException {
if( sourceMap == null && file != null ) {
sourceMap = new OutputStreamWriter( new BufferedOutputStream( new FileOutputStream( file + ".map" ) ), StandardCharsets.UTF_8 );
}
return sourceMap;
}
/**
* Close all streams
*
@ -76,6 +96,11 @@ public class WasmTarget implements Closeable {
*/
@Override
public void close() throws IOException {
output.close();
if( output != null ) {
output.close();
}
if( sourceMap != null ) {
sourceMap.close();
}
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright 2019 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.sourcemap;
import java.io.IOException;
/**
* Encode an integer value as Base64VLQ
*/
class Base64VLQ {
private static final int VLQ_BASE_SHIFT = 5;
private static final int VLQ_BASE = 1 << VLQ_BASE_SHIFT;
private static final int VLQ_BASE_MASK = VLQ_BASE - 1;
private static final int VLQ_CONTINUATION_BIT = VLQ_BASE;
private static final String BASE64_MAP = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/**
* no instance
*/
private Base64VLQ() {
// nothing
}
/**
* Move the signet bit from the first position (two-complement value) to the last bit position.
*
* examples: 1 -> 2; -1 -> 3; 2 -> 4; -2 -> 5
*
* @param value
* two-complement value
* @return converted value
*/
private static int toVLQSigned( int value ) {
return (value < 0) ? (((-value) << 1) + 1) : ((value << 1) + 0);
}
/**
* Writes a VLQ encoded value to the provide target.
*
* @param out
* the target
* @param value
* the value
* @throws IOException
* if any I/O error occur
*/
static void appendBase64VLQ( Appendable out, int value ) throws IOException {
value = toVLQSigned( value );
do {
int digit = value & VLQ_BASE_MASK;
value >>>= VLQ_BASE_SHIFT;
if( value > 0 ) {
digit |= VLQ_CONTINUATION_BIT;
}
out.append( BASE64_MAP.charAt( digit ) );
} while( value > 0 );
}
}

View File

@ -0,0 +1,240 @@
/*
* Copyright 2019 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.sourcemap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
/**
* Generates Source Map version 3.
*
* https://sourcemaps.info/spec.html
*/
public class SourceMapWriter {
private List<SourceMapping> mappings = new ArrayList<>();
private LinkedHashMap<String, Integer> sourceFileNames = new LinkedHashMap<String, Integer>();
private int nextSourceFileNameIndex;
/**
* Adds a mapping for the given node. Mappings must be added in order.
*
* @param mapping
* the mapping
*/
public void addMapping( SourceMapping mapping ) {
if( !sourceFileNames.containsKey( mapping.getSourceFileName() ) ) {
sourceFileNames.put( mapping.getSourceFileName(), nextSourceFileNameIndex );
nextSourceFileNameIndex++;
}
mappings.add( mapping );
}
/**
* https://sourcemaps.info/spec.html
*
* @param out
* the target
* @throws IOException
* if any I/O error occur
*/
public void generate( Appendable out ) throws IOException {
out.append( "{\n" );
appendJsonField( out, "version", "3" );
// the source file names
out.append( ",\n" );
appendJsonField( out, "sources", "[" );
appendSourceFileNames( out );
out.append( "]" );
// WebAssembly does not have symbol names
out.append( ",\n" );
appendJsonField( out, "names", "[]" );
// generate the mappings
out.append( ",\n" );
appendJsonField( out, "mappings", "" );
(new Generator( out )).appendLineMappings();
out.append( "\n}" );
}
/**
* Write source file names.
*
* @param out
* the target
* @throws IOException
* if any I/O error occur
*/
private void appendSourceFileNames( Appendable out ) throws IOException {
boolean isFirst = true;
for( Entry<String, Integer> entry : sourceFileNames.entrySet() ) {
String key = entry.getKey();
if( isFirst ) {
isFirst = false;
} else {
out.append( ',' );
}
appendQuoteString( out, key );
}
}
/**
* Write the field name to JSON source map.
*
* @param out
* the target
* @param name
* the field name
* @param value
* optional value
* @throws IOException
* if any I/O error occur
*/
private static void appendJsonField( Appendable out, String name, CharSequence value ) throws IOException {
out.append( '\"' );
out.append( name );
out.append( "\":" );
out.append( value );
}
/**
* Write a quoted string to the JSON.
*
* @param out
* the target
* @param str
* the unquoted string
* @throws IOException
* if any I/O error occur
*/
private static void appendQuoteString( Appendable out, String str ) throws IOException {
out.append( '"' );
for( int i = 0; i < str.length(); i++ ) {
char ch = str.charAt( i );
switch( ch ) {
case '\\':
case '\"':
out.append( '\\' );
break;
default:
if( ch <= 0x1f ) {
out.append( "\\u" );
out.append( Character.forDigit( (ch >> 12) & 0xF, 16 ) );
out.append( Character.forDigit( (ch >> 8) & 0xF, 16 ) );
out.append( Character.forDigit( (ch >> 4) & 0xF, 16 ) );
out.append( Character.forDigit( ch & 0xF, 16 ) );
continue;
}
}
out.append( ch );
}
out.append( '\"' );
}
/**
* The generator of the source map
*/
private class Generator {
private final Appendable out;
private int previousLine = -1;
private int previousColumn;
private int previousSourceFileNameId;
private int previousSourceLine;
private int previousSourceColumn;
/**
* Create an instance.
*
* @param out
* the target for the source map
*/
Generator( Appendable out ) {
this.out = out;
}
/**
* Append the mappings to the source map.
*
* @throws IOException
* if any I/O error occur
*/
void appendLineMappings() throws IOException {
out.append( '\"' );
for( SourceMapping mapping : mappings ) {
int generatedLine = 1; // ever 1 for WebAssembly
int generatedColumn = mapping.getGeneratedColumn();
if( generatedLine > 0 && previousLine != generatedLine ) {
int start = previousLine == -1 ? 0 : previousLine;
for( int i = start; i < generatedLine; i++ ) {
out.append( ';' );
}
}
if( previousLine != generatedLine ) {
previousColumn = 0;
} else {
out.append( ',' );
}
writeEntry( mapping );
previousLine = generatedLine;
previousColumn = generatedColumn;
}
out.append( ";\"" );
}
/**
* Write a single single mapping to the source map.
*
* @param mapping
* the mapping
* @throws IOException
* if any I/O error occur
*/
void writeEntry( SourceMapping mapping ) throws IOException {
int column = mapping.getGeneratedColumn();
Base64VLQ.appendBase64VLQ( out, column - previousColumn );
previousColumn = column;
int sourceId = sourceFileNames.get( mapping.getSourceFileName() );
Base64VLQ.appendBase64VLQ( out, sourceId - previousSourceFileNameId );
previousSourceFileNameId = sourceId;
int srcline = mapping.getSourceLine();
int srcColumn = 0; // ever 0 for Java byte code because the line table does not support columns
Base64VLQ.appendBase64VLQ( out, srcline - previousSourceLine );
previousSourceLine = srcline;
Base64VLQ.appendBase64VLQ( out, srcColumn - previousSourceColumn );
previousSourceColumn = srcColumn;
}
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2019 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.sourcemap;
/**
* Mapping for Source Map.
*/
public class SourceMapping {
private final int generatedColumn;
private final int sourceLine;
private final String sourceFileName;
/**
* Create a mapping between a Java code line and a WebAssembly code position
*
* @param generatedColumn
* position in WebAssembly
* @param sourceLine
* Java source line
* @param sourceFileName
* Java source file
*/
public SourceMapping( int generatedColumn, int sourceLine, String sourceFileName ) {
this.generatedColumn = generatedColumn;
this.sourceLine = sourceLine;
this.sourceFileName = sourceFileName;
}
/**
* The generated column. This is equals to the binary offset in the *.wasm file
*
* @return binary offset
*/
int getGeneratedColumn() {
return generatedColumn;
}
/**
* The source line
*
* @return the line number
*/
int getSourceLine() {
return sourceLine;
}
/**
* Source file name
*
* @return the name
*/
String getSourceFileName() {
return sourceFileName;
}
}

View File

@ -0,0 +1,30 @@
package de.inetsoftware.jwebassembly.sourcemap;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import org.junit.Test;
public class SourceMapWriterTest {
@Test
public void simple() throws IOException {
SourceMapWriter map = new SourceMapWriter();
map.addMapping( new SourceMapping( 0, 0, "Test1.java" ) );
map.addMapping( new SourceMapping( 5, 1, "Test1.java" ) );
map.addMapping( new SourceMapping( 0, 4, "Test2.java" ) );
map.addMapping( new SourceMapping( 5, 9, "Test2.java" ) );
StringBuilder generate = new StringBuilder();
map.generate( generate );
String expected = "{\n" +
"\"version\":3,\n" +
"\"sources\":[\"Test1.java\",\"Test2.java\"],\n" +
"\"names\":[],\n" +
"\"mappings\":\";AAAA,KACA,LCGA,KAKA;\"\n" +
"}";
assertEquals( expected, generate.toString() );
}
}