Skip to content

Commit

Permalink
Command to set hit conditions
Browse files Browse the repository at this point in the history
  • Loading branch information
firelizzard18 committed Oct 21, 2020
1 parent 9854ec0 commit e2039ed
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 24 deletions.
34 changes: 11 additions & 23 deletions lib/byebug/commands/condition.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# frozen_string_literal: true

require_relative "../command"
require_relative "../helpers/parse"
require_relative "../subcommands"

require_relative "condition/expression"
require_relative "condition/hitcount"

module Byebug
#
Expand All @@ -10,24 +12,19 @@ module Byebug
# Adds the ability to stop on breakpoints only under certain conditions.
#
class ConditionCommand < Command
include Helpers::ParseHelper
include Subcommands

self.allow_in_post_mortem = true

def self.regexp
/^\s* cond(?:ition)? (?:\s+(\d+)(?:\s+(.*))?)? \s*$/x
/^\s* cond(?:ition)? (?:\s+ (.+))? \s*$/x
end

def self.description
<<-DESCRIPTION
cond[ition] <n>[ expr]
cond[ition] <subcommand>
#{short_description}
Specify breakpoint number <n> to break only if <expr> is true. <n> is
an integer and <expr> is an expression to be evaluated whenever
breakpoint <n> is reached. If no expression is specified, the condition
is removed.
DESCRIPTION
end

Expand All @@ -36,20 +33,11 @@ def self.short_description
end

def execute
return puts(help) unless @match[1]

breakpoints = Byebug.breakpoints.sort_by(&:id)
return errmsg(pr("condition.errors.no_breakpoints")) if breakpoints.empty?

pos, err = get_int(@match[1], "Condition", 1)
return errmsg(err) if err

breakpoint = breakpoints.find { |b| b.id == pos }
return errmsg(pr("break.errors.no_breakpoint")) unless breakpoint

return errmsg(pr("break.errors.not_changed", expr: @match[2])) unless syntax_valid?(@match[2])
subcmd_name = @match[1]
return puts(help) unless subcmd_name

breakpoint.expr = @match[2]
subcmd = subcommand_list.match(subcmd_name) || subcommand_list.match('expression')
subcmd.new(processor, arguments).execute
end
end
end
67 changes: 67 additions & 0 deletions lib/byebug/commands/condition/expression.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# frozen_string_literal: true

require_relative "../../command"
require_relative "../../helpers/parse"

module Byebug
#
# Reopens the +condition+ command to define the +expression+ subcommand
#
class ConditionCommand < Command
#
# Information about arguments of the current method/block
#
class ExpressionCommand < Command
include Helpers::ParseHelper

self.allow_in_post_mortem = true

def self.regexp
/
^\s*
(?:expr(?:ession)?\s+)?
(?:
(?<number>\d+) # breakpoint number
(?:\s+(?<expression>.*))? # conditional expression
)?
\s*$
/x
end

def self.description
<<-DESCRIPTION
cond[ition] [expr[ession]] <n> [<expr>]
#{short_description}
Set or unset the conditional expression of breakpoint number <n>. If a
conditional expression is set, the breakpoint will not break unless the
expression evaluates to true. If <expr> is omitted, the condition
expression is removed.
DESCRIPTION
end

def self.short_description
"Set a conditional expression on a breakpoint"
end

def execute
return puts(help) unless @match && @match[:number]

breakpoints = Byebug.breakpoints.sort_by(&:id)
return errmsg(pr("condition.errors.no_breakpoints")) if breakpoints.empty?

pos, err = get_int(@match[:number], "Condition", 1)
return errmsg(err) if err

breakpoint = breakpoints.find { |b| b.id == pos }
return errmsg(pr("break.errors.no_breakpoint")) unless breakpoint

return errmsg(pr("break.errors.not_changed", expr: @match[:expression])) unless syntax_valid?(@match[:expression])

breakpoint.expr = @match[:expression]
end
end
end
end
87 changes: 87 additions & 0 deletions lib/byebug/commands/condition/hitcount.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# frozen_string_literal: true

require_relative "../../command"
require_relative "../../helpers/parse"

module Byebug
#
# Reopens the +condition+ command to define the +expression+ subcommand
#
class ConditionCommand < Command
#
# Information about arguments of the current method/block
#
class HitCountCommand < Command
include Helpers::ParseHelper

self.allow_in_post_mortem = true

def self.regexp
/
^\s*
hit(?:\s?count)?
(?:\s+
(?<number>\d+) # breakpoint number
(?:\s+
(?<operation>[\w><=%]+) # hit condition
\s+(?<value>\d+) # value
)?
)?
\s*$
/x
end

def self.description
<<-DESCRIPTION
cond[ition] hit[[ ]count] <n> [<op> <value>]
#{short_description}
Set or unset the hit condition of breakpoint number <n>. If a hit
condition is set, the breakpoint will not break unless its hit count
meets the hit condition. If <op> <value> is omitted, the condition
expression is removed. <op> can be gt, ge, eq, mod, or the symbolic
equivalent.
DESCRIPTION
end

def self.short_description
"Set a hit condition on a breakpoint"
end

def execute
return puts(help) unless @match && @match[:number]

breakpoints = Byebug.breakpoints.sort_by(&:id)
return errmsg(pr("condition.errors.no_breakpoints")) if breakpoints.empty?

pos, err = get_int(@match[:number], "Condition", 1)
return errmsg(err) if err

breakpoint = breakpoints.find { |b| b.id == pos }
return errmsg(pr("break.errors.no_breakpoint")) unless breakpoint

v = @match[:value].to_i
case @match[:operation]
when nil
breakpoint.hit_condition = nil

when 'gt', '>'
breakpoint.hit_condition = :greater_or_equal
breakpoint.hit_value = v + 1
when 'ge', '>='
breakpoint.hit_condition = :greater_or_equal
breakpoint.hit_value = v
when 'eq', '=', '==', '==='
breakpoint.hit_condition = :equal
breakpoint.hit_value = v
when 'mod', '%'
breakpoint.hit_condition = :modulo
breakpoint.hit_value = v
else
return errmsg(pr("break.errors.hit_condition", cond: @match[:operation]))
end
end
end
end
end
14 changes: 13 additions & 1 deletion lib/byebug/commands/info/breakpoints.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,25 @@ def execute
private

def info_breakpoint(brkpt)
conds = []
case brkpt.hit_condition
when :greater_or_equal
conds << "hits >= #{brkpt.hit_value}"
when :equal
conds << "hits == #{brkpt.hit_value}"
when :modulo
conds << "!(hits % #{brkpt.hit_value})"
end

conds << brkpt.expr if brkpt.expr

interp = format(
"%-<id>3d %-<status>3s at %<file>s:%<line>s%<expression>s",
id: brkpt.id,
status: brkpt.enabled? ? "y" : "n",
file: brkpt.source,
line: brkpt.pos,
expression: brkpt.expr.nil? ? "" : " if #{brkpt.expr}"
expression: conds.empty? ? "" : " if #{conds.join(" and ")}"
)
puts interp
hits = brkpt.hit_count
Expand Down
1 change: 1 addition & 0 deletions lib/byebug/printers/texts/base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ break:
no_breakpoint: "Invalid breakpoint id. Use \"info breakpoint\" to find out the correct id"
no_breakpoint_delete: "No breakpoint number {pos}"
not_changed: "Incorrect expression \"{expr}\", breakpoint not changed"
hit_condition: "Invalid hit condition \"{cond}\". See \"help cond hit\"."
confirmations:
delete_all: "Delete all breakpoints?"
messages:
Expand Down

0 comments on commit e2039ed

Please sign in to comment.