fifthed_sim 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -1
  3. data/HelpWanted.md +23 -0
  4. data/README.md +144 -5
  5. data/bin/console +2 -8
  6. data/bin/playground +42 -0
  7. data/exe/diceroll +9 -0
  8. data/fifthed_sim.gemspec +4 -0
  9. data/lib/fifthed_sim/actor.rb +80 -0
  10. data/lib/fifthed_sim/attack.rb +86 -0
  11. data/lib/fifthed_sim/calculated_fixnum.rb +57 -0
  12. data/lib/fifthed_sim/compiler/parser.rb +46 -0
  13. data/lib/fifthed_sim/compiler/transform.rb +28 -0
  14. data/lib/fifthed_sim/compiler.rb +51 -0
  15. data/lib/fifthed_sim/damage.rb +54 -0
  16. data/lib/fifthed_sim/damage_types.rb +39 -0
  17. data/lib/fifthed_sim/dice_expression.rb +134 -0
  18. data/lib/fifthed_sim/distribution.rb +219 -20
  19. data/lib/fifthed_sim/nodes/addition_node.rb +75 -0
  20. data/lib/fifthed_sim/nodes/block_node.rb +46 -0
  21. data/lib/fifthed_sim/nodes/division_node.rb +26 -0
  22. data/lib/fifthed_sim/nodes/greater_node.rb +41 -0
  23. data/lib/fifthed_sim/nodes/less_node.rb +46 -0
  24. data/lib/fifthed_sim/nodes/multi_node.rb +135 -0
  25. data/lib/fifthed_sim/nodes/multiplication_node.rb +25 -0
  26. data/lib/fifthed_sim/nodes/number_node.rb +38 -0
  27. data/lib/fifthed_sim/nodes/roll_node.rb +88 -0
  28. data/lib/fifthed_sim/nodes/subtraction_node.rb +24 -0
  29. data/lib/fifthed_sim/roll_repl.rb +117 -0
  30. data/lib/fifthed_sim/spell.rb +74 -0
  31. data/lib/fifthed_sim/stat.rb +49 -0
  32. data/lib/fifthed_sim/stat_block.rb +57 -0
  33. data/lib/fifthed_sim/version.rb +3 -1
  34. data/lib/fifthed_sim.rb +28 -4
  35. metadata +74 -8
  36. data/lib/fifthed_sim/dice_calculation.rb +0 -88
  37. data/lib/fifthed_sim/dice_result.rb +0 -108
  38. data/lib/fifthed_sim/die_roll.rb +0 -66
  39. data/lib/fifthed_sim/helpers/average_comparison.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 68200fa0518a4975d872ed4583404f3a3e5a6a9f
4
- data.tar.gz: a89f5017dcfeca2e354ee3173694139ed9f3c1a8
3
+ metadata.gz: 0087fa99f564c98c6d462c20828beb2acf4146be
4
+ data.tar.gz: 26b12a81c991271aa16d25f9479b0859188f33e5
5
5
  SHA512:
6
- metadata.gz: ba637784bc60f87a409327852fc3468e901d96b7701d17ea9cc5fcb0e853be09da6f4fb79dd14d4c0b48381fc551d0f47a9b2f79eac317283c0a2678b1537c8f
7
- data.tar.gz: f828486f33596455f97f861738aa8e0646f9de188f0073cb889afa8f8566e2b512b64a47b00617cb059aa62a6347c331d926bd88497b4e05d40f7326b8c3cae3
6
+ metadata.gz: d1c996014ded703a1caa63f3be5f2a9fdedd5dace5bfb588829248aa92705a90f54c21ffe50f8890cf2be6abc930096fcb15ebeb5954b4350c9d0cbcc2ce0b16
7
+ data.tar.gz: 4eb2c588b583539b52f3a5122c1e17a95f9b0b394b80806b6cd5bf8c70196ef4fab891395d0044c870739d8791c128c450898a9948de24138022224067d1c38e
data/.travis.yml CHANGED
@@ -1,7 +1,6 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.2.2
5
4
  - 2.1.1
6
5
  - 2.3.0
7
6
  - 2.4.0
