mirror of
https://github.com/i-net-software/JWebAssembly.git
synced 2025-03-15 02:44:47 +01:00
next step for source map #6
This commit is contained in:
parent
e5036cc053
commit
0b1ff00ae5
@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 ) );
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
75
src/de/inetsoftware/jwebassembly/sourcemap/Base64VLQ.java
Normal file
75
src/de/inetsoftware/jwebassembly/sourcemap/Base64VLQ.java
Normal 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 );
|
||||
}
|
||||
}
|
240
src/de/inetsoftware/jwebassembly/sourcemap/SourceMapWriter.java
Normal file
240
src/de/inetsoftware/jwebassembly/sourcemap/SourceMapWriter.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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() );
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user