Antlr4: How to change my grammar to let chained elements being parsed? - parsing

I need a grammar to parse double dots delimited tokens like:
1..5, v[1]..v[2] or 1+f(1)..2+v[f(2)]..3+f(3).
Basically, these tokens represent integer ranges, for ex, 1..5 means integers in range 1 to 5. Token literal should only be represented as "Integer..Integer"
I also have to parse some integer literals and real literals as well.
So currently, I have a bottom up grammar like:
unary_expr
: range_expr # ToRangeExpr
| PLUS rhs=unary_expr # UnaryPlusExpr
| MINUS rhs=unary_expr # UnaryMinusExpr
| NOT rhs=unary_expr # UnaryNotExpr
;
range_expr
: index_expr # ToIndexExpr
| lhs=index_expr RANGEDOT rhs=index_expr # RangeExpr
| lhs=range_literal rhs=index_expr # RangeLiteralExpr
;
index_expr
: atom # ToAtom
| atom LBRACK expression RBRACK # IndexExpr
;
atom
: vector_atom # ToVectorAtom
| matrix_atom # ToMatrixAtom
| boolean_literal # ToBooleanLiteral
| int_literal # ToIntegerLiteral
| real_literal # ToRealLiteral
| char_literal # ToCharLiteral
| string_literal # ToStringLiteral
| tuple_literal # ToTupleLiteral
| range_literal # ToRangeLiteral
| tuple_element # ToTupleElement
| type_cast # ToTypeCast
| stream_state # ToStreamState
| function_call # ToFunctionCall
| ID # IDAtom
| IDENTITY # IdentityLiteral
| NULL # NullLiteral
| LPAREN expression RPAREN # ToSubExpr
range_literal: RANGE_LITERAL;
RANGE_LITERAL
: INT_LITERAL RANGEDOT INT_LITERAL
;
REAL_LITERAL
: DOT US+ INT_LITERAL REAL_EXP?
| INT_LITERAL DOT US* INT_LITERAL? REAL_EXP?
| INT_LITERAL REAL_EXP
| DOT INT_LITERAL REAL_EXP
;
REAL_EXP
: 'e' US* (PLUS | MINUS |)? US* INT_LITERAL
;
INT_LITERAL: NUM (NUM | US)*;
So currently, my grammar can parse multiple-integer-chained range tokens. However, I can't parse any multiple-expression-chained range tokens. I tried to change my range_expr as (make it more ambiguous):
range_expr
: range_literal
| index_expr (RANGEDOT index_expr*)
;
But, it didn't change my parsing sensitivities. So what else change should I make to let my grammar parse multiple index_expr chained range tokens?

Explanation
I couldn't reuse your grammar (because of the missing lexer/parser rules) but if I understand the problem correctly: you want to have a simple range of two numbers or to chain together an arbitrary number of expr. Idea for this is to have a sub-rule in index_expr that will match range of numbers (a specialized version of exprChain) and to have a recursive definition of expr that will consist of a chaining expression (exprChain).
Solution
As an example of the idea I introduce small grammar.
grammar test;
INT : [0-9]+;
REAL : [0-9]* '.' [0-9]+;
NAME : [a-zA-Z]+;
numeric
: INT | REAL
;
reference
: NAME # variable
| NAME '[' expr ']' # array
| NAME '(' expr ')' # functionCall
;
index_expr
: numeric '..' numeric # rangeOfNumbers
| expr # classicExpr
;
expr
: expr '+' expr # exprAdd
| reference # exprRef
| numeric # exprNumber
| expr '..' expr # exprChain
;
This example grammar is able to match all the ranged expressions you mentioned: 1..5 or .1...3 (as rangeOfNumbers), v[1]..v[2] or 1+f(1)..2+v[f(2)]..3+f(3) (both as exprChain).

The problem is I can't treat range_expr as unary_expr in my grammar, because it will confuse antlr4 with the real_literal option, and force antlr4 match logic_expr tokens (i.e xor, or, which I didn't show them in my question). After my micro fix, the partial grammar will be like:
unary_expr
: index_expr # ToIndexExpr
| PLUS rhs=unary_expr # UnaryPlusExpr
| MINUS rhs=unary_expr # UnaryMinusExpr
| NOT rhs=unary_expr # UnaryNotExpr
;
index_expr
: atom # ToAtom
| atom LBRACK expression RBRACK # IndexExpr
| lhs=index_expr RANGEDOT rhs=index_expr # RangeExpr
| lhs=range_literal rhs=index_expr # RangeLiteralExpr
;
atom
: vector_atom # ToVectorAtom
| matrix_atom # ToMatrixAtom
| boolean_literal # ToBooleanLiteral
| int_literal # ToIntegerLiteral
| real_literal # ToRealLiteral
| char_literal # ToCharLiteral
| string_literal # ToStringLiteral
| tuple_literal # ToTupleLiteral
| range_literal # ToRangeLiteral
| tuple_element # ToTupleElement
| type_cast # ToTypeCast
| stream_state # ToStreamState
| function_call # ToFunctionCall
| ID # IDAtom
| IDENTITY # IdentityLiteral
| NULL # NullLiteral
| LPAREN expression RPAREN # ToSubExpr
;
real_literal: REAL_LITERAL # RealLiteral
| DOT INT_LITERAL # EdgeCaseRealLiteral;
range_literal: RANGE_LITERAL;
RANGE_LITERAL
: INT_LITERAL RANGEDOT
;
REAL_LITERAL
: DOT US+ INT_LITERAL REAL_EXP?
| INT_LITERAL DOT US* INT_LITERAL? REAL_EXP?
| INT_LITERAL REAL_EXP
| DOT INT_LITERAL REAL_EXP
;
REAL_EXP
: 'e' US* (PLUS | MINUS |)? US* INT_LITERAL
;
INT_LITERAL: NUM (NUM | US)*;

