Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

infix-operator-precedence atom #10

Merged
merged 23 commits into from
Jul 11, 2017

Conversation

Normand-1024
Copy link
Contributor

also added a missing case in assignment-as-value classifier

@dgopstein
Copy link
Owner

  1. I can't figure out how to git right now, but here are some extra tests you should add: 8ac7ed9 there are more that you should add of your own as well, but this is a starting point.

  2. Would you mind adding a test to assignment-as-value for the missing assignment type? Recently I've adopted a policy of adding tests every time I find a bug so that a) I can confirm it was a bug b) I can confirm I've fixed it and c) it doesn't happen again.

It would also be nice to add similar tests to other atoms that include assignment, but if you're feeling lazy I can do that, let me know.

  1. I'm pretty sure I have a similar list of assignments somewhere else. Would you mind seeing if thats true, and if so, putting them both into a function like assignment? in one of the util files?

@@ -7,8 +7,8 @@
"Is this node an assignment expression"
[node]
(and (instance? IASTBinaryExpression node)
(contains? #{IASTBinaryExpression/op_assign IASTBinaryExpression/op_binaryAndAssign IASTBinaryExpression/op_binaryXorAssign
IASTBinaryExpression/op_divideAssign IASTBinaryExpression/op_minusAssign
(contains? #{IASTBinaryExpression/op_assign IASTBinaryExpression/op_binaryAndAssign IASTBinaryExpression/op_binaryOrAssign
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent catch! Can you add a test for this case in assignment as value?

IASTBinaryExpression/op_assign IASTBinaryExpression/op_binaryAndAssign IASTBinaryExpression/op_binaryOrAssign
IASTBinaryExpression/op_binaryXorAssign IASTBinaryExpression/op_divideAssign IASTBinaryExpression/op_minusAssign
IASTBinaryExpression/op_moduloAssign IASTBinaryExpression/op_multiplyAssign IASTBinaryExpression/op_plusAssign
IASTBinaryExpression/op_shiftLeftAssign IASTBinaryExpression/op_shiftRightAssign}]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure I've written this same list before?

@Normand-1024
Copy link
Contributor Author

It can be argued that the function returning operator precedence level isn't all that useful because of the fact that the conditional operator and assigment operator are on the same precedence level. I just end up using it to check whether the type or operator of the node is contained in the precedence list.

@dgopstein
Copy link
Owner

dgopstein commented May 22, 2017

I made a lot of other minor remarks, but I think this is the crux of the review. If you can only fix one thing, it should be this:

I agree, as it's implemented/used now get-operator-precedence is not useful.

I think the way you should use precedence-level is (1) add every type of operator to it (so you never have to test it for nil), then (2) remove node-operator-equal? and instead test for #(=by precedence-level node %)

@@ -496,6 +496,36 @@

(println "===================================================")))))

(defn get-precedence-level
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clojure functions don't use the "get" prefix convention, this probably belongs in classifier-util as well.

IASTBinaryExpression/op_binaryXorAssign 15 IASTBinaryExpression/op_divideAssign 15 IASTBinaryExpression/op_minusAssign 15
IASTBinaryExpression/op_moduloAssign 15 IASTBinaryExpression/op_multiplyAssign 15 IASTBinaryExpression/op_plusAssign 15
IASTBinaryExpression/op_shiftLeftAssign 15 IASTBinaryExpression/op_shiftRightAssign 15 CPPASTConditionalExpression 15
CPPASTExpressionList 16}]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This list is fine, but if you wanted to get crafty you could use lists and a macro to reduce redundancy dramatically.

CPPASTExpressionList 16}]
(if (instance? IASTBinaryExpression node) (precedence-list (.getOperator node)) (precedence-list (type node)))))

(defn assignment-operator?
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically only one of the operators involved is the "assignment operator", yet they all perform assignment. I think probably assignment? is the right name. Also, I think this belongs in classifier-util, not util (although, heads up, I'm in the midst of refactoring util right now actually...)

(let [assignment-list
#{IASTBinaryExpression/op_assign IASTBinaryExpression/op_binaryAndAssign IASTBinaryExpression/op_binaryOrAssign IASTBinaryExpression/op_binaryXorAssign IASTBinaryExpression/op_divideAssign IASTBinaryExpression/op_minusAssign IASTBinaryExpression/op_moduloAssign IASTBinaryExpression/op_multiplyAssign IASTBinaryExpression/op_plusAssign IASTBinaryExpression/op_shiftLeftAssign IASTBinaryExpression/op_shiftRightAssign}]

(if (instance? IASTBinaryExpression node) (contains? assignment-list (.getOperator node)) false)))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any time you have a boolean literal in an if you can probably do the same thing better with a logical function (i.e. and or or)


if (0 && 1 || 2 && 3) ; // <true>
if ((0 && 1) || (2 && 3));
if (0 && (1 || 2) && 3);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add to the lines which intentionally shouldn't have an atom. helps to show intentionality instead of forgetfulness.

IASTBinaryExpression/op_minus IASTBinaryExpression/op_divide IASTBinaryExpression/op_modulo
IASTBinaryExpression/op_shiftLeft IASTBinaryExpression/op_shiftRight IASTBinaryExpression/op_greaterThan
IASTBinaryExpression/op_greaterEqual IASTBinaryExpression/op_lessThan IASTBinaryExpression/op_lessEqual CPPASTConditionalExpression}]
(if (instance? IASTBinaryExpression node) (contains? non-associative-list (.getOperator node)) (contains? non-associative-list (type node)))))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lispy way to do this:

