Augmented Abstract Syntax Tree - abstract-syntax-tree

Here is a simple grammar:
START = DECL DECL $ ;
DECL = TYPE NAME '=' VAL ;
TYPE = 'int' | 'float' ;
NAME = 'a' | 'b' ;
VAL = '4' ;
I parse this input stream with Grako:
int a = 4
float b = 4
and I retrieve this abstract syntax tree (JSON):
[
"int",
"a",
[
"=",
"4"
],
[
"float",
"b",
[
"=",
"4"
]
]
]
Is there a simple way to obtain ASTs like this:
[
"int" TYPE,
"a" NAME,
[
"=" DECL,
"4" VAL
],
[
"float" TYPE,
"b" NAME,
[
"=" DECL,
"4" VAL
]
]
]
or this:
...
"int TYPE",
...
?
I believe semantic actions in the Grako generated parser is the solution, but I can't figure it out.
Is there a simple way to do this ?

The output format you propose is not JSON-compatible, and it's not Python. By using Grako's features for AST customization you can obtain output that can be processed in Python and in any other language that has a JSON library.
Modify the grammar by adding an AST name to the elements of interest, like this:
START = DECL DECL $ ;
DECL = TYPE:TYPE NAME:NAME '=' VAL:VAL ;
TYPE = 'int' | 'float' ;
NAME = 'a' | 'b' ;
VAL = '4' ;
And you'll obtain output like this:
AST:
[AST({'NAME': 'a', 'VAL': '4', 'TYPE': 'int'}), AST({'NAME': 'b', 'VAL': '4', 'TYPE': 'float'})]
JSON:
[
{
"TYPE": "int",
"NAME": "a",
"VAL": "4"
},
{
"TYPE": "float",
"NAME": "b",
"VAL": "4"
}
]
The resulting AST is easy to process into whichever final output you need.

Related

How can I spread array elements in parent object by jq?

I want to transform following type of json obj
[
{
"A": "a",
"Tags": [
{ "key":"x", "value":0},
{ "key":"y", "value":1},
]
},
{...}
]
to this, including Tags list on top
[
{
"A": "a",
"x": 0,
"y": 1
},
{...}
]
I try to use JQ but without result.
map(. + (.Tags | from_entries) | del(.Tags))
Will map() over all the objects in the array and:
Convert .Tags to an object using from_entries
This is added to the original object (. + ())
Delete the original .Tags
Output:
[
{
"A": "a",
"x": 0,
"y": 1
}
]
Online Demo

Conditional if/then/else for JMESPath?