data/HelpWanted.md ADDED
@@ -0,0 +1,23 @@
1
+ # Help Wanted
2
+
3
+ This file contains a list of improvements we desire for this gem.
4
+ If you can help out, feel free to make a pull request on the [Github](https://github.com/AnthonySuper/FifthedSim)
5
+
6
+
7
+ ## Top and Bottom Nodes
8
+ Some dice games require that you roll a certain amount of dice, and take the top or bottom rolls.
9
+ For example, to apply *disadvantage* in D&D, you roll 2d20 and take the lowest D20.
10
+
11
+ For the case of choosing between two dice calculations, we use the `.or_less` and `.or_greater` methods.
12
+ Unfortunately, these are not the only types of top and bottom calculations used in games.
13
+ Rolling a stat in D&D, for example, is `4d6 (drop lowest)`.
14
+
15
+ Finding a generic distribution for this is, as it turns out, hard to do efficiently.
16
+ The simple way is to take all possible combinations of rolls, drop the lowest, count the remaining values, and create a distribution from this information.
17
+ Unfortunately, the number of combinations of rolls is `dice_type^dice_number`, which is an exponential calculation.
18
+
19
+ If you know of a better formula, we'd love to hear about it.
20
+ You can either [open an issue](https://github.com/AnthonySuper/FifthedSim/issues/new) describing it, or create a pull request yourself.
21
+
22
+ If there does not exist a better formula, we'd also love to hear about it.
23
+ You can [open an issue](https://github.com/AnthonySuper/FifthedSim/issues/new) with a link to a proof that this operation cannot be done any quicker, and we will implement the brute-force solution.
data/README.md CHANGED
@@ -1,8 +1,151 @@
1
1
  # FifthedSim
2
+ [![Build Status](https://travis-ci.org/AnthonySuper/FifthedSim.svg?branch=master)](https://travis-ci.org/AnthonySuper/FifthedSim)
3
+
2
4
 
3
5
  This is a gem to simulate a game that you play with dice on a d20 system.
4
6
  It is unfinished, but intends to enable a user to run simulations, or to see the overall probability of things which happen.
5
7
 
8
+ ## Usage
9
+
10
+ ### Executable
11
+
12
+ If you just want to roll dice, simply install the gem and type "diceroll".
13
+ This will bring up a Dice REPL, which has some nice functionality.
14
+ Just type a dice expression, and you will get a result:
15
+
16
+ ```
17
+ > d20 + 3d6
18
+ => 18 + (3 2 6)
19
+ = 29
20
+ ```
21
+ You can also get some info about a roll by typing `info`.
22
+ Type `help` to see all the commands.
23
+
24
+ ### Dice Expressions
25
+
26
+ This gem generalizes the use of dice into *DiceExpressions*, which is an expression representing a calculation done on dice.
27
+ This expression is *lazily evaluated*, which means that it does not turn into an actual numerical value until you call `.to_i` or `.value` on it.
28
+
29
+ DiceExpressions are more powerful than simply rolling dice and doing math on the results, as we can both reuse them and do statistics on them.
30
+ This allows us to figure out a variety of useful information.
31
+
32
+ Dice expressions are almost always constructed via the `Fixnum#d` method:
33
+
34
+ ```ruby
35
+ 1.d(20)
36
+ ```
37
+
38
+ From here, mathematical operations work as normal:
39
+
40
+ ```ruby
41
+ result = (1.d(20) + 3 + 2.d(6)) / (1.d(4) * 2 * 1.d(2))
42
+ ```
43
+
44
+ As mentioned earlier, we can get a numeric value from this expression:
45
+
46
+ ```ruby
47
+ result.value # -> 2
48
+ ```
49
+
50
+ We can also reroll this expression, to get another DiceExpression with a new value:
51
+
52
+ ```ruby
53
+ result.reroll.value # -> 6
54
+ ```
55
+
56
+ More interestingly, we can do statistics on this value.
57
+ We can obtain a `Distribution` of possible results for a given dice expression easily:
58
+
59
+ ```ruby
60
+ distribution = result.distribution
61
+ ```
62
+
63
+ Now, let's see how likely our value of 2 was.
64
+ Let's also see what percentile our value is in.
65
+
66
+ ```ruby
67
+ distribution.percent_exactly(2) # -> 0.199305...
68
+ distribution.percentile_of(2) # -> 0.475694...
69
+ ```
70
+
71
+ So our roll wasn't terrible, but it wasn't great as well.
72
+
73
+ #### Combinations
74
+ `DiceExpressions` are a powerful construct, because they allow combinations with arbitrary functions, while still being a `DiceExpression`.
75
+ To use an example, let's try to model the damage of a kobold's dagger attack against a player character with 12 AC.
76
+
77
+ The kobold rolls `1d20 + 4` to hit.
78
+ If he gets a 12 or higher, he does `1d4 + 2` damage to this player.
79
+ Let's model this attack:
80
+
81
+ ```ruby
82
+ attack = (1.d(20) + 4).test_then do |result|
83
+ if result < 12
84
+ 0.to_dice_expression
85
+ else
86
+ 1.d(4) + 2
87
+ end
88
+ end
89
+ ```
90
+
91
+ This is just a `DiceExpression`, so we can get its value, and reroll it:
92
+
93
+ ```ruby
94
+ attack.value # => 0, the poor guy missed
95
+ attack.reroll.value # => 3, he hit... and critfailed damage.
96
+ ```
97
+
98
+ Even more interestingly, however, we can do *statistics* on it.
99
+ Let's see the chance of him doing at least one damage:
100
+
101
+ ```ruby
102
+ attack.distribution.percent_greater(0) # => 0.65, 65% of doing damage
103
+ ```
104
+ What about the average damage per attack?
105
+
106
+ ```ruby
107
+ attack.average # => 2.925
108
+ ```
109
+
110
+ As long as the block passed to `test_then` is *pure* (IE, the same input maps to the same output, regardless of anything else that happens in the program), then we can do any kind of calculation we want inside of it.
111
+ This is useful in a variety of situations.
112
+
113
+ ### Simulation
114
+ FifthedSim was originally designed to simulate D&D 5e games.
115
+ Doing this is still under construction, but it's coming along nicely.
116
+
117
+ Simulation is based on *Actors*.
118
+ An Actor represents a character or NPC in the game.
119
+ Actors are defined with a nice DSL:
120
+
121
+ ```ruby
122
+ actor = FifthedSim.define_actor("Bobby") do
123
+ base_ac 10
124
+ stats do
125
+ str 10
126
+ dex do
127
+ value 18
128
+ save_mod_bonus 5
129
+ end
130
+ wis 8
131
+ cha 16
132
+ con 14
133
+ int 12
134
+ end
135
+ attack "rapier" do
136
+ to_hit 5
137
+ damage do
138
+ piercing 1.d(6)
139
+ end
140
+ end
141
+ end
142
+ ```
143
+
144
+ Construction based on YAML or JSON is still a work in progress.
145
+ Actors are intended to be used to simulate battles.
146
+ This is currently a work in progress, although simulating individual attacks and such *does* currently work.
147
+
148
+
6
149
  ## Installation
7
150
 
8
151
  Add this line to your application's Gemfile:
@@ -19,10 +162,6 @@ Or install it yourself as:
19
162
 
20
163
  $ gem install fifthed_sim
21
164
 
22
- ## Usage
23
-
24
- See the rdoc for now.
25
-
26
165
  ## Development
27
166
 
28
167
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -32,7 +171,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
171
  ## Contributing
33
172
 
34
173
  Bug reports and pull requests are welcome on GitHub at https://github.com/anthonysuper/fifthedsim.
35
-
174
+ Please see the [HelpWanted.md](HelpWanted.md) file for specific patches we are looking for.
36
175
 
37
176
  ## License
38
177
 
data/bin/console CHANGED
@@ -3,12 +3,6 @@
3
3
  require "bundler/setup"
4
4
  require "fifthed_sim"
5
5
 
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
6
 
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start
7
+ require "pry"
8
+ Pry.start
data/bin/playground ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "fifthed_sim"
5
+
6
+
7
+ require "pry"
8
+ require 'ostruct'
9
+ require 'csv'
10
+
11
+ iron_will = FifthedSim::Attack.define "Sword of Iron Will" do
12
+ to_hit 9
13
+ damage do
14
+ slashing 2.d(6) + 5
15
+ end
16
+ crit_threshold 19
17
+ crit_damage do
18
+ slashing 7.d(6) + 5
19
+ end
20
+ end
21
+
22
+ class FakeMonster
23
+ def initialize(ac = 18); @ac = ac; end;
24
+ def resistant_to?(_); false; end;
25
+ def immune_to?(_); false; end;
26
+ attr_accessor :ac
27
+ end
28
+
29
+
30
+ def write_csv(d, filename= "data.csv")
31
+ m = d.map
32
+ keys = m.keys.sort
33
+ CSV.open(filename, "wb") do |csv|
34
+ csv << ["Outcome", *keys]
35
+ csv << ["Chance", *keys.map{|k| m[k]}]
36
+ end
37
+ end
38
+
39
+
40
+ hit = iron_will.against(FakeMonster.new)
41
+
42
+ binding.pry
data/exe/diceroll ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "../lib"))
3
+
4
+ require 'fifthed_sim'
5
+ require 'readline'
6
+
7
+
8
+
9
+ FifthedSim::RollRepl.new.run
data/fifthed_sim.gemspec CHANGED
@@ -17,9 +17,13 @@ Gem::Specification.new do |spec|
17
17
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
18
  spec.bindir = "exe"
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.executables << 'diceroll'
20
21
  spec.require_paths = ["lib"]
21
22
 
23
+ spec.add_dependency "parslet", "~> 1.7.1"
24
+ spec.add_dependency "rainbow", "~> 2.0.0"
22
25
  spec.add_development_dependency "bundler", "~> 1.12"
23
26
  spec.add_development_dependency "rake", "~> 10.0"
24
27
  spec.add_development_dependency "rspec", "~> 3.0"
28
+ spec.add_development_dependency "pry", "~> 0.10.4"
25
29
  end
@@ -0,0 +1,80 @@
1
+ module FifthedSim
2
+ class Actor
3
+ class DefinitionProxy
4
+ def initialize(name, &block)
5
+ @attrs = {
6
+ name: name,
7
+ attacks: {},
8
+ spells: {},
9
+ base_ac: 10
10
+ }
11
+ instance_eval(&block)
12
+ end
13
+
14
+ attr_reader :attrs
15
+
16
+ def attack(name, &block)
17
+ if block_given? && name.is_a?(String)
18
+ @attrs[:attacks][name] = Attack.define(name, &block)
19
+ elsif name.is_a?(Attack)
20
+ @attrs[:attacks][name.name] << name
21
+ else
22
+ raise ArgumentError, "must be an attack"
23
+ end
24
+ end
25
+
26
+ def spell(name, &block)
27
+ if block_given? && name.is_a?(String)
28
+ @attrs[:spells][name] = Spell.define(name, &block)
29
+ elsif name.is_a?(Spell)
30
+ @attrs[:spells][name.name] << name
31
+ else
32
+ raise ArgumentError, "must be a spell"
33
+ end
34
+ end
35
+
36
+ def stats(n = nil, &block)
37
+ if n && n.is_a?(StatBlock)
38
+ @attrs[:stats] = n
39
+ elsif block
40
+ @attrs[:stats] = StatBlock.define(&block)
41
+ else
42
+ raise ArgumentError, "Must be a statblock"
43
+ end
44
+ end
45
+
46
+ def base_ac(num)
47
+ @attrs[:base_ac] = num
48
+ end
49
+ end
50
+
51
+ def self.define(name, &block)
52
+ d = DefinitionProxy.new(name, &block)
53
+ return self.new(d.attrs)
54
+ end
55
+
56
+ ASSIGNABLE_ATTRS = [:base_ac,
57
+ :name,
58
+ :attacks,
59
+ :spells]
60
+ def initialize(attrs)
61
+ attrs.to_a.keep_if{ |(k, v)| ASSIGNABLE_ATTRS.include?(v) }
62
+ .each { |(k, v)| self.instance_variable_set("@#{k}", v) }
63
+ end
64
+
65
+ def random_attack
66
+ @attacks.keys.sample
67
+ end
68
+
69
+ ##
70
+ # TODO: Implement armor
71
+ def ac
72
+ base_ac
73
+ end
74
+
75
+ attr_reader :base_ac,
76
+ :name,
77
+ :attacks,
78
+ :spells
79
+ end
80
+ end
@@ -0,0 +1,86 @@
1
+ require_relative './damage'
2
+ module FifthedSim
3
+ ##
4
+ # Model an attack vs AC
5
+ class Attack
6
+ class DefinitionProxy
7
+ def initialize(name, &block)
8
+ @attrs = {
9
+ name: name,
10
+ to_hit: 0,
11
+ damage: nil,
12
+ crit_threshold: 20,
13
+ crit_damage: nil
14
+ }
15
+ instance_eval(&block)
16
+ end
17
+
18
+ attr_reader :attrs
19
+
20
+ def to_hit(num)
21
+ @attrs[:to_hit] = num
22
+ end
23
+
24
+ def damage(arg = nil, &block)
25
+ if block_given?
26
+ @attrs[:damage] = Damage.define(&block)
27
+ else
28
+ damage_check(arg)
29
+ @attrs[:damage] = arg
30
+ end
31
+ end
32
+
33
+ def crit_damage(arg = nil, &block)
34
+ if block_given?
35
+ @attrs[:crit_damage] = Damage.define(&block)
36
+ else
37
+ damage_check(arg)
38
+ end
39
+ end
40
+
41
+ def crit_threshold(thr)
42
+ @attrs[:crit_threshold] = thr
43
+ end
44
+
45
+ protected
46
+ def damage_check(arg)
47
+ unless arg.is_a? Damage
48
+ raise TypeError, "#{arg.inspect} is not Damage"
49
+ end
50
+ end
51
+ end
52
+
53
+ def self.define(name, &block)
54
+ d = DefinitionProxy.new(name, &block)
55
+ self.new(d.attrs)
56
+ end
57
+
58
+ def initialize(attrs)
59
+ @name = attrs[:name]
60
+ @to_hit = attrs[:to_hit]
61
+ @damage = attrs[:damage]
62
+ @crit_threshold = attrs[:crit_threshold]
63
+ @crit_damage = attrs[:crit_damage]
64
+ end
65
+
66
+ def hit_roll
67
+ 1.d(20) + @to_hit
68
+ end
69
+
70
+ def against(other)
71
+ hit_roll.test_then do |res|
72
+ if res < other.ac
73
+ 0.to_dice_expression
74
+ elsif res > other.ac && res < (@crit_threshold + @to_hit)
75
+ @damage.to(other)
76
+ else
77
+ @crit_damage.to(other)
78
+ end
79
+ end
80
+ end
81
+
82
+ def raw_damage
83
+ @damage.raw
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,57 @@
1
+ module FifthedSim
2
+ module CalculatedFixnum
3
+ refine Fixnum do
4
+ def value
5
+ self
6
+ end
7
+
8
+ def average
9
+ self
10
+ end
11
+
12
+ def has_critfail?
13
+ false
14
+ end
15
+
16
+ def has_crit?
17
+ false
18
+ end
19
+
20
+ def distance_from_average
21
+ 0
22
+ end
23
+
24
+ def distribution
25
+ FifthedSim::Distribution.for_number(self)
26
+ end
27
+
28
+ def average?
29
+ true
30
+ end
31
+
32
+ def below_average?
33
+ false
34
+ end
35
+
36
+ def above_average?
37
+ false
38
+ end
39
+
40
+ def difference_from_average
41
+ 0
42
+ end
43
+
44
+ def reroll
45
+ self
46
+ end
47
+
48
+ def value_equation(terminal: false)
49
+ self.to_s
50
+ end
51
+
52
+ def expression_equation
53
+ self.to_s
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,46 @@
1
+ require 'parslet'
2
+
3
+ class FifthedSim::Compiler
4
+ class Parser < Parslet::Parser
5
+ root :addition
6
+ rule(:space) { match('\s').repeat(1) }
7
+ rule(:space?) { space.maybe }
8
+ rule(:lparen) { str("(") >> space? }
9
+ rule(:rparen) { str(")") >> space? }
10
+ rule(:comma) { str(",") >> space? }
11
+ rule(:number) { match('[0-9]').repeat(1).as(:number) }
12
+ rule(:number?) { number.maybe }
13
+ rule(:dice) do
14
+ (number?.as(:die_count) >> str("d") >> number.as(:die_type) >> space?)
15
+ .as(:dice)
16
+ end
17
+ rule(:base_term) { (dice | number) >> space? }
18
+ rule(:primary) { lparen >> addition >> rparen | base_term }
19
+
20
+ rule(:add_op) { match('\+|-').as(:op) >> space? }
21
+ rule(:addition) do
22
+ (multiplication.as(:lhs) >> add_op >> addition.as(:rhs)) |
23
+ multiplication
24
+ end
25
+
26
+ rule(:mult_op) { match('\*|/').as(:op) >> space? }
27
+
28
+ rule(:multiplication) do
29
+ (mult_term.as(:lhs) >> (mult_op >> addition.as(:rhs))) |
30
+ mult_term
31
+ end
32
+
33
+ rule(:mult_term) do
34
+ primary | funcall
35
+ end
36
+
37
+ rule(:ident) { match('[a-zA-z]').repeat(1) }
38
+
39
+ rule(:arglist) do
40
+ addition >> (comma >> addition).repeat
41
+ end
42
+
43
+ rule(:funcall) { ident.as(:ident) >> lparen >> arglist.as(:args) >> rparen }
44
+ rule(:expression) { funcall | multiplication | addition | base_term }
45
+ end
46
+ end
@@ -0,0 +1,28 @@
1
+ require 'parslet'
2
+
3
+ class FifthedSim::Compiler
4
+ class Transform < Parslet::Transform
5
+ rule(:number => simple(:x)) { Integer(x) }
6
+ rule(dice: {die_count: simple(:c), die_type: simple(:t)}) do
7
+ if c
8
+ c.d(t)
9
+ else
10
+ FifthedSim::RollNode.roll(t)
11
+ end
12
+ end
13
+ rule(op: simple(:op), lhs: simple(:lhs), rhs: simple(:rhs)) do
14
+ lhs.to_dice_expression.public_send(op, rhs.to_dice_expression)
15
+ end
16
+ FUNC_MAP = {
17
+ "max" => :or_greater,
18
+ "min" => :or_least
19
+ }
20
+ FUNC_MAP.each do |k, v|
21
+ rule(ident: k, args: sequence(:args)) do |d|
22
+ d[:args].inject do |mem, arg|
23
+ mem.to_dice_expression.public_send(v, arg)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,51 @@
1
+ module FifthedSim
2
+ class Compiler
3
+ def self.parse(str)
4
+ begin
5
+ Parser.new.parse(str)
6
+ rescue Parslet::ParseFailed => e
7
+ msg = e.message
8
+ raise CompileError.new(e)
9
+ end
10
+ end
11
+
12
+ def self.compile(str)
13
+ tree = self.parse(str)
14
+ transformed = Transform.new.apply(tree)
15
+ if transformed.is_a? DiceExpression
16
+ transformed
17
+ else
18
+ raise TransformError.new(transformed)
19
+ end
20
+ end
21
+
22
+ class CompileError < StandardError
23
+ def initialize(err)
24
+ msg = err.message
25
+ super(msg)
26
+ @line = msg.match(/line (\d+)/)[1].to_i
27
+ @char = msg.match(/char (\d+)/)[1].to_i
28
+ @tree_cause = err.cause.ascii_tree
29
+ end
30
+ attr_reader :line
31
+ attr_reader :char
32
+ attr_reader :tree_cause
33
+ end
34
+
35
+ class TransformError < CompileError
36
+ def initialize(hash)
37
+ @message = "Could not transform"
38
+ if hash.is_a? Hash
39
+ @source_hash = hash
40
+ @line, @char = hash.values.keep_if{|x| x.is_a? Parslet::Slice}
41
+ .first.line_and_column
42
+ end
43
+ end
44
+
45
+ attr_reader :source_hash, :message
46
+ end
47
+ end
48
+ end
49
+
50
+ require_relative './compiler/parser'
51
+ require_relative './compiler/transform'