Related

Cucumber rails test result

I am learning cucumber / capybara on rails. I working on a scenario that expects to display sorted data from a table. I cannot figure out this message:
pending # express the regexp above with the code you wish you had
I have this table:
| title | rating | release_date |
| Aladdin | G | 25-Nov-1992 |
| The Terminator | R | 26-Oct-1984 |
| When Harry Met Sally | R | 21-Jul-1989 |
| The Help | PG-13 | 10-Aug-2011 |
| Chocolat | PG-13 | 5-Jan-2001 |
| Amelie | R | 25-Apr-2001 |
| 2001: A Space Odyssey | G | 6-Apr-1968 |
| The Incredibles | PG | 5-Nov-2004 |
| Raiders of the Lost Ark | PG | 12-Jun-1981 |
| Chicken Run | G | 21-Jun-2000 |
Below is my step definition:
When(/^I clicked "(.*?)"$/) do |arg1|
if arg1 == "Movie Title"
click_link ("title_header")
end
end
Then(/^I expect "(.*)" before "(.*)"$/) do |arg1, arg2|
assert page.body.expect =~ /arg1.*arg2/
end
The message again I am trying to resolve is below:
You can implement step definitions for undefined steps with these snippets:
Then(/^I expect to see "(.*?)" before "(.*?)"$/) do |arg1, arg2|
pending # express the regexp above with the code you wish you had
end
Thank you for the help.
You need to define the step correctly, you are missing the 'to see' in your step definition

render with the specified mime type not downloading properly in rails

I am using following statement, but it is not downloading the excel file instead it downloading the normal text file to the browser.
render 'employees/checkin_report', :dates_arr => #dates_arr, :employees => #employees, :content_type => "application/vnd.ms-excel"
I am having checkin_report.xls.erb file, in employees view folder.
If you go to config/initializers/mime_types.rb in your project. You'll see something like this.
#Add new mime types for use in respond_to blocks:
#Mime::Type.register "text/richtext", :rtf```
There you can register new MIME types.
| Short name | respond_to symbol | Aliases and explanations |
|:---------------:|:-----------------:| ---------------------------:|
| text/html | :html :html | application/xhtml+xml |
| text/plain | :text, :txt | |
| text/javascript | :js | application/javascript, |
| | | application/x-javascript |
| text/css | :css | Cascading style sheets |
| text/calendar | :ics | iCalendar format for sharing|
| | |meeting requests and tasks |
| text/csv | :csv | Comma-separated values |
| application/xml | :xml | text/xml, application/x-xml |
| application/ | :rss | Really Simple Syndication |
| rss+xml | |format for web feeds |
| application/ | :atom | Atom Syndication Format for |
| atom+xml | |web feeds |
| application/ | :yaml | text/yaml--the human |
| x-yaml | |readable |
| | | Data serialization format |
| application/ | :url_encoded_form | The default content type of |
| x-www-form | |HTML forms |
| urlencoded | | |
| multipart/ | :multipart_form | Used for HTML forms that |
| form-data | |contain files, non-ASCII |
| | |data, and binary data |
| application/json| :json | text/x-json, application/ |
| | |jsonrequest-- |
| | | Javascript object notaion |
I'm sure there are more MIME types you can implement. These are the more common ones.

Thinking sphinx ancestry searching

I need to get all of the users from groups including subgroups:
app/indices/user_index.rb
ThinkingSphinx::Index.define :user, with: :active_record, delta: ThinkingSphinx::Deltas::SidekiqDelta do
has groups.id
has "CONCAT_WS('/',groups.id,groups.ancestry)", as: :group_ids, type: :integer, multi: true
end
But when i'm trying to search:
User.search_for_ids(with_all: { group_ids: [3] })
It returns all of the users from subgroups, but without users from group with id 3
Sphinx 2.1.7 running undex arch linux
Thinking sphinx v3.1.1
Finally found out the problem:
has "CONCAT_WS('/',groups.id,groups.ancestry)", as: :group_ids, type: :integer, multi: true
was returning only 1 id or ancestry per group, meaning if user has few root groups, e.g 3,5
the expression above will return only 1 group:
+----+--------+-----------+
| id | groups | group_ids |
+----+--------+-----------+
| 39 | 5 | 5/3 |
| 40 | 245,3 | 245 |
| 42 | 5 | 5/3 |
| 43 | 234 | 234/3/5 |
and the user with id 40 was was not finding. But, if you notice, everything works fine for groups column. The solution is to use group concat:
has "CONCAT_WS('/',GROUP_CONCAT(DISTINCT groups.`id` SEPARATOR '/'), GROUP_CONCAT(groups.`ancestry` SEPARATOR '/') )", as: :group_ids, type: :integer, multi: true
Result:
+----+--------+-----------+
| id | groups | group_ids |
+----+--------+-----------+
| 39 | 5 | 5/3 |
| 40 | 245,3 | 245/3 |
| 42 | 5 | 5/3 |
| 43 | 234 | 234/3/5 |
Also, it works fine with "/" separator.

