csv_plus_plus 0.0.5 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +1 -0
- data/lib/csv_plus_plus/cell.rb +24 -8
- data/lib/csv_plus_plus/cli.rb +29 -16
- data/lib/csv_plus_plus/cli_flag.rb +10 -2
- data/lib/csv_plus_plus/code_section.rb +22 -3
- data/lib/csv_plus_plus/color.rb +19 -5
- data/lib/csv_plus_plus/google_options.rb +6 -2
- data/lib/csv_plus_plus/graph.rb +0 -1
- data/lib/csv_plus_plus/language/ast_builder.rb +68 -0
- data/lib/csv_plus_plus/language/benchmarked_compiler.rb +65 -0
- data/lib/csv_plus_plus/language/builtins.rb +46 -0
- data/lib/csv_plus_plus/language/cell_value.tab.rb +1 -2
- data/lib/csv_plus_plus/language/code_section.tab.rb +1 -2
- data/lib/csv_plus_plus/language/compiler.rb +74 -86
- data/lib/csv_plus_plus/language/entities/boolean.rb +3 -2
- data/lib/csv_plus_plus/language/entities/cell_reference.rb +10 -3
- data/lib/csv_plus_plus/language/entities/entity.rb +20 -8
- data/lib/csv_plus_plus/language/entities/function.rb +6 -4
- data/lib/csv_plus_plus/language/entities/function_call.rb +4 -3
- data/lib/csv_plus_plus/language/entities/number.rb +6 -4
- data/lib/csv_plus_plus/language/entities/runtime_value.rb +9 -8
- data/lib/csv_plus_plus/language/entities/string.rb +6 -4
- data/lib/csv_plus_plus/language/references.rb +22 -5
- data/lib/csv_plus_plus/language/runtime.rb +80 -22
- data/lib/csv_plus_plus/language/scope.rb +29 -38
- data/lib/csv_plus_plus/language/syntax_error.rb +10 -5
- data/lib/csv_plus_plus/lexer/lexer.rb +25 -12
- data/lib/csv_plus_plus/lexer/tokenizer.rb +35 -11
- data/lib/csv_plus_plus/modifier.rb +38 -13
- data/lib/csv_plus_plus/modifier.tab.rb +2 -2
- data/lib/csv_plus_plus/options.rb +17 -2
- data/lib/csv_plus_plus/row.rb +15 -4
- data/lib/csv_plus_plus/template.rb +10 -6
- data/lib/csv_plus_plus/version.rb +1 -1
- data/lib/csv_plus_plus/writer/excel.rb +2 -9
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +22 -20
- data/lib/csv_plus_plus/writer/google_sheet_builder.rb +8 -10
- data/lib/csv_plus_plus/writer/google_sheets.rb +4 -10
- data/lib/csv_plus_plus/writer/rubyxl_builder.rb +23 -15
- data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +15 -8
- data/lib/csv_plus_plus.rb +21 -3
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a93c3ee93f2320dc717ee330c55cd3f300cfa4d155a4502b9a433cdafdcc389
|
4
|
+
data.tar.gz: aa95dca43a374d57f2217030e0aefef680341549613b0388cb5df1122cb5f815
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19a827c9a5fc8f0f9fe9d58c4e6b85de821c9e09627465673c2813c2690fadc886c7d8f24e3da5402783b27347a79f7832b13c82abe4e204026d12839d6f4691
|
7
|
+
data.tar.gz: b157b531d9ae9e2fda9c93ed466976deacfe639b1fbc1527b60c706be9d97792946341a767f00c32b6d35cbc6e388711980767d9ef6b5c20b4a2f44ebe7fb121
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
data/lib/csv_plus_plus/cell.rb
CHANGED
@@ -4,21 +4,33 @@ require_relative './language/cell_value.tab'
|
|
4
4
|
require_relative './modifier'
|
5
5
|
|
6
6
|
module CSVPlusPlus
|
7
|
-
##
|
8
7
|
# A cell of a template
|
8
|
+
#
|
9
|
+
# @attr ast [Entity]
|
10
|
+
# @attr row_index [Integer] The cell's row index (starts at 0)
|
11
|
+
# @attr_reader index [Integer] The cell's index (starts at 0)
|
12
|
+
# @attr_reader modifier [Modifier] The modifier for this cell
|
9
13
|
class Cell
|
10
14
|
attr_accessor :ast, :row_index
|
11
15
|
attr_reader :index, :modifier
|
12
16
|
|
13
|
-
# Parse a +value+ into a Cell object.
|
14
|
-
#
|
17
|
+
# Parse a +value+ into a Cell object.
|
18
|
+
#
|
19
|
+
# @param value [String] A string value which should already have been processed through a CSV parser
|
20
|
+
# @param runtime [Runtime]
|
21
|
+
# @param modifier [Modifier]
|
22
|
+
#
|
23
|
+
# @return [Cell]
|
15
24
|
def self.parse(value, runtime:, modifier:)
|
16
25
|
new(value:, row_index: runtime.row_index, index: runtime.cell_index, modifier:).tap do |c|
|
17
26
|
c.ast = ::CSVPlusPlus::Language::CellValueParser.new.parse(value, runtime)
|
18
27
|
end
|
19
28
|
end
|
20
29
|
|
21
|
-
#
|
30
|
+
# @param row_index [Integer] The cell's row index (starts at 0)
|
31
|
+
# @param index [Integer] The cell's index (starts at 0)
|
32
|
+
# @param value [String] A string value which should already have been processed through a CSV parser
|
33
|
+
# @param modifier [Modifier] A modifier to apply to this cell
|
22
34
|
def initialize(row_index:, index:, value:, modifier:)
|
23
35
|
@value = value
|
24
36
|
@modifier = modifier
|
@@ -26,25 +38,29 @@ module CSVPlusPlus
|
|
26
38
|
@row_index = row_index
|
27
39
|
end
|
28
40
|
|
29
|
-
# The value (cleaned up some)
|
41
|
+
# The +@value+ (cleaned up some)
|
42
|
+
#
|
43
|
+
# @return [String]
|
30
44
|
def value
|
31
45
|
return if @value.nil? || @value.strip.empty?
|
32
46
|
|
33
47
|
@value.strip
|
34
48
|
end
|
35
49
|
|
36
|
-
#
|
50
|
+
# @return [String]
|
37
51
|
def to_s
|
38
52
|
"Cell(index: #{@index}, row_index: #{@row_index}, value: #{@value}, modifier: #{@modifier})"
|
39
53
|
end
|
40
54
|
|
41
55
|
# A compiled final representation of the cell. This can only happen after all cell have had
|
42
56
|
# variables and functions resolved.
|
57
|
+
#
|
58
|
+
# @return [String]
|
43
59
|
def to_csv
|
44
60
|
return value unless @ast
|
45
61
|
|
46
|
-
# This looks really simple but we're relying on each node of the AST to define #to_s
|
47
|
-
# this at the top will recursively print the tree
|
62
|
+
# This looks really simple but we're relying on each node of the AST to define #to_s such that calling
|
63
|
+
# this at the top will recursively print the tree (as a well-formatted spreadsheet formula)
|
48
64
|
"=#{@ast}"
|
49
65
|
end
|
50
66
|
end
|
data/lib/csv_plus_plus/cli.rb
CHANGED
@@ -4,27 +4,32 @@ require 'optparse'
|
|
4
4
|
|
5
5
|
module CSVPlusPlus
|
6
6
|
# Handle running the application with the given CLI flags
|
7
|
+
#
|
8
|
+
# @attr options [Options, nil] The parsed CLI options
|
7
9
|
class CLI
|
8
|
-
|
10
|
+
attr_accessor :options
|
11
|
+
|
12
|
+
# Handle CLI flags and launch the compiler
|
13
|
+
#
|
14
|
+
# @return [CLI]
|
9
15
|
def self.launch_compiler!
|
10
16
|
cli = new
|
11
|
-
cli.
|
17
|
+
cli.parse_options!
|
18
|
+
cli.main
|
12
19
|
rescue ::StandardError => e
|
13
20
|
cli.handle_error(e)
|
14
21
|
exit(1)
|
15
22
|
end
|
16
23
|
|
17
|
-
#
|
18
|
-
def
|
19
|
-
parse_options!
|
20
|
-
end
|
21
|
-
|
22
|
-
# compile the given template, using the given CLI flags
|
23
|
-
def compile!
|
24
|
+
# Compile the given template using the given CLI flags
|
25
|
+
def main
|
26
|
+
parse_options! unless @options
|
24
27
|
::CSVPlusPlus.apply_template_to_sheet!(::ARGF.read, ::ARGF.filename, @options)
|
25
28
|
end
|
26
29
|
|
27
|
-
#
|
30
|
+
# Nicely handle a given error. How it's handled depends on if it's our error and if @options.verbose
|
31
|
+
#
|
32
|
+
# @param error [CSVPlusPlus::Error, Google::Apis::ClientError, StandardError]
|
28
33
|
def handle_error(error)
|
29
34
|
case error
|
30
35
|
when ::CSVPlusPlus::Error
|
@@ -37,6 +42,20 @@ module CSVPlusPlus
|
|
37
42
|
end
|
38
43
|
end
|
39
44
|
|
45
|
+
# Handle the supplied command line options, setting +@options+ or throw an error if anything is invalid
|
46
|
+
def parse_options!
|
47
|
+
@options = ::CSVPlusPlus::Options.new
|
48
|
+
option_parser.parse!
|
49
|
+
validate_options
|
50
|
+
rescue ::OptionParser::InvalidOption => e
|
51
|
+
raise(::CSVPlusPlus::Error, e.message)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [String]
|
55
|
+
def to_s
|
56
|
+
"CLI(options: #{options})"
|
57
|
+
end
|
58
|
+
|
40
59
|
private
|
41
60
|
|
42
61
|
def handle_internal_error(error)
|
@@ -54,12 +73,6 @@ module CSVPlusPlus
|
|
54
73
|
warn("#{error.status_code} Error making Google API request [#{error.message}]: #{error.body}")
|
55
74
|
end
|
56
75
|
|
57
|
-
def parse_options!
|
58
|
-
@options = ::CSVPlusPlus::Options.new
|
59
|
-
option_parser.parse!
|
60
|
-
validate_options
|
61
|
-
end
|
62
|
-
|
63
76
|
def validate_options
|
64
77
|
error_message = @options.validate
|
65
78
|
return if error_message.nil?
|
@@ -4,10 +4,18 @@ require_relative './google_options'
|
|
4
4
|
|
5
5
|
module CSVPlusPlus
|
6
6
|
# Individual CLI flags that a user can supply
|
7
|
+
#
|
8
|
+
# @attr_reader short_flag [String] A definition of the short/single-character flag
|
9
|
+
# @attr_reader long_flag [String] A definition of the long/word-based flag
|
10
|
+
# @attr_reader description [String] A description of what the flag does
|
11
|
+
# @attr_reader handler [Proc(Options, String)] A proc which is called to handle when this flag is seen
|
7
12
|
class CliFlag
|
8
13
|
attr_reader :short_flag, :long_flag, :description, :handler
|
9
14
|
|
10
|
-
#
|
15
|
+
# @param short_flag [String] A definition of the short/single-character flag
|
16
|
+
# @param long_flag [String] A definition of the long/word-based flag
|
17
|
+
# @param description [String] A description of what the flag does
|
18
|
+
# @param handler [Proc(Options, String)] A proc which is called to handle when this flag is seen
|
11
19
|
def initialize(short_flag, long_flag, description, handler)
|
12
20
|
@short_flag = short_flag
|
13
21
|
@long_flag = long_flag
|
@@ -15,7 +23,7 @@ module CSVPlusPlus
|
|
15
23
|
@handler = handler
|
16
24
|
end
|
17
25
|
|
18
|
-
#
|
26
|
+
# @return [String]
|
19
27
|
def to_s
|
20
28
|
"#{@short_flag}, #{@long_flag} #{@description}"
|
21
29
|
end
|
@@ -4,44 +4,63 @@ require_relative './language/code_section.tab'
|
|
4
4
|
require_relative './language/entities'
|
5
5
|
|
6
6
|
module CSVPlusPlus
|
7
|
-
##
|
8
7
|
# A representation of the code section part of a template (the variable and function definitions)
|
8
|
+
#
|
9
|
+
# @attr variables [Hash<Symbol, Variable>] All defined variables
|
10
|
+
# @attr_reader functions [Hash<Symbol, Function>] All defined functions
|
9
11
|
class CodeSection
|
10
12
|
attr_reader :functions
|
11
13
|
attr_accessor :variables
|
12
14
|
|
13
|
-
#
|
15
|
+
# @param variables [Hash<Symbol, Variable>] Initial variables
|
16
|
+
# @param functions [Hash<Symbol, Variable>] Initial functions
|
14
17
|
def initialize(variables: {}, functions: {})
|
15
18
|
@variables = variables
|
16
19
|
@functions = functions
|
17
20
|
end
|
18
21
|
|
19
22
|
# Define a (or re-define an existing) variable
|
23
|
+
#
|
24
|
+
# @param id [String, Symbol] The identifier for the variable
|
25
|
+
# @param entity [Entity] The value (entity) the variable holds
|
20
26
|
def def_variable(id, entity)
|
21
27
|
@variables[id.to_sym] = entity
|
22
28
|
end
|
23
29
|
|
24
30
|
# Define (or re-define existing) variables
|
31
|
+
#
|
32
|
+
# @param variables [Hash<Symbol, Variable>] Variables to define
|
25
33
|
def def_variables(variables)
|
26
34
|
variables.each { |id, entity| def_variable(id, entity) }
|
27
35
|
end
|
28
36
|
|
29
37
|
# Define a (or re-define an existing) function
|
38
|
+
#
|
39
|
+
# @param id [String, Symbol] The identifier for the function
|
40
|
+
# @param entity [Entities::Function] The defined function
|
30
41
|
def def_function(id, entity)
|
31
42
|
@functions[id.to_sym] = entity
|
32
43
|
end
|
33
44
|
|
34
45
|
# Is the variable defined?
|
46
|
+
#
|
47
|
+
# @param var_id [Symbol, String] The identifier of the variable
|
48
|
+
#
|
49
|
+
# @return [boolean]
|
35
50
|
def defined_variable?(var_id)
|
36
51
|
@variables.key?(var_id.to_sym)
|
37
52
|
end
|
38
53
|
|
39
54
|
# Is the function defined?
|
55
|
+
#
|
56
|
+
# @param fn_id [Symbol, String] The identifier of the function
|
57
|
+
#
|
58
|
+
# @return [boolean]
|
40
59
|
def defined_function?(fn_id)
|
41
60
|
@functions.key?(fn_id.to_sym)
|
42
61
|
end
|
43
62
|
|
44
|
-
#
|
63
|
+
# @return [String]
|
45
64
|
def to_s
|
46
65
|
"CodeSection(functions: #{@functions}, variables: #{@variables})"
|
47
66
|
end
|
data/lib/csv_plus_plus/color.rb
CHANGED
@@ -1,11 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CSVPlusPlus
|
4
|
-
# A color value
|
4
|
+
# A color value parsed into it's respective components
|
5
|
+
#
|
6
|
+
# attr_reader blue_hex [String] The blue value in hex ("FF", "00", "AF", etc)
|
7
|
+
# attr_reader green_hex [String] The green value in hex ("FF", "00", "AF", etc)
|
8
|
+
# attr_reader red_hex [String] The red value in hex ("FF", "00", "AF", etc)
|
5
9
|
class Color
|
6
10
|
attr_reader :red_hex, :green_hex, :blue_hex
|
7
11
|
|
8
12
|
# create an instance from a string like "#FFF" or "#FFFFFF"
|
13
|
+
#
|
14
|
+
# @param hex_string [String] The hex string input to parse
|
9
15
|
def initialize(hex_string)
|
10
16
|
@red_hex, @green_hex, @blue_hex = hex_string
|
11
17
|
.gsub(/^#?/, '')
|
@@ -15,31 +21,39 @@ module CSVPlusPlus
|
|
15
21
|
end
|
16
22
|
|
17
23
|
# The percent (decimal between 0-1) of red
|
24
|
+
#
|
25
|
+
# @return [Numeric]
|
18
26
|
def red_percent
|
19
27
|
hex_to_percent(@red_hex)
|
20
28
|
end
|
21
29
|
|
22
30
|
# The percent (decimal between 0-1) of green
|
31
|
+
#
|
32
|
+
# @return [Numeric]
|
23
33
|
def green_percent
|
24
34
|
hex_to_percent(@green_hex)
|
25
35
|
end
|
26
36
|
|
27
37
|
# The percent (decimal between 0-1) of blue
|
38
|
+
#
|
39
|
+
# @return [Numeric]
|
28
40
|
def blue_percent
|
29
41
|
hex_to_percent(@blue_hex)
|
30
42
|
end
|
31
43
|
|
32
|
-
#
|
44
|
+
# Create a hex representation of the color (without a '#')
|
45
|
+
#
|
46
|
+
# @return [String]
|
33
47
|
def to_hex
|
34
48
|
[@red_hex, @green_hex, @blue_hex].join
|
35
49
|
end
|
36
50
|
|
37
|
-
#
|
51
|
+
# @return [String]
|
38
52
|
def to_s
|
39
53
|
"Color(r: #{@red_hex}, g: #{@green_hex}, b: #{@blue_hex})"
|
40
54
|
end
|
41
55
|
|
42
|
-
#
|
56
|
+
# @return [boolean]
|
43
57
|
def ==(other)
|
44
58
|
other.is_a?(self.class) &&
|
45
59
|
other.red_hex == @red_hex &&
|
@@ -50,7 +64,7 @@ module CSVPlusPlus
|
|
50
64
|
private
|
51
65
|
|
52
66
|
def hex_to_percent(hex)
|
53
|
-
hex.to_i(16) / 255
|
67
|
+
hex.to_i(16) / 255.0
|
54
68
|
end
|
55
69
|
end
|
56
70
|
end
|
@@ -2,9 +2,13 @@
|
|
2
2
|
|
3
3
|
module CSVPlusPlus
|
4
4
|
# The Google-specific options a user can supply
|
5
|
+
#
|
6
|
+
# attr sheet_id [String] The ID of the Google Sheet to write to
|
5
7
|
GoogleOptions =
|
6
8
|
::Struct.new(:sheet_id) do
|
7
|
-
#
|
9
|
+
# Format a string with a verbose description of what we're doing with the options
|
10
|
+
#
|
11
|
+
# @return [String]
|
8
12
|
def verbose_summary
|
9
13
|
<<~SUMMARY
|
10
14
|
## Google Sheets Options
|
@@ -13,7 +17,7 @@ module CSVPlusPlus
|
|
13
17
|
SUMMARY
|
14
18
|
end
|
15
19
|
|
16
|
-
#
|
20
|
+
# @return [String]
|
17
21
|
def to_s
|
18
22
|
"GoogleOptions(sheet_id: #{sheet_id})"
|
19
23
|
end
|
data/lib/csv_plus_plus/graph.rb
CHANGED
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './entities'
|
4
|
+
|
5
|
+
module CSVPlusPlus
|
6
|
+
module Language
|
7
|
+
# Some helpful functions that can be mixed into a class to help building ASTs
|
8
|
+
module ASTBuilder
|
9
|
+
# Let the current class have functions which can build a given entity by calling it's type. For example
|
10
|
+
# +number(1)+, +variable(:foo)+
|
11
|
+
#
|
12
|
+
# @param method_name [Symbol] The +method_name+ to respond to
|
13
|
+
# @param arguments [] The arguments to create the entity with
|
14
|
+
#
|
15
|
+
# @return [Entity, #super]
|
16
|
+
def method_missing(method_name, *arguments)
|
17
|
+
entity_class = ::CSVPlusPlus::Language::TYPES[method_name.to_sym]
|
18
|
+
return super unless entity_class
|
19
|
+
|
20
|
+
entity_class.new(*arguments)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Let the current class have functions which can build a given entity by calling it's type. For example
|
24
|
+
# +number(1)+, +variable(:foo)+
|
25
|
+
#
|
26
|
+
# @param method_name [Symbol] The +method_name+ to respond to
|
27
|
+
# @param arguments [] The arguments to create the entity with
|
28
|
+
#
|
29
|
+
# @return [Boolean, #super]
|
30
|
+
def respond_to_missing?(method_name, *_arguments)
|
31
|
+
::CSVPlusPlus::Language::TYPES.include?(method_name.to_sym) || super
|
32
|
+
end
|
33
|
+
|
34
|
+
# Turns index-based/X,Y coordinates into a A1 format
|
35
|
+
#
|
36
|
+
# @param row_index [Integer]
|
37
|
+
# @param cell_index [Integer]
|
38
|
+
#
|
39
|
+
# @return [String]
|
40
|
+
def ref(row_index: nil, cell_index: nil)
|
41
|
+
return unless row_index || cell_index
|
42
|
+
|
43
|
+
rowref = row_index ? (row_index + 1).to_s : ''
|
44
|
+
cellref = cell_index ? cell_ref(cell_index) : ''
|
45
|
+
cell_reference([cellref, rowref].join)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
ALPHA = ('A'..'Z').to_a.freeze
|
51
|
+
private_constant :ALPHA
|
52
|
+
|
53
|
+
def cell_ref(cell_index)
|
54
|
+
c = cell_index.dup
|
55
|
+
ref = ''
|
56
|
+
|
57
|
+
while c >= 0
|
58
|
+
# rubocop:disable Lint/ConstantResolution
|
59
|
+
ref += ALPHA[c % 26]
|
60
|
+
# rubocop:enable Lint/ConstantResolution
|
61
|
+
c = (c / 26).floor - 1
|
62
|
+
end
|
63
|
+
|
64
|
+
ref.reverse
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'benchmark'
|
4
|
+
|
5
|
+
module CSVPlusPlus
|
6
|
+
module Language
|
7
|
+
# Extend a +Compiler+ class and add benchmark timings
|
8
|
+
# @attr_reader timings [Array<Benchmark::Tms>] +Benchmark+ timings that have been accumulated by each step of
|
9
|
+
# compilation
|
10
|
+
# @attr_reader benchmark [Benchmark] A +Benchmark+ instance
|
11
|
+
module BenchmarkedCompiler
|
12
|
+
attr_reader :benchmark, :timings
|
13
|
+
|
14
|
+
# Wrap a +Compiler+ with our instance methods that add benchmarks
|
15
|
+
def self.with_benchmarks(compiler, &block)
|
16
|
+
::Benchmark.benchmark(::Benchmark::CAPTION, 25, ::Benchmark::FORMAT, '> Total') do |x|
|
17
|
+
# compiler = new(options:, runtime:, benchmark: x)
|
18
|
+
compiler.extend(self)
|
19
|
+
compiler.benchmark = x
|
20
|
+
|
21
|
+
block.call(compiler)
|
22
|
+
|
23
|
+
[compiler.timings.reduce(:+)]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param benchmark [Benchmark] A +Benchmark+ instance
|
28
|
+
def benchmark=(benchmark)
|
29
|
+
@benchmark = benchmark
|
30
|
+
@timings = []
|
31
|
+
end
|
32
|
+
|
33
|
+
# Time the Compiler#outputting! stage
|
34
|
+
def outputting!
|
35
|
+
time_stage('Writing the spreadsheet') { super }
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def parse_code_section!
|
41
|
+
time_stage('Parsing code section') { super }
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse_csv_section!
|
45
|
+
time_stage('Parsing CSV section') { super }
|
46
|
+
end
|
47
|
+
|
48
|
+
def expanding
|
49
|
+
time_stage('Expanding rows') { super }
|
50
|
+
end
|
51
|
+
|
52
|
+
def resolve_all_cells!(template)
|
53
|
+
time_stage('Resolving each cell') { super(template) }
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def time_stage(stage, &block)
|
59
|
+
ret = nil
|
60
|
+
@timings << @benchmark.report(stage) { ret = block.call }
|
61
|
+
ret
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './ast_builder'
|
4
|
+
|
5
|
+
module CSVPlusPlus
|
6
|
+
module Language
|
7
|
+
# Provides ASTs for builtin functions and variables
|
8
|
+
module Builtins
|
9
|
+
extend ::CSVPlusPlus::Language::ASTBuilder
|
10
|
+
|
11
|
+
VARIABLES = {
|
12
|
+
# The number (integer) of the current cell. Starts at 1
|
13
|
+
cellnum: runtime_value(->(runtime) { number(runtime.cell_index + 1) }),
|
14
|
+
|
15
|
+
# A reference to the current cell
|
16
|
+
cellref: runtime_value(->(runtime) { ref(row_index: runtime.row_index, cell_index: runtime.cell_index) }),
|
17
|
+
|
18
|
+
# A reference to the row above
|
19
|
+
rowabove: runtime_value(->(runtime) { ref(row_index: [0, (runtime.row_index - 1)].max) }),
|
20
|
+
|
21
|
+
# A reference to the row below
|
22
|
+
rowbelow: runtime_value(->(runtime) { ref(row_index: runtime.row_index + 1) }),
|
23
|
+
|
24
|
+
# The number (integer) of the current row. Starts at 1
|
25
|
+
rownum: runtime_value(->(runtime) { number(runtime.rownum) }),
|
26
|
+
|
27
|
+
# A reference to the current row
|
28
|
+
rowref: runtime_value(->(runtime) { ref(row_index: runtime.row_index) })
|
29
|
+
}.freeze
|
30
|
+
public_constant :VARIABLES
|
31
|
+
|
32
|
+
FUNCTIONS = {
|
33
|
+
# TODO: A reference to a cell in a given row?
|
34
|
+
# A reference to a cell above the current row
|
35
|
+
cellabove: runtime_value(->(runtime, args) { cell_reference([args[0], [1, (runtime.rownum - 1)].max].join) }),
|
36
|
+
|
37
|
+
# A reference to a cell in the current row
|
38
|
+
celladjacent: runtime_value(->(runtime, args) { cell_reference([args[0], runtime.rownum].join) }),
|
39
|
+
|
40
|
+
# A reference to a cell below the current row
|
41
|
+
cellbelow: runtime_value(->(runtime, args) { cell_reference([args[0], runtime.rownum + 1].join) })
|
42
|
+
}.freeze
|
43
|
+
public_constant :FUNCTIONS
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -29,11 +29,10 @@ module_eval(<<'...end cell_value.y/module_eval...', 'cell_value.y', 48)
|
|
29
29
|
@ast
|
30
30
|
end
|
31
31
|
|
32
|
-
def tokenizer
|
32
|
+
def tokenizer
|
33
33
|
::CSVPlusPlus::Lexer::Tokenizer.new(
|
34
34
|
catchall: /[\(\)\/\*\+\-,=&]/,
|
35
35
|
ignore: /\s+/,
|
36
|
-
input:,
|
37
36
|
tokens: [
|
38
37
|
[/true/i, :TRUE],
|
39
38
|
[/false/i, :FALSE],
|
@@ -33,11 +33,10 @@ module_eval(<<'...end code_section.y/module_eval...', 'code_section.y', 67)
|
|
33
33
|
'code section'
|
34
34
|
end
|
35
35
|
|
36
|
-
def tokenizer
|
36
|
+
def tokenizer
|
37
37
|
::CSVPlusPlus::Lexer::Tokenizer.new(
|
38
38
|
catchall: /[\(\)\{\}\/\*\+\-,=&]/,
|
39
39
|
ignore: /\s+|\#[^\n]+\n/,
|
40
|
-
input:,
|
41
40
|
stop_fn: lambda do |scanner|
|
42
41
|
return false unless scanner.scan(/#{::CSVPlusPlus::Lexer::END_OF_CODE_SECTION}/)
|
43
42
|
|