Slide 1

Slide 1 text

Beware the Dreaded Dead End!!! By @schneems

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Beware the Dreaded Dead End!!! By @schneems

Slide 5

Slide 5 text

> # <= routes.rb:121: syntax error, unexpected end-of-input, expecting `end’ Rails.application.routes.draw do constraints -> { Rails.application.config.non_production } do namespace :foo do resource :bar end end constraints -> { Rails.application.config.non_production } do namespace :bar do resource :baz end end namespace :admin do resource :session match "/foobar(*path)", via: :all, to: redirect { |_params, req| uri = URI(req.path.gsub("foobar", "foobaz")) uri.query = req.query_string.presence uri.to_s } end

Slide 6

Slide 6 text

> Rails.application.routes.draw do constraints -> { Rails.application.config.non_production } do namespace :foo do resource :bar end end constraints -> { Rails.application.config.non_production } do namespace :bar do resource :baz end end namespace :admin do resource :session match "/foobar(*path)", via: :all, to: redirect { |_params, req| uri = URI(req.path.gsub("foobar", "foobaz")) uri.query = req.query_string.presence uri.to_s } end # <= routes.rb:121: syntax error, unexpected end-of-input, expecting `end’

Slide 7

Slide 7 text

> RSpec.describe Cutlass::BashResult do it "preserves stdout, stderr, and status" stdout = SecureRandom.hex(16) stderr = SecureRandom.hex(16) status = 0 result = BashResult.new( stdout: stdout, stderr: stderr, status: status ) expect(result.stdout).to eq(stdout) expect(result.stderr).to eq(stderr) expect(result.status).to eq(status) end end # <= result_spec:19 syntax error, unexpected end-of-input, expecting `end’

Slide 8

Slide 8 text

🤔

Slide 9

Slide 9 text

> 3 module Cutlass 4 RSpec.describe Cutlass::BashResult do ❯ 5 it "preserves stdout, stderr, and status" ❯ 19 end 21 it "success?" do 52 end 53 end 54 end This code has an unmatched `end`. Ensure that all `end` lines in your code have a matching syntax keyword (`def`, `do`, etc.)

Slide 10

Slide 10 text

>

Slide 11

Slide 11 text

> # frozen_string_literal: true module Cutlass RSpec.describe Cutlass::BashResult do it "preserves stdout, stderr, and status" stdout = SecureRandom.hex(16) stderr = SecureRandom.hex(16) status = 0 result = BashResult.new( stdout: stdout, stderr: stderr, status: status ) expect(result.stdout).to eq(stdout) expect(result.stderr).to eq(stderr) expect(result.status).to eq(status) end end end # <= result_spec:19 syntax error, unexpected end-of-input, expecting `end’

Slide 12

Slide 12 text

> 3 module Cutlass 4 RSpec.describe Cutlass::BashResult do ❯ 5 it "preserves stdout, stderr, and status" ❯ 19 end 21 it "success?" do 52 end 53 end 54 end

Slide 13

Slide 13 text

0 25 50 75 100 April May June July (I did not do a study)

Slide 14

Slide 14 text

(This is a lie) -30 -15 0 15 30 45 60 April May June July August

Slide 15

Slide 15 text

(Also a lie) 7% 8% 11% 11% 31% 32%

Slide 16

Slide 16 text

🥰

Slide 17

Slide 17 text

$ gem install dead_end

Slide 18

Slide 18 text

> 3 module Cutlass 4 RSpec.describe Cutlass::BashResult do ❯ 5 it "preserves stdout, stderr, and status" ❯ 19 end 21 it "success?" do 52 end 53 end 54 end DeadEnd: Unmatched `end` detected

Slide 19

Slide 19 text

> 1 module Cutlass 2 RSpec.describe Cutlass::BashResult do ❯ 3 it "preserves stdout, stderr, and status" do 5 it "success?" do 6 end 7 end 8 end DeadEnd: Missing `end` detected

Slide 20

Slide 20 text

> 1 class Cat 2 def to_json ❯ 3 hash = { ❯ 4 name: name, ❯ 5 fur_color: fur_color, ❯ 6 type: 'Cat', ❯ 7 better_than_dog: false ❯ 8 hash.to_json 9 end 10 end DeadEnd: Unmatched `}` character detected

Slide 21

Slide 21 text

> 1 class Animal 2 def self.ones_my_three_year_old_likes ❯ 3 array = ❯ 4 Dog.new, ❯ 5 Lion.new, ❯ 6 Tiger.new, ❯ 7 Aligator.new, ❯ 8 ] 10 return array 11 end 12 end DeadEnd: Unmatched `[` detected

Slide 22

Slide 22 text

> 1 class Cat 2 def meow ❯ 3 Animal.call do |a ❯ 5 end 6 end 7 end DeadEnd: Unmatched `|` character detected

Slide 23

Slide 23 text

> 1 class Cat 2 def meow ❯ 3 Animal.call do |a ❯ 5 end 6 end 7 end DeadEnd: Unmatched `|` character detected ? ? ?

Slide 24

Slide 24 text

$ gem install dead_end

Slide 25

Slide 25 text

Syntax errors

Slide 26

Slide 26 text

Lexing & Parsing

Slide 27

Slide 27 text

AI

Slide 28

Slide 28 text

dead_end

Slide 29

Slide 29 text

Internals

Slide 30

Slide 30 text

🤚

Slide 31

Slide 31 text

@schneems

Slide 32

Slide 32 text

@schneems

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

📕 How to Open Source

Slide 35

Slide 35 text

Heroku

Slide 36

Slide 36 text

Heroku

Slide 37

Slide 37 text

NoMemoryError ScriptError LoadError SyntaxError SecurityError SignalException KeyError StandardError ArgumentError EncodingError IOError EOFError I am Exceptional

Slide 38

Slide 38 text

> Dir.chhhdir("/tmp") do puts `ls` end

Slide 39

Slide 39 text

> Dir.chhhdir("/tmp") do puts `ls` end # unde fi ned method `chhhdir' for # Dir:Class (NoMethodError ) # Did you mean? chdir

Slide 40

Slide 40 text

>

Slide 41

Slide 41 text

require 'bundler' require 'date' require 'optparse' require 'ostruct' require 'shellwords' class Rexe VERSION = '1.5.1' PROJECT_URL = 'https://github.com/keithrbennett/rexe' module Helpers # Try executing code. If error raised, print message (but not stack trace) & exit -1. def try begin

Slide 42

Slide 42 text

end def bundler_run(&block) # This used to be an unconditional call to with_clean_env but that method is now deprecated: # [DEPRECATED] `Bundler.with_clean_env` has been deprecated in favor of `Bundler.with_unbundled_env`. # If you instead want the environment before bundler was originally loaded, # use `Bundler.with_original_env` if Bundler.respond_to?(:with_unbundled_env) Bundler.with_unbundled_env { block.call } else Bundler.with_clean_env { block.call } end end bundler_run { Rexe::Main.new.call }

