Following https://github.com/antlr/antlr4/blob/master/doc/parser-rules.md#rule-element-labels is there a way to explicitly add a field to a rule context object?
My use case is a sequence of dots and identifiers:
dotIdentifierSequence
: identifier dotIdentifierSequenceContinuation*
;
dotIdentifierSequenceContinuation
: DOT identifier
;
Often we want to deal with the "full path" of the dotIdentifierSequence structure. Atm this means using DotIdentifierSequenceContext#getText. However, DotIdentifierSequenceContext#getText walks the tree visiting each sub-node collecting the text.
Rule labels as discussed on that doc page would let me do:
dotIdentifierSequence
: i:identifier c+=dotIdentifierSequenceContinuation*
;
and add fields i and c to the DotIdentifierSequenceContext. However to get the full structure's text I'd still have to visit the i node and each c node.
What would be awesome is to be able to define a "full sequence text" String field for both DotIdentifierSequenceContext and DotIdentifierSequenceContinuationContext.
Is that in any way possible today?
The only way I could find to do this was the following:
dotIdentifierSequence
returns [String fullSequenceText]
: (i=identifier { $fullSequenceText = _localctx.i.getText(); }) (c=dotIdentifierSequenceContinuation { $fullSequenceText += _localctx.c.fullSequenceText; })*
;
dotIdentifierSequenceContinuation
returns [String fullSequenceText]
: DOT (i=identifier { $fullSequenceText = "." + _localctx.i.getText(); })
;
Which works, but unfortunately makes the grammar quite unreadable.
Related
I rather have this ugly way of building a string from a list as:
val input = listOf("[A,B]", "[C,D]")
val builder = StringBuilder()
builder.append("Serialized('IDs((")
for (pt in input) {
builder.append(pt[0] + " " + pt[1])
builder.append(", ")
}
builder.append("))')")
The problem is that it adds a comma after the last element and if I want to avoid that I need to add another if check in the loop for the last element.
I wonder if there is a more concise way of doing this in kotlin?
EDIT
End result should be something like:
Serialized('IDs((A B,C D))')
In Kotlin you can use joinToString for this kind of use case (it deals with inserting the separator only between elements).
It is very versatile because it allows to specify a transform function for each element (in addition to the more classic separator, prefix, postfix). This makes it equivalent to mapping all elements to strings and then joining them together, but in one single call.
If input really is a List<List<String>> like you mention in the title and you assume in your loop, you can use:
input.joinToString(
prefix = "Serialized('IDs((",
postfix = "))')",
separator = ", ",
) { (x, y) -> "$x $y" }
Note that the syntax with (x, y) is a destructuring syntax that automatically gets the first and second element of the lists inside your list (parentheses are important).
If your input is in fact a List<String> as in listOf("[A,B]", "[C,D]") that you wrote at the top of your code, you can instead use:
input.joinToString(
prefix = "Serialized('IDs((",
postfix = "))')",
separator = ", ",
) { it.removeSurrounding("[", "]").replace(",", " ") }
val input = listOf("[A,B]", "[C,D]")
val result =
"Serialized('IDs((" +
input.joinToString(",") { it.removeSurrounding("[", "]").replace(",", " ") } +
"))')"
println(result) // Output: Serialized('IDs((A B,C D))')
Kotlin provides an extension function [joinToString][1] (in Iterable) for this type of purpose.
input.joinToString(",", "Serialized('IDs((", "))')")
This will correctly add the separator.
I write (own lang -> JS) transpiler, using ANTLR (javascript target using visitor).
Focus is on variations, in target code generation.
An earlier SO post, described solution to a simpler situation. This one being different, primarily due to recursion being involved.
Grammar:
grammar Alang;
...
variableDeclaration : DECL (varDecBl1 | varDecBl2) EOS;
varDecBl1 : ID AS TYP;
varDecBl2 : CMP ID US (SGST varDecBl1+ SGFN);
...
DECL : 'var-declare' ;
EOS : ';' ;
SGST : 'segment-start:' ;
SGFN : 'segment-finish' ;
AS : 'as';
CMP : 'cmp';
US : 'using';
TYP
: 'str'
| 'int'
| 'bool'
;
ID : [a-zA-Z0-9_]+ ;
Two different cases of source code needs to be handled differently.
Source code 1:
var-declare fooString as str;
target of which needs to come out as: var fooString;
Source code 2:
var-declare cmp barComplex using
segment-start:
barF1 as int
barF2 as bool
barF3 as str
segment-finish;
target of this one has to be: var barComplex = new Map();
(as simple var declaration can't handle value type)
code generation done using:
visitVarDecBl1 = function(ctx) {
this.targetCode += `var ${ctx.getChild(0).getText()};`;
return this.visitChildren(ctx);
};
....
visitVarDecBl2 = function(ctx) {
this.targetCode += `var ${ctx.getChild(1).getText()} = new Map();`;
return this.visitChildren(ctx);
};
(targetCode consolidates the target code)
Above works for case 1.
For case 2, it ends up going beyond var barComplex = new Map(), due to recursive use of rule varDecBl1, which invokes visitVarDecBl1 code gen implement again.
Wrong outcome:
var barComplex = new Map();
var barF1; // <---- wrong one coming from visitVarDecBl1
var barF2; // <---- wrong
var barF3; // <---- wrong
To deal with this, one approach that I want to try, is to make visitVarDecBl1 conditional to parent ctx.
If parent = variableDeclaration, target code = var ${ctx.getChild(0).getText()};.
If parent = varDecBl2, skip generation of code.
But I can't find invoking rule within ctx payload, that I can string compare.
Using something like ctx.parentCtx gives me [378 371 204 196 178 168] (hashes?).
Inputs welcome. (including proposal of a better approach, if any)
In case anyone comes looking with similar situation at hand, I post the answer myself.
I dealt with it by extracting parent rule myself.
util = require("util");
...
// parentCtx object -> string
const parentCtxStr = util.inspect(ctx.parentCtx);
// snip out parent rule from payload, e.g.: 'varDecBl2 {…, ruleIndex: 16, …}'
const snip = parentCtxStr.slice(0,parentCtxStr.indexOf("{")-1); // extracts varDecBl2 for you
}
Now use value of snip to deal with as described above.
Of course, this way establishes a coupling to how payload is structured. And risk breaking in future if that is changed.
I'd rather use same stuff through the API, though I couldn't find it.
I want to add a certain value to a list. Both are inside my ELM-model:
type alias Model =
{ syllables : List Syllable
, words : List Word
, newSyllable : String
, newWord : String
}
I want to add the newSyllable value to the list of syllables, when I click the button.
I placed this attribute inside my view:
onClick TransferSyllable
Everything works right, but I wonder how I can transfer one value of my model into the list of values!?
Thanks.
EDIT:
This is my definition of "Syllable":
type alias Syllable =
{ content : String
, start : Bool
, mid : Bool
, end : Bool
}
I want to insert the value to the end of the list.
Since Syllable is a record type with four fields, and newSyllable is just a string, you'll need a function that turns a String into a Syllable. I'll assume that function has the signature:
makeSyllable : String -> Syllable
Adding the syllable onto the end of the list can be done using List.append. Since append takes a List a, you'll need to add brackets around newSyllable when passing it to append:
{ model | syllables = List.append model.syllables [ makeSyllable model.newSyllable ] }
I'm using VTD-XML to update XML files. In this I am trying to get a flexible way of maintaining attributes on an element. So if my original element is:
<MyElement name="myName" existattr="orig" />
I'd like to be able to update it to this:
<MyElement name="myName" existattr="new" newattr="newValue" />
I'm using a Map to manage the attribute/value pairs in my code and when I update the XML I'm doing something like the following:
private XMLModifier xm = new XMLModifier();
xm.bind(vn);
for (String key : attr.keySet()) {
int i = vn.getAttrVal(key);
if (i!=-1) {
xm.updateToken(i, attr.get(key));
} else {
xm.insertAttribute(key+"='"+attr.get(key)+"'");
}
}
vn = xm.outputAndReparse();
This works for updating existing attributes, however when the attribute doesn't already exist, it hits the insert (insertAttribute) and I get "ModifyException"
com.ximpleware.ModifyException: There can be only one insert per offset
at com.ximpleware.XMLModifier.insertBytesAt(XMLModifier.java:341)
at com.ximpleware.XMLModifier.insertAttribute(XMLModifier.java:1833)
My guess is that as I'm not manipulating the offset directly this might be expected. However I can see no function to insert an an attribute at a position in the element (at end).
My suspicion is that I will need to do it at the "offset" level using something like xm.insertBytesAt(int offset, byte[] content) - as this is an area I have needed to get into yet is there a way to calculate the offset at which I can insert (just before the end of the tag)?
Of course I may be mis-using VTD in some way here - if there is a better way of achieving this then happy to be directed.
Thanks
That's an interesting limitation of the API I hadn't encountered yet. It would be great if vtd-xml-author could elaborate on technical details and why this limitation exists.
As a solution to your problem, a simple approach would be to accumulate your key-value pairs to be inserted as a String, and then to insert them in a single call after your for loop has terminated.
I've tested that this works as per your code:
private XMLModifier xm_ = new XMLModifier();
xm.bind(vn);
String insertedAttributes = "";
for (String key : attr.keySet()) {
int i = vn.getAttrVal(key);
if (i!=-1) {
xm.updateToken(i, attr.get(key));
} else {
// Store the key-values to be inserted as attributes
insertedAttributes += " " + key + "='" + attr.get(key) + "'";
}
}
if (!insertedAttributes.equals("")) {
// Insert attributes only once
xm.insertAttribute(insertedAttributes);
}
This will also work if you need to update the attributes of multiple elements, simply nest the above code in while(autoPilot.evalXPath() != -1) and be sure to set insertedAttributes = ""; at the end of each while loop.
Hope this helps.
I may have missed this, but is there a built-in method for serializing/deserializing lua tables to text files and vice versa?
I had a pair of methods in place to do this on a lua table with fixed format (e.g. 3 columns of data with 5 rows).
Is there a way to do this on lua tables with any arbitrary format?
For an example, given this lua table:
local scenes={
{name="scnSplash",
obj={
{
name="bg",
type="background",
path="scnSplash_bg.png",
},
{
name="bird",
type="image",
path="scnSplash_bird.png",
x=0,
y=682,
},
}
},
}
It would be converted into text like this:
{name="scnSplash",obj={{name="bg",type="background",path="scnSplash_bg.png",},{name="bird", type="image",path="scnSplash_bird.png",x=0,y=682,}},}
The format of the serialized text can be defined in any way, as long as the text string can be deserialized into an empty lua table.
I'm not sure why JSON library was marked as the right answer as it seems to be very limited in serializing "lua tables with any arbitrary format". It doesn't handle boolean/table/function values as keys and doesn't handle circular references. Shared references are not serialized as shared and math.huge values are not serialized correctly on Windows. I realize that most of these are JSON limitations (and hence implemented this way in the library), but this was proposed as a solution for generic Lua table serialization (which it is not).
One would be better off by using one of the implementations from TableSerialization page or my Serpent serializer and pretty-printer.
Lua alone doesn't have any such builtin, but implementing one is not difficult. A number of prebaked implementations are listed here: http://lua-users.org/wiki/TableSerialization
require "json"
local t = json.decode( jsonFile( "sample.json" ) )
reference here for a simple json serializer.
Add json.lua from rxi/json.lua to your project, then use it with:
local json = require("json")
local encoded = json.encode({
name = "J. Doe",
age = 42
})
local decoded = json.decode(encoded)
print(decoded.name)
Note that the code chokes if there are functions in the value you are trying to serialize. You have to fix line 82 and 93 in the code to skip values that have the function type.
Small solution: The key can be done without brackets, but be sure that here is no minuses or other special symbols.
local nl = string.char(10) -- newline
function serialize_list (tabl, indent)
indent = indent and (indent.." ") or ""
local str = ''
str = str .. indent.."{"..nl
for key, value in pairs (tabl) do
local pr = (type(key)=="string") and ('["'..key..'"]=') or ""
if type (value) == "table" then
str = str..pr..serialize_list (value, indent)
elseif type (value) == "string" then
str = str..indent..pr..'"'..tostring(value)..'",'..nl
else
str = str..indent..pr..tostring(value)..','..nl
end
end
str = str .. indent.."},"..nl
return str
end
local str = serialize_list(tables)
print(str)