Am trying to do a simple if/then/else using JMESPath
For example: 'if the input is a string, return the string, else return the "value" property of the input'. An input of "abc" would return "abc". An input of {"value":"def"} would return "def"
With jq this is easy: if .|type == "string" then . else .value end
With JMESPath, I can get the type
type(#)
or the input:
#
or the value property:
value
but I have not found a way to combine them into an if-then-else. Is there any way to do this?
It is possible but not cleanly. The general form is to:
Make the value you are testing an array (wrap in square braces)
Apply the map function to map the filtered array to what value you want if true
At this point you have an array that is populated with one (true) item if the array filter passed, otherwise it is empty
Concat to that array one item (the false value)
Finally, take item at index 0 in this array - which will be the result of the condition
This should allow you to also derive possible transformations for both the false and true conditions
For example, if the test data is as so:
{
"test": 11
}
Depending on the value you can get either produce the results (using test data 11 and 2 as example):
"Yes, the value is 11 which is greater than 10"
OR
"No the value is 2 which is less than or equal to 10"
Like so:
[
map(
&join(' ', ['Yes, the value is', to_string(#), 'which is greater than 10']),
[test][? # > `10`]
),
join(' ', ['No the value is', to_string(test), ' which is less than or equal to 10'])
][] | #[0]
So to abstract a template:
[
map(
&<True Expression Here>,
[<Expression you are testing>][? # <Test Expression>]
),
<False Expression Here>)
][] | #[0]
people[?general.id !=100] || people
{
"people": [
{
"general": {
"id": 100,
"age": 20,
"other": "foo",
"name": "Bob"
},
"history": {
"first_login": "2014-01-01",
"last_login": "2014-01-02"
}
},
{
"general": {
"id": 101,
"age": 30,
"other": "bar",
"name": "Bill"
},
"history": {
"first_login": "2014-05-01",
"last_login": "2014-05-02"
}
}
]
}
if else condition works here

MOP: acess any slot definition ? (mito's col-type)

I define a class which uses the Mito ORM, the slots define a :col-type:
(isbn
:accessor isbn
:initarg :isbn
:col-type (or (:varchar 128) :null))
How to get the :col-type definition ? Since this is a slot in my class definition, is there no generic way to access it, like slot-definition :col-type ... ?
On the clos-mop documentation, I only find how to access
slot-definition-allocation
slot-definition-initargs
slot-definition-initform
slot-definition-initfunction
slot-definition-name
slot-definition-type
The isbn slot shows like this:
#<MITO.DAO.COLUMN:DAO-TABLE-COLUMN-CLASS {1005928483}>
--------------------
Name: BOOKSHOPS.MODELS:ISBN
Init args: (:ISBN)
Init form: #<unspecified>
Init function: NIL
--------------------
Group slots by inheritance [ ]
Sort slots alphabetically [X]
All Slots:
[ ] %CLASS = #<DAO-TABLE-CLASS BOOK>
[ ] %DOCUMENTATION = NIL
[ ] %TYPE = T
[ ] ALLOCATION = :INSTANCE
[ ] ALLOCATION-CLASS = NIL
[ ] COL-TYPE = (OR (:VARCHAR 128) :NULL)
[ ] DEFLATE = #<unbound>
[ ] GHOST = NIL
[ ] INFLATE = #<unbound>
[ ] INITARGS = (:ISBN)
[ ] INITFORM = NIL
[ ] INITFUNCTION = NIL
[ ] NAME = BOOKSHOPS.MODELS:ISBN
[ ] PRIMARY-KEY = NIL
[ ] READERS = (BOOKSHOPS.MODELS:ISBN)
[ ] REFERENCES = NIL
[ ] WRITERS = ((SETF BOOKSHOPS.MODELS:ISBN))
Thanks.
The col-type is an extension provided by mito.class.column:table-column-class. It has an accessor %table-column-type, which is wrapped by mito.class.column:table-column-type.

jq: Getting two levels of keys

I have some json data that looks like:
{
"p": {
"d": {
"a" : {
"r": "foo",
"g": 1
},
"b": {
"r": "bar",
"g": 2
}
},
"c": {
"e": {
"r": "baz",
"g": 1
}
},
...
}
}
I want something like:
{
"d": [
"a",
"b"
],
"c": [
"e"
]
}
I can get the list of keys on the first level under "p" with jq '.p|keys', and the structure and keys on the second level with jq '.p|map(.|keys)', but I can't figure out how to combine it.
Use map_values instead of map to map the values of a JSON object while preserving the keys:
jq '.p | map_values(keys)'
On jq versions lower than 1.5, map_values is not defined: instead, you can use []|=:
jq '.p | . []|= keys'
In general
Top level keys:
curl -s https://crates.io/api/v1/crates/atty | jq '. |= keys'
[
"categories",
"crate",
"keywords",
"versions"
]
Two levels of keys:
curl -s https://crates.io/api/v1/crates/atty | jq '.| map_values(keys)'
{
"crate": [
"badges",
"categories",
"created_at",
"description",
"documentation",
"downloads",
"exact_match",
"homepage",
"id",
"keywords",
"links",
"max_version",
"name",
"newest_version",
"recent_downloads",
"repository",
"updated_at",
"versions"
],
"versions": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16
],
"keywords": [
0,
1,
2
],
"categories": []
}
Method versions
topLevelJsonKeys() {
curl -s $1 | jq '. |= keys'
# EXAMPLE:
# topLevelJsonKeys https://crates.io/api/v1/crates/atty
}
topLevelJsonKeys2() {
curl -s $1 | jq '.| map_values(keys)'
# EXAMPLE:
# topLevelJsonKeys2 https://crates.io/api/v1/crates/atty
}
Here is a solution which uses reduce and setpath
.p
| reduce keys[] as $k (
.
; setpath([$k]; .[$k] | keys)
)

issue with list.groupBy()

I have a list as below
def qresultList = [
[location: 'a', txs: 10],
[location: 'b', txs: 20],
[location: 'a', txs: 30]
]
I want to get distinct list of locations with sum of txs for same location.. so I am doing groupby on location like this:
def totalsByLocation1 = qresultList.groupBy{ it.location }.
collectEntries{ key, vals -> [key, vals*.txs.sum()] }
The above code is inside SummaryUtilsService/getWorldSummary function
I am getting the following error
No signature of method: java.util.LinkedHashMap.collectEntries() is applicable for argument types: (summary.SummaryUtilsService$_getWorldSummary_closure3) values: [summary.SummaryUtilsService$_getWorldSummary_closure3#2750e6c9]
Update: the actual result from the query is
def qresultList = [
['a', 10],
['b', 20],
['a', 30]
]
so Its a list of lists..
From earlier questions, I assume you're using Grails 1.3.7 or something
The pre-groovy 1.8.X way of doing this is:
def totalsByLocation1 = qresultList.groupBy{ it.location }.inject([:]) { map, val ->
map << [ (val.key): val.value*.txs.sum() ]
}
Edit
If your input list is:
def qresultList = [
['a', 10],
['b', 20],
['a', 30]
]
Then you will need something like:
qresultList.groupBy { it[ 0 ] }.collectEntries { k, v ->
[ (k): v*.getAt( 1 ).sum() ]
}
For a list of maps:
assert [[a:40], [b:20]] == qresultList.groupBy {it.location}.collect {[(it.key): it.value.sum{it.txs}]}
For a map:
def locationTransactionSums = [:]
qresultList.groupBy {it.location}.each {
locationTransactionSums.(it.key) = it.value.sum{it.txs}
}
assert [a:40, b:20] == locationTransactionSums

Resources