Slide 43

Slide 43 text

if / end unless / end while / end until / end def / end for / end begin / end class / end module / end do / end

Slide 44

Slide 44 text

> Dir.chdir("lol") puts `ls` end ☠☠☠

Slide 45

Slide 45 text

> Dir.chdir("lol") puts `ls` end ??

Slide 46

Slide 46 text

> Dir.chdir("/tmp") Dir.chdir("lol") do puts `ls` end

Slide 47

Slide 47 text

> def bark puts "woof" end bark() # => "woof"

Slide 48

Slide 48 text

> def bark puts "woof" end bark do puts "not a syntax error" end # => "woof"

Slide 49

Slide 49 text

> expect(WebMock).to have_requested( :post, "localhost:#{port}" ). end $ ruby -wc scratch.rb Syntax OK

Slide 50

Slide 50 text

📓

Slide 51

Slide 51 text

Lexing & Parsing

Slide 52

Slide 52 text

"hello world" Ripper.lex >

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

"hello world" Ripper.lex [ [[1, 0], :on_tstring_beg, "\"", BEG], [[1, 1], :on_tstring_content, "hello world", BEG], [[1, 12], :on_tstring_end, "\"", END], [[1, 13], :on_nl, "\n", BEG] ] >

Slide 55

Slide 55 text

[ [[1, 0], :on_tstring_beg, "\"", BEG], [[1, 1], :on_tstring_content, "hello world", BEG], [[1, 12], :on_tstring_end, "\"", END], [[1, 13], :on_nl, "\n", BEG] ]

Slide 56

Slide 56 text

[ [[1, 0], :on_tstring_beg, "\"", BEG], [[1, 1], :on_tstring_content, "hello world", BEG], [[1, 12], :on_tstring_end, "\"", END], [[1, 13], :on_nl, "\n", BEG] ]

Slide 57

Slide 57 text

[ [[1, 0], :on_tstring_beg, "\"", BEG], [[1, 1], :on_tstring_content, "hello world", BEG], [[1, 12], :on_tstring_end, "\"", END], [[1, 13], :on_nl, "\n", BEG] ]

Slide 58

Slide 58 text

while b != 0 if a > b a = a - b else b = b - a end end return a Ripper.parse

Slide 59

Slide 59 text

while b != 0 # if a > b a = a - b else b = b - a end end return a Ripper.parse :(

Slide 60

Slide 60 text

🤔

Slide 61

Slide 61 text

while b != 0 # if a > b a = a - b else b = b - a end end return a Ripper.parse :(

Slide 62

Slide 62 text

while b != 0 # if a > b # a = a - b # else # b = b - a # end end return a Ripper.parse :)

Slide 63

Slide 63 text

while b != 0 # if a > b # a = a - b # else # b = b - a # end end return a Ripper.parse :)

Slide 64

Slide 64 text

AI

Slide 65

Slide 65 text

Artificial Intelligence Algorithm

Slide 66

Slide 66 text

Artificial Intelligence Algorithm

Slide 67

Slide 67 text

Pathfinding Credit: Factorio 'New pathfinding algorithm'

Slide 68

Slide 68 text

Credit @redblobgames: Introduction to the A-star Algorithm

Slide 69

Slide 69 text

Pathfinding Credit: Factorio 'New pathfinding algorithm'

Slide 70

Slide 70 text

Tree
 Search
 "Reti Draw" Endgame Chess problem by Richard Reti (1921)

Slide 71

Slide 71 text

Tree
 Search
 BOTVINNIK, M.M. (1984). "Computers in Chess Solving Inexact Search Problems"

Slide 72

Slide 72 text

Non-optimal Play

Slide 73

Slide 73 text

No content

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

No content

Slide 76

Slide 76 text

No content

Slide 77

Slide 77 text

No content

Slide 78

Slide 78 text

No content

Slide 79

Slide 79 text

No content

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

No content

Slide 82

Slide 82 text

No content

Slide 83

Slide 83 text

No content

Slide 84

Slide 84 text

No content

Slide 85

Slide 85 text

No content

Slide 86

Slide 86 text

No content

Slide 87

Slide 87 text

Checkmate Black

Slide 88

Slide 88 text

Optimal Draw Play

Slide 89

Slide 89 text

No content

Slide 90

Slide 90 text

No content

Slide 91

Slide 91 text

No content

Slide 92

Slide 92 text

No content

Slide 93

Slide 93 text

No content

Slide 94

Slide 94 text

No content

Slide 95

Slide 95 text

No content

Slide 96

Slide 96 text

No content

Slide 97

Slide 97 text

Draw

Slide 98

Slide 98 text

Tree
 Search
 BOTVINNIK, M.M. (1984). "Computers in Chess Solving Inexact Search Problems"

Slide 99

Slide 99 text

AI ~ Search

Slide 100

Slide 100 text

No content

Slide 101

Slide 101 text

while b != 0 if a > b a = a - b else b = b - a end end return a Ripper.parse

Slide 102

Slide 102 text

while b != 0 # if a > b a = a - b else b = b - a end end return a Ripper.parse

Slide 103

Slide 103 text

❌🌳💔

Slide 104

Slide 104 text

> Dir.chdir("/tmp") Dir.chdir("lol") end do??? do??? puts `ls`

Slide 105

Slide 105 text

> Dir.chdir("/tmp") Dir.chdir("lol") end do puts `ls`🥰

Slide 106

Slide 106 text

while b != 0 # if a > b # a = a - b # else # b = b - a # end end return a Ripper.parse

Slide 107

Slide 107 text

🌳 ❌ 💔

Slide 108

Slide 108 text

demo

Slide 109

Slide 109 text

module SyntaxErrorSearch # Used for formatting invalid blocks class DisplayInvalidBlocks attr_reader :filename def initialize(block_array, io: $stderr, filename: nil) @filename = filename @io = io @blocks = block_array @lines = @blocks.map(&:lines).flatten @digit_count = @lines.last.line_number.to_s.length @code_lines = @blocks.first.code_lines @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} end def call 64: syntax error, unexpected end-of-input, expecting `end' 📕 ❌

Slide 110

Slide 110 text

51 if line.empty? 52 "#{number.to_s}#{line}" 53 else 54 string = String.new 55 string << "\e[1;3m" if @invalid_line_hash[line] # Bold, italics 56 string << "#{number.to_s} " 57 string << line.to_s 58 string << "\e[0m" 59 string 60 end 61 end.join 62 end 63 end 64 end

Slide 111

Slide 111 text