(contains? non-associative-list
           ((if (instance? IASTBinaryExpression node)
              .getOperator
              type)
            node))```


(defn node-operator-equal?
"compare types two nodes, if both binary expression then compare their operators"
[node1 node2]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

node-operator-equal? is fine, but I might consider calling it just operator-equal? and making it variadic. Makes it more useful and the code smaller.

(some #(and (not (nil? (get-precedence-level %)))
(not (node-operator-equal? node %))) (children node)))

false))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, don't put false the return of an if.

(some #(not (nil? (get-precedence-level %))) (children node))
;;Current node is an associative operator
(some #(and (not (nil? (get-precedence-level %)))
(not (node-operator-equal? node %))) (children node)))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you use (not (nil? several times here, but the way clojure handles truthiness means you almost never need (not (nil? unless you care about the difference between nil and false.

[node]
(let [precedence-list
{;"currently supports binary operators(not including sizeof), comma operator and conditional operator"
IASTBinaryExpression/op_modulo 5 IASTBinaryExpression/op_multiply 5 IASTBinaryExpression/op_divide 5
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why aren't all the precedence levels defined in this function? This should definitely have everything from 1-15 or else it won't really be useful outside your code.

"Is this node a infix-operator-precedence-atom?"
[node]
(and
(and (precedence-level node)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need to nest ands, just add more operands to the first one

(and (and x y) z) is the same as (and x y z)

(let [assignment-list
#{IASTBinaryExpression/op_assign IASTBinaryExpression/op_binaryAndAssign IASTBinaryExpression/op_binaryOrAssign IASTBinaryExpression/op_binaryXorAssign IASTBinaryExpression/op_divideAssign IASTBinaryExpression/op_minusAssign IASTBinaryExpression/op_moduloAssign IASTBinaryExpression/op_multiplyAssign IASTBinaryExpression/op_plusAssign IASTBinaryExpression/op_shiftLeftAssign IASTBinaryExpression/op_shiftRightAssign}]

(and (instance? IASTBinaryExpression node) (contains? assignment-list (.getOperator node)))))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the functions in util look great, but can you put them in cdt_util instead?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe classifier_util is better actually. what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking through cdt_util and classifier_util, I get the general sense that classifier_util contains generally higher level functions(if that makes sense to you). So I think it's more fitting to put it in classifier_util

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good way to put it. I think you're right about both points.


(if (non-associative? node)
;;Current node is a non-associative operator
(some #(precedence-level %) (children node))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eventually I'm going to add a few more entries to the set of operators in precedence-level. Will that break this code?

What are you actually testing when you're using precedence-level as a predicate? I recommend you make a function that describes what you're testing. It should probably end with a ? and return a boolean.

Copy link
Contributor Author

@Normand-1024 Normand-1024 May 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you're going to add and it really depends on how eclipse parses and what we consider confusing or not confusing. But I think it'll maintain a good accuracy.

(precedence-level node) is basically testing whether or not this node represents an operator. If it returns a number then yes and testing can go on, if nil then this node can be ignored.

With unary operators taken into account I think the code needs to be reconsidered.

! a && b: we agreed that this code is confusing, and currently my code takes care of it

*p && b: assuming p is pointing to a boolean, I don't think this code is as confusing, but in terms of precedence level it is basically the same as the one above

++a + b: again, basically the same thing, but I don't think it's as confusing as the first one.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds like we'd be better of with a function called operator? or something. But, to be honest, that sounds the same as expression? no?

These are really great questions re !, *, and ++. Could you type them up into an email and send to Justin/the group?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think operators in this case is a subset of expressions. a as a variable is also parsed as an expression, namely CPPASTIdExpression.

And yes I'll try to type up an email and send it to the group

Copy link
Owner

@dgopstein dgopstein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, I think this PR is great. I made a bunch of comments. Fix the ones you want to, leave the ones you don't. There's only one mandatory comment, which is that you should remove the log-files you committed. But otherwise this is really good. I'll be glad to have it in the codebase!

children
(map operator-group)
(remove nil?)
(map (fn [child-group] (sort [(operator-group node) child-group])))))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very common pattern of have 2 function arities, both of which do the same thing, one using a predefined value. I usually define the unary version in terms of the binary:

([node] (group-pair node (children node)))

(let [node-group (operator-group node)]
(or (= :arith_unary node-group)
(= :not node-group)
(= :pointer node-group))))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you wanted, this function could be a single line

(def rvalue-unary-operator? (comp #{:arith_unary :not :pointer} operator-group))

But in general, I'm more a fan of naming your data than naming your behaviors, so might go:

(def rvalue-unary-ops #{:arith_unary :not :pointer})

(defn specific-confusing-operator-combination?
"if the combination of groups of operators exists in this set, then the combination is confusing"
[combination]
(some #(= % combination) (map sort #{[:de_incr :pointer] [:multiply :add] [:arith_unary :add] [:arith_unary :multiply] [:and :add] [:and :multiply] [:and :arith_unary] [:or :add] [:or :multiply] [:or :arith_unary] [:or :and] [:not :add] [:not :multiply] [:not :arith_unary] [:not :and] [:not :or]
Copy link
Owner

@dgopstein dgopstein Jul 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. please use shorter lines, this is hard to read
  2. when you map over a set #{}, you get back a seq '(), so I wouldn't even bother using set notation to begin with, because its misleading.
  3. you don't sort the combination, which means that if you pass in [:pointer :de_incr] it won't match even when it should

In total I'd probably do something like:

(def confusing-op-combinations (->> [[:de_incr :pointer] [:multiply :add] ...] (map sort) (into #{})))
(defn specific-confusing-operator-combination? [combination] (confusing-op-combinations (sort combination)))



;;
;;The following 3 functions are for the binary operator special case
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/binary/unary/

(= :not node-group)
(= :pointer node-group))))

(defn unbracktted-unary-in-children
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is unbracktted?

(defn unbracktted-unary-in-children
"Is this node not a bracket and constains unary operator in its children?"
[node]
(if (not= "()" (write-node node))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love to see a predicate parens?, but I'd probably implement it (instance? IASTParenthesisExpression) or whatever.

Copy link
Owner

@dgopstein dgopstein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just confirm that not sorting combination isn't a bug for me...

;;
(defn rvalue-unary-operator?
;;====================================
;;For binary operator special case
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you really mean binary, not unary here?? if so, please explain more, I don't understand

:gcc-path "~/opt/src/gcc",
:ag-path "~/opt/src/the_silver_searcher",
:github-top-c "~/opt/src/github-top-c"
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you remove this on purpose?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JK, saw you added it back.

(some #(= % combination) (map sort #{[:de_incr :pointer] [:multiply :add] [:arith_unary :add] [:arith_unary :multiply] [:and :add] [:and :multiply] [:and :arith_unary] [:or :add] [:or :multiply] [:or :arith_unary] [:or :and] [:not :add] [:not :multiply] [:not :arith_unary] [:not :and] [:not :or]
;;[:compare :and] [:compare :or] // Underclassify
[:compare :not] [:compare :compare] [:pointer :add] [:cond :arith_unary] [:cond :and] [:cond :or] [:cond :not] [:cond :compare] [:cond :cond] [:non-asso :add] [:non-asso :multiply] [:non-asso :arith_unary] [:non-asso :and] [:non-asso :or] [:non-asso :not] [:non-asso :non-asso] [:field_ref :pointer]})))
(some #(= % combination) (map sort [[:de_incr :pointer] [:multiply :add] [:arith_unary :add] [:arith_unary :multiply]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure it's not wrong to not sort combination I feel like it'll actually get the wrong answer 50% of the time?

Also, this function has to do 60 comparisons or something. But if you put all the operator combinations into a set and store that using def, then use a set lookup, it's only a single comparison. 60x speedup.

(def confusing-combinations (into #{} (map sort [[:de_incr :pointer] [:multiply :add] ...])))
(defn specific-confusing-operator-combination? [combination] (confusing-combinations (sort combination)))

[:cond :cond] [:non-asso :add] [:non-asso :multiply] [:non-asso :arith_unary]
[:non-asso :and] [:non-asso :or] [:non-asso :not] [:non-asso :non-asso]
[:field_ref :pointer]])))
(some #(= % combination) (into #{} (map sort [[:de_incr :pointer] [:multiply :add] [:arith_unary :add] [:arith_unary :multiply]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for doing this, but it actually misses the main benefit of what I was proposing. the construction of the set (into #{} happens every time specific-confusing-operator-combination? is run, which is lame, additionally, (some #(= % combination) has to loop over every element of the set, where as if you did a set-lookup it's constant-time. So, if you wanted to make this better you could:

  1. put (into #{} (map ... into a def variable outside the function, so it's only created one time ever
  2. don't use (some XXX set) on a set, instead just do (set XXX)

That said, if you're sick of this, we can commit the code now, up to you, just let me know.

@dgopstein
Copy link
Owner

Sorry about all the BS, but I really am very happy with how this turned out. Thanks!

@dgopstein dgopstein merged commit 04126c3 into dgopstein:master Jul 11, 2017
@Normand-1024 Normand-1024 deleted the infix-operator-precedence branch July 11, 2017 21:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants