in a Parser Grammar I'd like to define several variables in the
locals clause.
A simplified example looks like this:
body
locals [
Map<String, String> bodyContent = new HashMap<String, String>();
long counter = 0;
]
: BODY_CAPTION NEWLINE line ;
line : key='MyKey' TAB value='MyValue' NEWLINE
{
$body::bodyContent.put($key.text, $value.text);
$body::counter++;
} ;
This gives the error:
unknown attribute 'counter' for rule 'body' in '$body::counter'
If I swap the lines in the locals clause like this
locals [
long counter = 0;
Map<String, String> bodyContent = new HashMap<String, String>();
]
it gives the error:
unknown attribute 'bodyContent' for rule 'body' in '$body::bodyContent'
Apparently, ANTLR recognizes only the first local variable definition in the locals clause.
Is there a way, to define multiple local variables under locals?
Yes, but they are comma separated like the parameter list and returns clause.
locals [
Map<String, String> bodyContent = new HashMap<String, String>(),
long counter = 0
]
Related
I want to define synonym words related to a particular domain in Lucene 8*. I have a list of synonyms in CSV format. I didn't see any sample code of example for this. I only saw example for older version which doesn't work now.
Here is a simple example of using synonyms in Lucene 8 (tested using 8.7.0).
Here is an example analyzer:
boolean ignoreSynonymCase = Boolean.TRUE;
Analyzer analyzer = new Analyzer() {
#Override
protected Analyzer.TokenStreamComponents createComponents(String fieldName) {
Tokenizer source = new StandardTokenizer();
TokenStream tokenStream = source;
tokenStream = new LowerCaseFilter(tokenStream);
tokenStream = new ASCIIFoldingFilter(tokenStream);
tokenStream = new SynonymGraphFilter(tokenStream, getSynonyms(), ignoreSynonymCase);
tokenStream = new FlattenGraphFilter(tokenStream);
return new Analyzer.TokenStreamComponents(source, tokenStream);
}
};
It uses a SynonymGraphFilter to handle your synonyms, which need to be added to a SynonymMap (see below for that).
Note the use of FlattenGraphFilter in the above example - which is needed during indexing as described in the synonym filter javadoc:
However, if you use this during indexing, you must follow it with FlattenGraphFilter to squash tokens on top of one another like SynonymFilter, because the indexer can't directly consume a graph.
My getSynonyms() method is as follows:
private static SynonymMap getSynonyms() {
// de-duplicate rules when loading:
boolean dedup = Boolean.TRUE;
// include original word in index:
boolean includeOrig = Boolean.TRUE;
SynonymMap.Builder builder = new SynonymMap.Builder(dedup);
// examples of single synonyms:
builder.add(new CharsRef("can't"), new CharsRef("cannot"), includeOrig);
builder.add(new CharsRef("what's"), new CharsRef("what is"), includeOrig);
// example with multiple synonyms:
CharsRefBuilder multiWordCharsRef = new CharsRefBuilder();
SynonymMap.Builder.join(new String[]{"do not", "does not"}, multiWordCharsRef);
builder.add(new CharsRef("don't"), multiWordCharsRef.get(), includeOrig);
SynonymMap synonymMap = null;
try {
synonymMap = builder.build();
} catch (IOException ex) {
System.err.print(ex);
}
return synonymMap;
}
So, for example, it treats cannot as a synonym for can't. And you can therefore search for cannot successfully in a phrase such as This can't be done!.
How you load your synonyms from your source CSV file is up to you - for example, you can call builder.add() in a loop.
In python, I often use strings as templates, e.g.
templateUrl = '{host}/api/v3/{container}/{resourceid}'
params = {'host': 'www.api.com', 'container': 'books', 'resourceid': 10}
api.get(templateUrl.format(**params))
This allows for easy base class setup and the like. How can I do the same in dart?
I'm assuming I will need to create a utility function to parse the template and substitute manually but really hoping there is something ready to use.
Perhaps a TemplateString class with a format method that takes a Map of name/value pairs to substitute into the string.
Note: the objective is to have a generic "format" or "interpolation" function that doesn't need to know in advance what tags or names will exist in the template.
Further clarification: the templates themselves are not resolved when they are set up. Specifically, the template is defined in one place in the code and then used in many other places.
Dart does not have a generic template string functionality that would allow you to insert values into your template at runtime.
Dart only allows you to interpolate strings with variables using the $ syntax in strings, e.g. var string = '$domain/api/v3/${actions.get}'. You would need to have all the variables defined in your code beforehand.
However, you can easily create your own implementation.
Implementation
You pretty much explained how to do it in your question yourself: you pass a map and use it to have generic access to the parameters using the [] operator.
To convert the template string into something that is easy to access, I would simply create another List containing fixed components, like /api/v3/ and another Map that holds generic components with their name and their position in the template string.
class TemplateString {
final List<String> fixedComponents;
final Map<int, String> genericComponents;
int totalComponents;
TemplateString(String template)
: fixedComponents = <String>[],
genericComponents = <int, String>{},
totalComponents = 0 {
final List<String> components = template.split('{');
for (String component in components) {
if (component == '') continue; // If the template starts with "{", skip the first element.
final split = component.split('}');
if (split.length != 1) {
// The condition allows for template strings without parameters.
genericComponents[totalComponents] = split.first;
totalComponents++;
}
if (split.last != '') {
fixedComponents.add(split.last);
totalComponents++;
}
}
}
String format(Map<String, dynamic> params) {
String result = '';
int fixedComponent = 0;
for (int i = 0; i < totalComponents; i++) {
if (genericComponents.containsKey(i)) {
result += '${params[genericComponents[i]]}';
continue;
}
result += fixedComponents[fixedComponent++];
}
return result;
}
}
Here would be an example usage, I hope that the result is what you expected:
main() {
final templateUrl = TemplateString('{host}/api/v3/{container}/{resourceid}');
final params = <String, dynamic>{'host': 'www.api.com', 'container': 'books', 'resourceid': 10};
print(templateUrl.format(params)); // www.api.com/api/v3/books/10
}
Here it is as a Gist.
Here is my solution:
extension StringFormating on String {
String format(List<String> values) {
int index = 0;
return replaceAllMapped(new RegExp(r'{.*?}'), (_) {
final value = values[index];
index++;
return value;
});
}
String formatWithMap(Map<String, String> mappedValues) {
return replaceAllMapped(new RegExp(r'{(.*?)}'), (match) {
final mapped = mappedValues[match[1]];
if (mapped == null)
throw ArgumentError(
'$mappedValues does not contain the key "${match[1]}"');
return mapped;
});
}
}
This gives you a very similar functionality to what python offers:
"Test {} with {}!".format(["it", "foo"]);
"Test {a} with {b}!".formatWithMap({"a": "it", "b": "foo"})
both return "Test it with foo!"
It's even more easy in Dart. Sample code below :
String host = "www.api.com"
String container = "books"
int resourceId = 10
String templateUrl = "$host/api/v3/$container/${resourceId.toString()}"
With the map, you can do as follows :
Map<String, String> params = {'host': 'www.api.com', 'container': 'books', 'resourceid': 10}
String templateUrl = "${params['host']}/api/v3/${params['container']}/${params['resourceId']}"
Note : The above code defines Map as <String, String>. You might want <String, Dynamic> (and use .toString())
Wouldn't it be simplest to just make it a function with named arguments? You could add some input validation if you wanted to.
String templateUrl({String host = "", String container = "", int resourceid = 0 }) {
return "$host/api/v3/$container/$resourceId";
}
void main() {
api.get(templateUrl(host:"www.api.com", container:"books", resourceid:10));
}
I wish to access outside variables for a test function that I am writing, in Groovy.
However, it doesn't seem that I can.
My code is like this:
Map<String, String> originalTableRowState = new HashMap<String, String>(),
newTableRowState
// if there is table data to get, and do actions on
def WebDriver driver = DriverFactory.getWebDriver()
def List<WebElement> dataRows = driver.findElements(
By.cssSelector('div.tab-pane.active .dataTables_scrollBody tbody tr:not(.dataTables_empty)'))
'if there\'s table data, this test should run'
if (dataRows.size() > 0) {
WebUI.comment('populate the tableRowState with the data from the first table row')
fetchFirstRowDataInto(originalTableRowState)
}
void fetchFirstRowDataInto(Map<String, String> tableRowState) {
List<WebElement> tableHeadings = driver.findElements(
By.cssSelector('div.tab-pane.active .dataTables_scrollHead th'))
WebElement firstRow = dataRows.get(0)
List<WebElement> dataCells = firstRow.findElements(
By.xpath('//td[not(#class="dataTables_empty") and not(*)]'))
for (int i = 0; i < dataCells.size(); i++) {
// save data to originalTableRowState with the table header text as the key
tableRowState.put(tableHeadings.get(i), dataCells.get(i))
}
}
and when I run it, it greets me with the error saying that Variable 'driver' is not defined outside test case. I just added the def keywords to the driver,dataRows definintions.
How to make driver,dataRows accessible inside functions, without passing them in as parameters?
I fixed the method variable-access issue by declaring it a JS-like closure:
/* change void fetchFirstRowDataInto(Map<String, String> tableRowState) { to def fetchFirstRowDataInto = { Map<String, String> tableRowState -> */
and putting the definition above the invocation.
I welcome any better solutions...
I'm serializing an object with YamlDotNet with both reference and value types. What i'm looking to accomplish is that my integer values of zero remain in the outputted yaml, but null values would be discarded. EmitDefaults looks to discard '0' for numeric values. i understand null is the default value for reference types. Json.Net solved this with breaking it out into the following properties:
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore,
is there any way to accomplish the below?
class foo
{
int index {get;set;}
string bar {get;set;}
}
new foo { index =0; bar = null }
would yield the following yaml:
index: 0
new foo { index =0; bar = "bar" }
would yield the following yaml:
index: 0
bar: bar
Thanks
Not sure this is what you want, but this is how I force all default values to be serialized:
public override string ToString()
{
var builder = new SerializerBuilder();
builder.EmitDefaults(); // Force even default values to be written, like 0, false.
var serializer = builder.Build();
var strWriter = new StringWriter();
serializer.Serialize(strWriter, this);
return strWriter.ToString();
}
I am trying to send binary data, i.e. byte arrays, using yaml. According to the yaml documentation, Yaml Binary Type, this is supported. On the Java side I use SnakeYaml and if a value of byte[] is passed, then the yaml correctly gives !!binary.
This functionality does not seem to be supported "out of the box" in YamlDotNet. The below code snippet creates a sequence of integer values:
IDictionary<string, object> data = new Dictionary<string, object>();
const string value = ":| value: <XML> /n\n C:\\cat";
byte[] bytes = Encoding.UTF8.GetBytes(value);
data.Add(ValueKey, bytes);
// Turn the object representation into text
using (var output = new StringWriter())
{
var serializer = new Serializer();
serializer.Serialize(output, data);
return output.ToString();
}
Output like:
val:\r- 58\r- 124\r- 32\r- 118\r- 97\r- 108\r- 117\r- 101\r- 58\r- 32\r- 60\r- 88\r- 77\r- 76\r- 62\r- 32\r- 47\r- 110\r- 10\r- 32\r- 67\r- 58\r- 92\r- 99\r- 97\r- 116\r
But I would like something more like:
val: !!binary |-
OnwgdmFsdWU6IDxYTUw+IC9uCiBDOlxjYXQ=
Can anyone recommend a workaround?
The preferred way to add support for custom types is to use a custom IYamlTypeConverter. A possible implementation for the !!binary type would be:
public class ByteArrayConverter : IYamlTypeConverter
{
public bool Accepts(Type type)
{
// Return true to indicate that this converter is able to handle the byte[] type
return type == typeof(byte[]);
}
public object ReadYaml(IParser parser, Type type)
{
var scalar = (YamlDotNet.Core.Events.Scalar)parser.Current;
var bytes = Convert.FromBase64String(scalar.Value);
parser.MoveNext();
return bytes;
}
public void WriteYaml(IEmitter emitter, object value, Type type)
{
var bytes = (byte[])value;
emitter.Emit(new YamlDotNet.Core.Events.Scalar(
null,
"tag:yaml.org,2002:binary",
Convert.ToBase64String(bytes),
ScalarStyle.Plain,
false,
false
));
}
}
To use the converter in the Serializer, you simply need to register it using the following code:
var serializer = new Serializer();
serializer.RegisterTypeConverter(new ByteArrayConverter());
For the Deserializer, you also need to register the converter, but you also need to add a tag mapping to resolve the !!binary tag to the byte[] type:
var deserializer = new Deserializer();
deserializer.RegisterTagMapping("tag:yaml.org,2002:binary", typeof(byte[]));
deserializer.RegisterTypeConverter(new ByteArrayConverter());
A fully working example can be tried here
For anyone that's interested.... I fixed this by creating the string myself and adding the !!binary tag, and also doing some clean up. Below is the code.
ToYaml:
IDictionary<string, string> data = new Dictionary<string, string>();
string byteAsBase64Fromat = Convert.ToBase64String("The string to convert");
byteAsBase64Fromat = "!!binary |-\n" + byteAsBase64Fromat + "\n";
data.Add(ValueKey, byteAsBase64Fromat);
string yaml;
using (var output = new StringWriter())
{
var serializer = new Serializer();
serializer.Serialize(output, data);
yaml = output.ToString();
}
string yaml = yaml.Replace(">", "");
return yaml.Replace(Environment.NewLine + Environment.NewLine, Environment.NewLine);
And then back by:
string binaryText = ((YamlScalarNode)data.Children[new YamlScalarNode(ValueKey)]).Value
String value = Convert.FromBase64String(binaryText);