50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? 52 "#{number.to_s}#{line}" 53 else 54 string = String.new 55 string << "\e[1;3m" if @invalid_line_hash[line] # Bold, italics 56 string << "#{number.to_s} " 57 string << line.to_s 58 string << "\e[0m" ❯ 59 string 60 end 61 end.join 62 end 63 end 64 end

Slide 112

Slide 112 text

50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? 52 "#{number.to_s}#{line}" 53 else 54 string = String.new 55 string << "\e[1;3m" if @invalid_line_hash[line] # Bold, italics 56 string << "#{number.to_s} " 57 string << line.to_s ❯ 58 string << "\e[0m" ❯ 59 string 60 end 61 end.join 62 end 63 end 64 end

Slide 113

Slide 113 text

50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? 52 "#{number.to_s}#{line}" 53 else 54 string = String.new 55 string << "\e[1;3m" if @invalid_line_hash[line] # Bold, italics 56 string << "#{number.to_s} " ❯ 57 string << line.to_s ❯ 58 string << "\e[0m" ❯ 59 string 60 end 61 end.join 62 end 63 end 64 end

Slide 114

Slide 114 text

50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? 52 "#{number.to_s}#{line}" 53 else 54 string = String.new 55 string << "\e[1;3m" if @invalid_line_hash[line] # Bold, italics ❯ 56 string << "#{number.to_s} " ❯ 57 string << line.to_s ❯ 58 string << "\e[0m" ❯ 59 string 60 end 61 end.join 62 end 63 end 64 end

Slide 115

Slide 115 text

50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? 52 "#{number.to_s}#{line}" 53 else 54 string = String.new ❯ 55 string << "\e[1;3m" if @invalid_line_hash[line] # Bold, italics ❯ 56 string << "#{number.to_s} " ❯ 57 string << line.to_s ❯ 58 string << "\e[0m" ❯ 59 string 60 end 61 end.join 62 end 63 end 64 end

Slide 116

Slide 116 text

50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? 52 "#{number.to_s}#{line}" 53 else ❯ 54 string = String.new ❯ 55 string << "\e[1;3m" if @invalid_line_hash[line] # Bold, italics ❯ 56 string << "#{number.to_s} " ❯ 57 string << line.to_s ❯ 58 string << "\e[0m" ❯ 59 string 60 end 61 end.join 62 end 63 end 64 end

Slide 117

Slide 117 text

50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? 52 "#{number.to_s}#{line}" 53 else 60 end 61 end.join 62 end 63 end 64 end 📕 ❌

Slide 118

Slide 118 text

42 string << code_with_lines 43 string << "```\n" 44 string 45 end 46 47 def code_with_lines 48 @code_lines.map do |line| 49 next if line.hidden? 50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? ❯ 52 "#{number.to_s}#{line}" 53 else 60 end 61 end.join 62 end 63 end 64 end

Slide 119

Slide 119 text

42 string << code_with_lines 43 string << "```\n" 44 string 45 end 46 47 def code_with_lines 48 @code_lines.map do |line| 49 next if line.hidden? 50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? 53 else 60 end 61 end.join 62 end 63 end 64 end 📕 ❌

Slide 120

Slide 120 text

42 string << code_with_lines 43 string << "```\n" 44 string 45 end 46 47 def code_with_lines 48 @code_lines.map do |line| 49 next if line.hidden? 50 number = line.line_number.to_s.rjust(@digit_count) ❯ 51 if line.empty? ❯ 53 else ❯ 60 end 61 end.join 62 end 63 end 64 end

Slide 121

Slide 121 text

42 string << code_with_lines 43 string << "```\n" 44 string 45 end 46 47 def code_with_lines 48 @code_lines.map do |line| 49 next if line.hidden? 50 number = line.line_number.to_s.rjust(@digit_count) 61 end.join 62 end 63 end 64 end 📕 ❌

Slide 122

Slide 122 text

42 string << code_with_lines 43 string << "```\n" 44 string 45 end 46 47 def code_with_lines 48 @code_lines.map do |line| ❯ 49 next if line.hidden? ❯ 50 number = line.line_number.to_s.rjust(@digit_count) 61 end.join 62 end 63 end 64 end

Slide 123

Slide 123 text

42 string << code_with_lines 43 string << "```\n" 44 string 45 end 46 47 def code_with_lines 48 @code_lines.map do |line| 61 end.join 62 end 63 end 64 end 📕 ❌

Slide 124

Slide 124 text

42 string << code_with_lines 43 string << "```\n" 44 string 45 end 46 47 def code_with_lines ❯ 48 @code_lines.map do |line| ❯ 61 end.join 62 end 63 end 64 end

Slide 125

Slide 125 text

42 string << code_with_lines 43 string << "```\n" 44 string 45 end 46 47 def code_with_lines 62 end 63 end 64 end 📕 ❌

Slide 126

Slide 126 text

42 string << code_with_lines 43 string << "```\n" 44 string 45 end 46 ❯ 47 def code_with_lines ❯ 62 end 63 end 64 end

Slide 127

Slide 127 text

40 string << "```\n" 41 string << "#".rjust(@digit_count) + " filename: #{filename}\n\n" if filename 42 string << code_with_lines 43 string << "```\n" 44 string 45 end 46 63 end 64 end 📕 ❌

Slide 128

Slide 128 text

13 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 35 36 def filename 37 38 def code_with_filename 39 string = String.new("") 40 string << "```\n" 41 string << "#".rjust(@digit_count) + " filename: #{filename}\n\n" if filename 42 string << code_with_lines 43 string << "```\n" 44 string 45 end 46 63 end 64 end

Slide 129

Slide 129 text

13 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 35 36 def filename 37 38 def code_with_filename 39 string = String.new("") 40 string << "```\n" 41 string << "#".rjust(@digit_count) + " filename: #{filename}\n\n" if filename 42 string << code_with_lines 43 string << "```\n" ❯ 44 string 45 end 46 63 end 64 end

Slide 130

Slide 130 text

13 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 35 36 def filename 37 38 def code_with_filename 39 string = String.new("") 40 string << "```\n" 41 string << "#".rjust(@digit_count) + " filename: #{filename}\n\n" if filename 42 string << code_with_lines ❯ 43 string << "```\n" ❯ 44 string 45 end 46 63 end 64 end

Slide 131

Slide 131 text

13 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 35 36 def filename 37 38 def code_with_filename 39 string = String.new("") 40 string << "```\n" 41 string << "#".rjust(@digit_count) + " filename: #{filename}\n\n" if filename ❯ 42 string << code_with_lines ❯ 43 string << "```\n" ❯ 44 string 45 end 46 63 end 64 end

Slide 132

Slide 132 text