Rails: update mutiple attribute of multiple model concurrently

I want to update values taken from a single form in two different model at the same time, I have written the following code to do so :-
if #mess.update_attributes!(:mess_name => params[:mess_name_update], :mess_capacity => params[:mess_capacity_update]) && #mess_price.update_attributes!(:breakfast_charge => params[:mess_breakfast_charge_update], :lunch_charge => params[:mess_lunch_charge_update], :dinner_charge => params[:mess_dinner_charge_update], :monthly_charge => params[:mess_monthly_charge_update], :semesterly_charge => params[:mess_semesterly_charge_update], :start_date => params[:start_date_update], :end_date => params[:end_date_update], :rebate => params[:rebate_update])
flash[:success] = "Mess Details Updated Successfully!!"
else
flash[:error] = "Some Error! Please Try Again!"
end
But the above code is giving following error
ActiveRecord::RecordInvalid
Validation failed: Start date can't be blank
Following is the two schema I am using, for #mess its MessType model and for #mess_price its MessPrice model:
MessType
+---------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+----------------+
| mess_id | int(11) | NO | PRI | NULL | auto_increment |
| mess_name | varchar(255) | NO | | NULL | |
| mess_capacity | int(11) | NO | | NULL | |
| start_date | date | No | | NULL | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
+---------------+--------------+------+-----+---------+----------------+
MessPrice
+-------------------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+----------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| breakfast_charge | float | YES | | NULL | |
| lunch_charge | float | YES | | NULL | |
| dinner_charge | float | YES | | NULL | |
| monthly_charge | float | YES | | NULL | |
| semesterly_charge | float | YES | | NULL | |
| rebate | float | YES | | NULL | |
| start_date | date | YES | | NULL | |
| end_date | date | YES | | NULL | |
| mess_id | int(11) | YES | MUL | NULL | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
+-------------------+----------+------+-----+---------+----------------+
I recommend installing the awesome_print gem - https://github.com/michaeldv/awesome_print
# add to Gemfile
gem 'awesome_print'
# install
bundle
Then the first thing in your controller action do
logger.debug " -----------"
logger.ap params
logger.debug " -----------"
Check your log file log/development.log for the output, it could be the params are coming across correctly but not what you expect?
some of the attributes might be nested in another hash key and need to be accessed via params[:something][:xyz]
I would also recommend making the code more readable and running it in a transaction
#mess.mess_name = params[:mess_name_update]
#mess.mess_capacity = params[:mess_capacity_update]
#mess_price.breakfast_charge = params[:mess_breakfast_charge_update]
#mess_price.lunch_charge = params[:mess_lunch_charge_update]
#mess_price.dinner_charge = params[:mess_dinner_charge_update]
#mess_price.monthly_charge = params[:mess_monthly_charge_update]
#mess_price.semesterly_charge = params[:mess_semesterly_charge_update]
#mess_price.start_date = params[:start_date_update]
#mess_price.end_date = params[:end_date_update]
#mess_price.rebate = params[:rebate_update]
# NOTE: an alternative to above is to name your html input fields the rails way
# so that params are sent in a nested hash, i.e. "mess_price[start_date]" -> params[:mess_price][:start_date]
# then you can do #mess_price.update_attributes(params[:mess_price])
# using form_for html helper will automatically apply this style of naming to html input fields
Mess.transaction do
# you might want save! vs save - depends if you show validation error in the UI or not
if #mess.save && #mess_price.save
flash[:success] = "Mess Details Updated Successfully!!"
else
# don't save changes, show validation errors
raise ActiveRecord::Rollback
flash[:error] = "Some Error! Please Try Again!"
end
end

group by country with ActiveRecords in Rails

I have a table with users:
name | country |
.. | UK |
.. | US |
.. | US |
.. | UK |
.. | FR |
.. | FR |
.. | UK |
.. | UK |
.. | DE |
.. | DE |
.. | UK |
.. | CA |
.
.
What is the most efficient way with ActiveRecord to get the list of countries in my view and for each country how many users are from, so:
US 123
UK 54
DE 33
.
.
.
Do the following:
counts = Users.count(:group => :country)
Now print the count:
counts.each |country, count| do
p "#{country} - #{count}"
end
# Prints
US 123
UK 54
DE 33
counts["UK"] #-> prints 123

Resources