13 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 35 36 def filename 37 38 def code_with_filename 39 string = String.new("") 40 string << "```\n" ❯ 41 string << "#".rjust(@digit_count) + " filename: #{filename}\n\n" if filename ❯ 42 string << code_with_lines ❯ 43 string << "```\n" ❯ 44 string 45 end 46 63 end 64 end

Slide 133

Slide 133 text

13 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 35 36 def filename 37 38 def code_with_filename 39 string = String.new("") ❯ 40 string << "```\n" ❯ 41 string << "#".rjust(@digit_count) + " filename: #{filename}\n\n" if filename ❯ 42 string << code_with_lines ❯ 43 string << "```\n" ❯ 44 string 45 end 46 63 end 64 end

Slide 134

Slide 134 text

13 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 35 36 def filename 37 38 def code_with_filename ❯ 39 string = String.new("") ❯ 40 string << "```\n" ❯ 41 string << "#".rjust(@digit_count) + " filename: #{filename}\n\n" if filename ❯ 42 string << code_with_lines ❯ 43 string << "```\n" ❯ 44 string 45 end 46 63 end 64 end

Slide 135

Slide 135 text

13 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 35 36 def filename 37 38 def code_with_filename 45 end 46 63 end 64 end 📕 ❌

Slide 136

Slide 136 text

1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename 5 6 def initialize(block_array, io: $stderr, filename: nil) 7 @filename = filename 8 @io = io 9 @blocks = block_array 10 @lines = @blocks.map(&:lines).flatten 11 @digit_count = @lines.last.line_number.to_s.length 12 @code_lines = @blocks.first.code_lines 13 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 35 36 def filename 37

Slide 137

Slide 137 text

1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename 5 6 def initialize(block_array, io: $stderr, filename: nil) 7 @filename = filename 8 @io = io 9 @blocks = block_array 10 @lines = @blocks.map(&:lines).flatten 11 @digit_count = @lines.last.line_number.to_s.length 12 @code_lines = @blocks.first.code_lines 13 ❯ 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 35 36 def filename 37

Slide 138

Slide 138 text

1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename 5 6 def initialize(block_array, io: $stderr, filename: nil) 7 @filename = filename 8 @io = io 9 @blocks = block_array 10 @lines = @blocks.map(&:lines).flatten 11 @digit_count = @lines.last.line_number.to_s.length ❯ 12 @code_lines = @blocks.first.code_lines ❯ 13 ❯ 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 35 36 def filename

Slide 139

Slide 139 text

1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename 5 6 def initialize(block_array, io: $stderr, filename: nil) 7 @filename = filename 8 @io = io 9 @blocks = block_array 10 @lines = @blocks.map(&:lines).flatten ❯ 11 @digit_count = @lines.last.line_number.to_s.length ❯ 12 @code_lines = @blocks.first.code_lines ❯ 13 ❯ 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 35 36 def filename

Slide 140

Slide 140 text

1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename 5 6 def initialize(block_array, io: $stderr, filename: nil) 7 @filename = filename 8 @io = io 9 @blocks = block_array ❯ 10 @lines = @blocks.map(&:lines).flatten ❯ 11 @digit_count = @lines.last.line_number.to_s.length ❯ 12 @code_lines = @blocks.first.code_lines ❯ 13 ❯ 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 35 36 def filename

Slide 141

Slide 141 text

1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename 5 6 def initialize(block_array, io: $stderr, filename: nil) 7 @filename = filename 8 @io = io ❯ 9 @blocks = block_array ❯ 10 @lines = @blocks.map(&:lines).flatten ❯ 11 @digit_count = @lines.last.line_number.to_s.length ❯ 12 @code_lines = @blocks.first.code_lines ❯ 13 ❯ 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 35 36 def filename

Slide 142

Slide 142 text

1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename 5 6 def initialize(block_array, io: $stderr, filename: nil) 7 @filename = filename ❯ 8 @io = io ❯ 9 @blocks = block_array ❯ 10 @lines = @blocks.map(&:lines).flatten ❯ 11 @digit_count = @lines.last.line_number.to_s.length ❯ 12 @code_lines = @blocks.first.code_lines ❯ 13 ❯ 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 35 36 def filename

Slide 143

Slide 143 text

1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename 5 6 def initialize(block_array, io: $stderr, filename: nil) ❯ 7 @filename = filename ❯ 8 @io = io ❯ 9 @blocks = block_array ❯ 10 @lines = @blocks.map(&:lines).flatten ❯ 11 @digit_count = @lines.last.line_number.to_s.length ❯ 12 @code_lines = @blocks.first.code_lines ❯ 13 ❯ 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 35 36 def filename

Slide 144

Slide 144 text

1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename 5 6 def initialize(block_array, io: $stderr, filename: nil) 15 end 16 35 36 def filename 37 38 def code_with_filename 45 end 46 63 end 64 end 📕 ❌

Slide 145

Slide 145 text

1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename 5 6 def initialize(block_array, io: $stderr, filename: nil) 15 end 16 35 36 def filename 37 ❯ 38 def code_with_filename ❯ 45 end 46 63 end 64 end

Slide 146

Slide 146 text

1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename 5 6 def initialize(block_array, io: $stderr, filename: nil) 15 end 16 35 36 def filename 37 46 63 end 64 end 📕 ❌

Slide 147

Slide 147 text

1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename 5 ❯ 6 def initialize(block_array, io: $stderr, filename: nil) ❯ 15 end 16 35 36 def filename 37 46 63 end 64 end

Slide 148

Slide 148 text

📕 ❌ 1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename 5 16 35 36 def filename 37 46 63 end 64 end

Slide 149

Slide 149 text

1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename ❯ 5 ❯ 16 ❯ 35 ❯ 36 def filename ❯ 37 ❯ 46 63 end 64 end

Slide 150

Slide 150 text

1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename 63 end 64 end 📕 ✅

Slide 151

Slide 151 text

Syntax O K 🥰🥰🥰 1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename 63 end 64 end 📕 ✅

Slide 152

Slide 152 text

DeadEnd: Missing `end` detected This code has a missing `end`. Ensure that all syntax keywords (`def`, `do`, etc.) have a matching `end`. file: /Users/rschneeman/Documents/projects/dead_end/spec/fixtures/this_project_extra_def.rb.txt simplified: 1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename ❯ 36 def filename ❯ 38 def code_with_filename ❯ 45 end 63 end 64 end

Slide 153

Slide 153 text

Internals

Slide 154

Slide 154 text

module Kernel module_function def require(file) dead_end_original_require(file) rescue SyntaxError => e DeadEnd.handle_error(e) end end dead_end/auto.rb

Slide 155

Slide 155 text

# ... Timeout.timeout(timeout) do record_dir ||= ENV["DEBUG"] ? "tmp" : nil search = CodeSearch.new(source, record_dir: record_dir).call end # ... dead_end/internals.rb

Slide 156

Slide 156 text

# ... Timeout.timeout(timeout) do record_dir ||= ENV["DEBUG"] ? "tmp" : nil search = CodeSearch.new(source, record_dir: record_dir).call end # ...

Slide 157

Slide 157 text

# # ## Syntax error detection # # When the frontier holds the syntax error, we can stop searching # # search = CodeSearch.new(<<~EOM) # def dog # def lol # end # EOM # # search.call # # search.invalid_blocks.map(&:to_s) # => # # => ["def lol\n"] # code_search.rb

Slide 158

Slide 158 text

end # Main search loop def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end @invalid_blocks.concat(frontier.detect_invalid_blocks ) code_search.rb

Slide 159

Slide 159 text

end # Main search loop def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end code_search.rb

Slide 160

Slide 160 text

end # Main search loop def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end code_search.rb 📕 ✅ 📕 ❌

Slide 161

Slide 161 text

> 1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename ❯ 5 ❯ 16 ❯ 35 ❯ 36 def filename ❯ 37 ❯ 46 63 end 64 end 📕 ❌

Slide 162

Slide 162 text

> 1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename 63 end 64 end 📕 ✅

Slide 163

Slide 163 text

# ... end module DeadEnd # ... def self.invalid?(source) source = source.join if source.is_a?(Array) source = source.to_s Ripper.new(source).tap(&:parse).error? end # ... end dead_end.rb

Slide 164

Slide 164 text

> 47 def code_with_lines 48 @code_lines.map do |line| 49 next if line.hidden? 50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? 52 "#{number.to_s}#{line}" 53 else 54 string = String.new 55 string << "\e[1;3m" if @invalid_line_hash[line] # Bold, italics 56 string << "#{number.to_s} " 57 string << line.to_s 58 string << "\e[0m" 59 string 60 end 61 end.join 62 end 63 end 64 end

Slide 165

Slide 165 text

end # Main search loop def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end code_search.rb

Slide 166

Slide 166 text

end # Main search loop def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end @invalid_blocks.concat(frontier.detect_invalid_blocks ) code_search.rb

Slide 167

Slide 167 text

def expand? return false if @frontier.empty? # ... # Expand all blocks before moving to unvisited lines frontier_indent >= unvisited_indent end code_frontier.rb

Slide 168

Slide 168 text

def expand? return false if @frontier.empty? # ... # Expand all blocks before moving to unvisited lines frontier_indent >= unvisited_indent end code_frontier.rb

Slide 169

Slide 169 text

end # Main search loop def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end @invalid_blocks.concat(frontier.detect_invalid_blocks ) code_search.rb

Slide 170

Slide 170 text

end # Main search loop def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end @invalid_blocks.concat(frontier.detect_invalid_blocks ) code_search.rb

Slide 171

Slide 171 text

# Parses the most indented lines into blocks that are marked # and added to the frontier def visit_new_blocks max_indent = frontier.next_indent_line&.indent while (line = frontier.next_indent_line) && (line.indent == max_indent) @parse_blocks_from_indent_line.each_neighbor_block(frontier.next_indent_line) do |block| record(block: block, name: "add") block.mark_invisible if block.valid? push(block, name: "add") end end end code_search.rb

Slide 172

Slide 172 text

> 47 def code_with_lines 48 @code_lines.map do |line| 49 next if line.hidden? 50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? 52 "#{number.to_s}#{line}" 53 else 54 string = String.new 55 string << "\e[1;3m" if @invalid_line_hash[line] # Bold, italics 56 string << "#{number.to_s} " 57 string << line.to_s 58 string << "\e[0m" ❯ 59 string 60 end 61 end.join 62 end 63 end 64 end

Slide 173

Slide 173 text

> 47 def code_with_lines 48 @code_lines.map do |line| 49 next if line.hidden? 50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? 52 "#{number.to_s}#{line}" 53 else 54 string = String.new 55 string << "\e[1;3m" if @invalid_line_hash[line] # Bold, italics 56 string << "#{number.to_s} " 57 string << line.to_s ❯ 58 string << "\e[0m" ❯ 59 string 60 end 61 end.join 62 end 63 end 64 end

Slide 174

Slide 174 text

> 47 def code_with_lines 48 @code_lines.map do |line| 49 next if line.hidden? 50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? 52 "#{number.to_s}#{line}" 53 else 54 string = String.new 55 string << "\e[1;3m" if @invalid_line_hash[line] # Bold, italics 56 string << "#{number.to_s} " ❯ 57 string << line.to_s ❯ 58 string << "\e[0m" ❯ 59 string 60 end 61 end.join 62 end 63 end 64 end

Slide 175

Slide 175 text

> 47 def code_with_lines 48 @code_lines.map do |line| 49 next if line.hidden? 50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? 52 "#{number.to_s}#{line}" 53 else 54 string = String.new 55 string << "\e[1;3m" if @invalid_line_hash[line] # Bold, italics ❯ 56 string << "#{number.to_s} " ❯ 57 string << line.to_s ❯ 58 string << "\e[0m" ❯ 59 string 60 end 61 end.join 62 end 63 end 64 end

Slide 176

Slide 176 text

> 47 def code_with_lines 48 @code_lines.map do |line| 49 next if line.hidden? 50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? 52 "#{number.to_s}#{line}" 53 else 54 string = String.new ❯ 55 string << "\e[1;3m" if @invalid_line_hash[line] # Bold, italics ❯ 56 string << "#{number.to_s} " ❯ 57 string << line.to_s ❯ 58 string << "\e[0m" ❯ 59 string 60 end 61 end.join 62 end 63 end 64 end

Slide 177

Slide 177 text

> 47 def code_with_lines 48 @code_lines.map do |line| 49 next if line.hidden? 50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? 52 "#{number.to_s}#{line}" 53 else ❯ 54 string = String.new ❯ 55 string << "\e[1;3m" if @invalid_line_hash[line] # Bold, italics ❯ 56 string << "#{number.to_s} " ❯ 57 string << line.to_s ❯ 58 string << "\e[0m" ❯ 59 string 60 end 61 end.join 62 end 63 end 64 end

Slide 178

Slide 178 text

# Parses the most indented lines into blocks that are marked # and added to the frontier def visit_new_blocks max_indent = frontier.next_indent_line&.indent while (line = frontier.next_indent_line) && (line.indent == max_indent) @parse_blocks_from_indent_line.each_neighbor_block(frontier.next_indent_line) do |block| record(block: block, name: "add") block.mark_invisible if block.valid? push(block, name: "add") end end end code_search.rb

Slide 179

Slide 179 text

# Parses the most indented lines into blocks that are marked # and added to the frontier def visit_new_blocks max_indent = frontier.next_indent_line&.indent while (line = frontier.next_indent_line) && (line.indent == max_indent) @parse_blocks_from_indent_line.each_neighbor_block(frontier.next_indent_line) do |block| record(block: block, name: "add") block.mark_invisible if block.valid? push(block, name: "add") end end end code_search.rb

Slide 180

Slide 180 text

> 47 def code_with_lines 48 @code_lines.map do |line| 49 next if line.hidden? 50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? 52 "#{number.to_s}#{line}" 53 else ❯ 54 string = String.new ❯ 55 string << "\e[1;3m" if @invalid_line_hash[line] # Bold, italics ❯ 56 string << "#{number.to_s} " ❯ 57 string << line.to_s ❯ 58 string << "\e[0m" ❯ 59 string 60 end 61 end.join 62 end 63 end 64 end

Slide 181

Slide 181 text

> 47 def code_with_lines 48 @code_lines.map do |line| 49 next if line.hidden? 50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? 52 "#{number.to_s}#{line}" 53 else 60 end 61 end.join 62 end 63 end 64 end

Slide 182

Slide 182 text

# Parses the most indented lines into blocks that are marked # and added to the frontier def visit_new_blocks max_indent = frontier.next_indent_line&.indent while (line = frontier.next_indent_line) && (line.indent == max_indent) @parse_blocks_from_indent_line.each_neighbor_block(frontier.next_indent_line) do |block| record(block: block, name: "add") block.mark_invisible if block.valid? push(block, name: "add") end end end code_search.rb

Slide 183

Slide 183 text

sweep(block: block, name: "comments") end # Main search loop def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end code_search.rb

Slide 184

Slide 184 text

sweep(block: block, name: "comments") end # Main search loop def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end code_search.rb

Slide 185

Slide 185 text

sweep(block: block, name: "comments") end # Main search loop def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end code_search.rb

Slide 186

Slide 186 text

> 43 string << "```\n" 44 string 45 end 46 47 def code_with_lines 48 @code_lines.map do |line| 49 next if line.hidden? 50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? ❯ 52 "#{number.to_s}#{line}" 53 else 60 end 61 end.join 62 end 63 end 64 end

Slide 187

Slide 187 text

> 43 string << "```\n" 44 string 45 end 46 47 def code_with_lines 48 @code_lines.map do |line| 49 next if line.hidden? 50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? 53 else 60 end 61 end.join 62 end 63 end 64 end

Slide 188

Slide 188 text

sweep(block: block, name: "comments") end # Main search loop def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end code_search.rb

Slide 189

Slide 189 text

code_search.rb sweep(block: block, name: "comments") end # Main search loop def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end

Slide 190

Slide 190 text

end end # Given an already existing block in the frontier, expand it to see # if it contains our invalid syntax def expand_invalid_block block = frontier.pop return unless block record(block: block, name: "pop") block = @block_expand.call(block) push(block, name: "expand") end def sweep_heredocs code_search.rb

Slide 191

Slide 191 text

end end # Given an already existing block in the frontier, expand it to see # if it contains our invalid syntax def expand_invalid_block block = frontier.pop return unless block record(block: block, name: "pop") block = @block_expand.call(block) push(block, name: "expand") end def sweep_heredocs code_search.rb

Slide 192

Slide 192 text

end end # Given an already existing block in the frontier, expand it to see # if it contains our invalid syntax def expand_invalid_block block = frontier.pop return unless block record(block: block, name: "pop") block = @block_expand.call(block) push(block, name: "expand") end def sweep_heredocs code_search.rb

Slide 193

Slide 193 text

# frozen_string_literal: true module DeadEnd # This class is responsible for taking a code block that exists # at a far indentaion and then iteratively increasing the block # so that it captures everything within the same indentation block. # # def dog # puts "bow" # puts "wow" # end # # block = BlockExpand.new(code_lines: code_lines) # .call(CodeBlock.new(lines: code_lines[1])) # # puts block.to_s # # => puts "bow" block_expand.rb

Slide 194

Slide 194 text

# puts "wow" # end # class BlockExpand def initialize(code_lines: ) @code_lines = code_lines end def call(block) if (next_block = expand_neighbors(block, grab_empty: true)) return next_block end expand_indent(block) end def expand_indent(block) block_expand.rb

Slide 195

Slide 195 text

def expand_indent(block) block = AroundBlockScan.new(code_lines: @code_lines, block: block) .skip(:hidden?) .stop_after_kw .scan_adjacent_indent .code_block end def expand_neighbors(block, grab_empty: true) scan = AroundBlockScan.new(code_lines: @code_lines, block: block) .skip(:hidden?) .stop_after_kw .scan_neighbors # Slurp up empties block_expand.rb

Slide 196

Slide 196 text

def expand_indent(block) block = AroundBlockScan.new(code_lines: @code_lines, block: block) .skip(:hidden?) .stop_after_kw .scan_adjacent_indent .code_block end def expand_neighbors(block, grab_empty: true) scan = AroundBlockScan.new(code_lines: @code_lines, block: block) .skip(:hidden?) .stop_after_kw .scan_neighbors # Slurp up empties block_expand.rb

Slide 197

Slide 197 text

def expand_indent(block) block = AroundBlockScan.new(code_lines: @code_lines, block: block) .skip(:hidden?) .stop_after_kw .scan_adjacent_indent .code_block end def expand_neighbors(block, grab_empty: true) scan = AroundBlockScan.new(code_lines: @code_lines, block: block) .skip(:hidden?) .stop_after_kw .scan_neighbors # Slurp up empties block_expand.rb

Slide 198

Slide 198 text

def expand_indent(block) block = AroundBlockScan.new(code_lines: @code_lines, block: block) .skip(:hidden?) .stop_after_kw .scan_adjacent_indent .code_block end def expand_neighbors(block, grab_empty: true) scan = AroundBlockScan.new(code_lines: @code_lines, block: block) .skip(:hidden?) .stop_after_kw .scan_neighbors # Slurp up empties block_expand.rb

Slide 199

Slide 199 text

def expand_indent(block) block = AroundBlockScan.new(code_lines: @code_lines, block: block) .skip(:hidden?) .stop_after_kw .scan_adjacent_indent .code_block end def expand_neighbors(block, grab_empty: true) scan = AroundBlockScan.new(code_lines: @code_lines, block: block) .skip(:hidden?) .stop_after_kw .scan_neighbors # Slurp up empties block_expand.rb

Slide 200

Slide 200 text

> 41 string << "#".rjust(@digit_count) + " filename: #{filename}\n\n" if filename 42 string << code_with_lines 43 string << "```\n" 44 string 45 end 46 47 def code_with_lines 48 @code_lines.map do |line| 49 next if line.hidden? 50 number = line.line_number.to_s.rjust(@digit_count) 51 if line.empty? ❯ 52 "#{number.to_s}#{line}" 53 else 60 end 61 end.join 62 end 63 end 64 end

Slide 201

Slide 201 text

> 42 string << code_with_lines 43 string << "```\n" 44 string 45 end 46 47 def code_with_lines 48 @code_lines.map do |line| 49 next if line.hidden? 50 number = line.line_number.to_s.rjust(@digit_count) ❯ 51 if line.empty? ❯ 53 else ❯ 60 end 61 end.join 62 end 63 end 64 end

Slide 202

Slide 202 text

> 42 string << code_with_lines 43 string << "```\n" 44 string 45 end 46 47 def code_with_lines 48 @code_lines.map do |line| 49 next if line.hidden? 50 number = line.line_number.to_s.rjust(@digit_count) 61 end.join 62 end 63 end 64 end

Slide 203

Slide 203 text

sweep(block: block, name: "comments") end # Main search loop def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end code_search.rb

Slide 204

Slide 204 text

sweep(block: block, name: "comments") end # Main search loop def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end code_search.rb

Slide 205

Slide 205 text

def expand_indent(block) block = AroundBlockScan.new(code_lines: @code_lines, block: block) .skip(:hidden?) .stop_after_kw .scan_adjacent_indent .code_block end def expand_neighbors(block, grab_empty: true) scan = AroundBlockScan.new(code_lines: @code_lines, block: block) .skip(:hidden?) .stop_after_kw .scan_neighbors # Slurp up empties block_expand.rb

Slide 206

Slide 206 text

code_line.rb class CodeLine TRAILING_SLASH = ("\\" + $/).freeze attr_reader :line, :index, :indent, :original_line def initialize(line: , index:) @original_line = line.freeze @line = @original_line if line.strip.empty? @empty = true @indent = 0 else @empty = false @indent = SpaceCount.indent(line) end @index = index @status = nil # valid, invalid, unknown

Slide 207

Slide 207 text

private def lex_detect! lex_array = LexAll.new(source: line) kw_count = 0 end_count = 0 lex_array.each_with_index do |lex, index| next unless lex.type == :on_kw case lex.token when 'if', 'unless', 'while', 'until' # Only count if/unless when it's not a "trailing" if/unless kw_count += 1 if !lex.expr_label? when 'def', 'case', 'for', 'begin', 'class', 'module', 'do' kw_count += 1 when 'end' end_count += 1 end code_line.rb

Slide 208

Slide 208 text

> source_code = %q{ string = <<~EOM "I am a here doc" if true EOM } Ripper.lex(source_code)

Slide 209

Slide 209 text

> source_code = %q{ string = <<~EOM "I am a here doc" if true EOM } Ripper.lex(source_code) [[[1, 0], :on_ignored_nl, "\n", BEG], [[2, 0], :on_ident, "string", CMDARG], [[2, 6], :on_sp, " ", CMDARG], [[2, 7], :on_op, "=", BEG], [[2, 8], :on_sp, " ", BEG], [[2, 9], :on_heredoc_beg, "<<~EOM", BEG], [[2, 15], :on_nl, "\n", BEG], [[3, 0], :on_tstring_content, " \"I am a here doc\" if true\n", BEG], [[4, 0], :on_tstring_content, "EOM \n", BEG]]

Slide 210

Slide 210 text

> source_code = %q{ string = <<~EOM "I am a here doc" if true EOM } Ripper.lex(source_code) [[[1, 0], :on_ignored_nl, "\n", BEG], [[2, 0], :on_ident, "string", CMDARG], [[2, 6], :on_sp, " ", CMDARG], [[2, 7], :on_op, "=", BEG], [[2, 8], :on_sp, " ", BEG], [[2, 9], :on_heredoc_beg, "<<~EOM", BEG], [[2, 15], :on_nl, "\n", BEG], [[3, 0], :on_tstring_content, " \"I am a here doc\" if true\n", BEG], [[4, 0], :on_tstring_content, "EOM \n", BEG]]

Slide 211

Slide 211 text

> source_code = %q{ string = <<~EOM "I am a here doc" if true EOM } Ripper.lex(source_code) [[[1, 0], :on_ignored_nl, "\n", BEG], [[2, 0], :on_ident, "string", CMDARG], [[2, 6], :on_sp, " ", CMDARG], [[2, 7], :on_op, "=", BEG], [[2, 8], :on_sp, " ", BEG], [[2, 9], :on_heredoc_beg, "<<~EOM", BEG], [[2, 15], :on_nl, "\n", BEG], [[3, 0], :on_tstring_content, " \"I am a here doc\" if true\n", BEG], [[4, 0], :on_tstring_content, "EOM \n", BEG]]

Slide 212

Slide 212 text

> source_code = %q{ string = <<~EOM "I am a here doc" if true EOM } Ripper.lex(source_code.lines[2])

Slide 213

Slide 213 text

> [[[1, 0], :on_sp, " ", BEG], [[1, 4], :on_tstring_beg, "\"", BEG], [[1, 5], :on_tstring_content, "I am a here doc", BEG], [[1, 20], :on_tstring_end, "\"", END], [[1, 21], :on_sp, " ", END], [[1, 22], :on_kw, "if", BEG|LABEL], [[1, 24], :on_sp, " ", BEG|LABEL], [[1, 25], :on_kw, "true", END], [[1, 29], :on_nl, "\n", BEG]] source_code = %q{ string = <<~EOM "I am a here doc" if true EOM } Ripper.lex(source_code.lines[2])

Slide 214

Slide 214 text

private def lex_detect! lex_array = LexAll.new(source: line) kw_count = 0 end_count = 0 lex_array.each_with_index do |lex, index| next unless lex.type == :on_kw case lex.token when 'if', 'unless', 'while', 'until' # Only count if/unless when it's not a "trailing" if/unless kw_count += 1 if !lex.expr_label? when 'def', 'case', 'for', 'begin', 'class', 'module', 'do' kw_count += 1 when 'end' end_count += 1 end code_line.rb

Slide 215

Slide 215 text

sweep(block: block, name: "comments") end # Main search loop def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end code_search.rb

Slide 216

Slide 216 text

sweep(block: block, name: "comments") end # Main search loop def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end code_search.rb

Slide 217

Slide 217 text

> 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 17 def call ❯ 18 @io.puts <<~EOM ❯ 19 ❯ 20 DeadEnd: A syntax error was detected ❯ 21 ❯ 22 This code has an unmatched `end` this is caused by either ❯ 23 missing a syntax keyword (`def`, `do`, etc.) or inclusion ❯ 24 of an extra `end` line: ❯ 25 EOM 26 27 @io.puts(<<~EOM) if filename 28 file: #{filename} 29 EOM 30 31 @io.puts <<~EOM

Slide 218

Slide 218 text

> 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 17 def call 26 27 @io.puts(<<~EOM) if filename 28 file: #{filename} 29 EOM 30 31 @io.puts <<~EOM 32 #{code_with_filename} 33 EOM 34 end 35 36 def filename 37 38 def code_with_filename 39 string = String.new("")

Slide 219

Slide 219 text

> 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 17 def call 26 ❯ 27 @io.puts(<<~EOM) if filename ❯ 28 file: #{filename} ❯ 29 EOM 30 31 @io.puts <<~EOM 32 #{code_with_filename} 33 EOM 34 end 35 36 def filename 37 38 def code_with_filename 39 string = String.new("")

Slide 220

Slide 220 text

> 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 17 def call 26 30 ❯ 31 @io.puts <<~EOM ❯ 32 #{code_with_filename} ❯ 33 EOM 34 end 35 36 def filename 37 38 def code_with_filename 39 string = String.new("") 40 string << "```\n" 41 string << "#".rjust(@digit_count) + " filename: #{filename}\n\n" if filename 42 string << code_with_lines

Slide 221

Slide 221 text

> 14 @invalid_line_hash = @lines.each_with_object({}) {|line, h| h[line] = true} 15 end 16 17 def call 26 30 34 end 35 36 def filename 37 38 def code_with_filename 39 string = String.new("") 40 string << "```\n" 41 string << "#".rjust(@digit_count) + " filename: #{filename}\n\n" if filename 42 string << code_with_lines 43 string << "```\n" 44 string 45 end

Slide 222

Slide 222 text

sweep(block: block, name: "comments") end # Main search loop def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end code_search.rb

Slide 223

Slide 223 text

$ dead_end bad.rb --record tmp/

Slide 224

Slide 224 text

No content

Slide 225

Slide 225 text

No content

Slide 226

Slide 226 text

sweep(block: block, name: "comments") end # Main search loop def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end code_search.rb

Slide 227

Slide 227 text

def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end @invalid_blocks.concat(frontier.detect_invalid_blocks ) @invalid_blocks.sort_by! {|block| block.starts_at } self end code_search.rb

Slide 228

Slide 228 text

# Example: # # combination([:a, :b, :c, :d]) # # => [[:a], [:b], [:c], [:d], # [:a, :b], [:a, :c], [:a, :d], # [:b, :c], [:b, :d], [:c, :d], # [:a, :b, :c], [:a, :b, :d], [:a, :c, :d], # [:b, :c, :d], # [:a, :b, :c, :d]] def self.combination(array) guesses = [] 1.upto(array.length).each do |size| guesses.concat(array.combination(size).to_a) end guesses end code_frontier.rb

Slide 229

Slide 229 text

# Example: # # combination([:a, :b, :c, :d]) # # => [[:a], [:b], [:c], [:d], # [:a, :b], [:a, :c], [:a, :d], # [:b, :c], [:b, :d], [:c, :d], # [:a, :b, :c], [:a, :b, :d], [:a, :c, :d], # [:b, :c, :d], # [:a, :b, :c, :d]] def self.combination(array) guesses = [] 1.upto(array.length).each do |size| guesses.concat(array.combination(size).to_a) end guesses end code_frontier.rb

Slide 230

Slide 230 text

# Given that we know our syntax error exists somewhere in our frontier, we want to find # the smallest possible set of blocks that contain all the syntax errors def detect_invalid_blocks self.class.combination(@frontier.select(&:invalid?)).detect do |block_array| holds_all_syntax_errors?(block_array) end || [] end end end code_frontier.rb

Slide 231

Slide 231 text

guesses end # Given that we know our syntax error exists somewhere in our frontier, we want to find # the smallest possible set of blocks that contain all the syntax errors def detect_invalid_blocks self.class.combination(@frontier.select(&:invalid?)).detect do |block_array| holds_all_syntax_errors?(block_array) end || [] end end end code_frontier.rb

Slide 232

Slide 232 text

Timeout.timeout(timeout) do record_dir ||= ENV["DEBUG"] ? "tmp" : nil search = CodeSearch.new(source, record_dir: record_dir).call end blocks = search.invalid_blocks DisplayInvalidBlocks.new( blocks: blocks, filename: filename, terminal: terminal, code_lines: search.code_lines, invalid_obj: invalid_type(source), io: io ).call dead_end/internals.rb

Slide 233

Slide 233 text

Timeout.timeout(timeout) do record_dir ||= ENV["DEBUG"] ? "tmp" : nil search = CodeSearch.new(source, record_dir: record_dir).call end blocks = search.invalid_blocks DisplayInvalidBlocks.new( blocks: blocks, filename: filename, terminal: terminal, code_lines: search.code_lines, invalid_obj: invalid_type(source), io: io ).call dead_end/internals.rb

Slide 234

Slide 234 text

Timeout.timeout(timeout) do record_dir ||= ENV["DEBUG"] ? "tmp" : nil search = CodeSearch.new(source, record_dir: record_dir).call end blocks = search.invalid_blocks DisplayInvalidBlocks.new( blocks: blocks, filename: filename, terminal: terminal, code_lines: search.code_lines, invalid_obj: invalid_type(source), io: io ).call dead_end/internals.rb

Slide 235

Slide 235 text

> DeadEnd: Missing `end` detected This code has a missing `end`. Ensure that all syntax keywords (`def`, `do`, etc.) have a matching `end`. file: /Users/rschneeman/Documents/projects/dead_end/spec/fixtures/this_project_extr simplified: 1 module SyntaxErrorSearch 3 class DisplayInvalidBlocks 4 attr_reader :filename ❯ 36 def filename ❯ 38 def code_with_filename ❯ 45 end 63 end 64 end

Slide 236

Slide 236 text

dead_end

Slide 237

Slide 237 text

Are you a dev?

Slide 238

Slide 238 text

No content

Slide 239

Slide 239 text

No content

Slide 240

Slide 240 text

Are you an open source dev?

Slide 241

Slide 241 text

No content

Slide 242

Slide 242 text

Are you a Ruby core dev?

Slide 243

Slide 243 text

❤ 🙏 🙏

Slide 244

Slide 244 text

Lexing Parsing Syntax SyntaxErrors AI Pathfinding and goal seeking

Slide 245

Slide 245 text

No content

Slide 246

Slide 246 text

No content

Slide 247

Slide 247 text

No content

Slide 248

Slide 248 text

No content

Slide 249

Slide 249 text

@schneems

Slide 250

Slide 250 text

No content

Slide 251

Slide 251 text

Questions?