furud 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 +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +101 -0
- data/lib/furud/engine.rb +756 -0
- data/lib/furud/format.rb +244 -0
- data/lib/furud/formula.rb +538 -0
- data/lib/furud/functions.rb +794 -0
- data/lib/furud/types.rb +70 -0
- data/lib/furud/version.rb +5 -0
- data/lib/furud.rb +15 -0
- data/sig/furud.rbs +128 -0
- metadata +57 -0
data/lib/furud/types.rb
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Furud
|
|
4
|
+
Reference = Data.define(:sheet, :row, :column, :absolute_row, :absolute_column) do
|
|
5
|
+
def initialize(sheet: nil, row:, column:, absolute_row: false, absolute_column: false)
|
|
6
|
+
raise ArgumentError, "row and column must be positive" unless row.to_i.positive? && column.to_i.positive?
|
|
7
|
+
|
|
8
|
+
super(sheet: sheet&.to_s, row: Integer(row), column: Integer(column),
|
|
9
|
+
absolute_row: !!absolute_row, absolute_column: !!absolute_column)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
Area = Data.define(:sheet, :top, :left, :bottom, :right) do
|
|
14
|
+
def initialize(sheet: nil, top:, left:, bottom:, right:)
|
|
15
|
+
values = [top, left, bottom, right].map { |value| Integer(value) }
|
|
16
|
+
raise ArgumentError, "area coordinates must be positive" unless values.all?(&:positive?)
|
|
17
|
+
|
|
18
|
+
top, left, bottom, right = values
|
|
19
|
+
super(sheet: sheet&.to_s, top: [top, bottom].min, left: [left, right].min,
|
|
20
|
+
bottom: [top, bottom].max, right: [left, right].max)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def include?(reference)
|
|
24
|
+
(sheet.nil? || reference.sheet == sheet) && reference.row.between?(top, bottom) &&
|
|
25
|
+
reference.column.between?(left, right)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
ERROR_CODES = { div0: "#DIV/0!", value: "#VALUE!", ref: "#REF!", name: "#NAME?",
|
|
30
|
+
na: "#N/A", num: "#NUM!", cycle: "#CYCLE!", spill: "#SPILL!",
|
|
31
|
+
calc: "#CALC!" }.freeze
|
|
32
|
+
ErrorValue = Data.define(:code) do
|
|
33
|
+
def initialize(code:)
|
|
34
|
+
super(code: code.to_sym)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_s = ERROR_CODES.fetch(code, "#VALUE!")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
Node = Data.define(:type, :value, :children) do
|
|
41
|
+
def initialize(type:, value: nil, children: [])
|
|
42
|
+
super(type: type.to_sym, value: value, children: children.freeze)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
Adjustment = Data.define(:type, :sheet, :at, :count) do
|
|
47
|
+
def initialize(type:, sheet: nil, at:, count: 1)
|
|
48
|
+
type = type.to_sym
|
|
49
|
+
raise ArgumentError, "type must be insert_rows, delete_rows, insert_columns, or delete_columns" unless
|
|
50
|
+
%i[insert_rows delete_rows insert_columns delete_columns].include?(type)
|
|
51
|
+
raise ArgumentError, "at and count must be positive" unless Integer(at).positive? && Integer(count).positive?
|
|
52
|
+
|
|
53
|
+
super(type: type, sheet: sheet&.to_s, at: Integer(at), count: Integer(count))
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
ArrayValue = Data.define(:rows) do
|
|
58
|
+
def initialize(rows:)
|
|
59
|
+
rows = rows.map { |row| row.dup.freeze }.freeze
|
|
60
|
+
raise ArgumentError, "array must not be empty" if rows.empty? || rows.any?(&:empty?)
|
|
61
|
+
raise ArgumentError, "array rows must have equal length" unless rows.map(&:length).uniq.one?
|
|
62
|
+
|
|
63
|
+
super(rows: rows)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def height = rows.length
|
|
67
|
+
def width = rows.first.length
|
|
68
|
+
def [](row, column) = rows[row][column]
|
|
69
|
+
end
|
|
70
|
+
end
|
data/lib/furud.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "furud/version"
|
|
4
|
+
require_relative "furud/types"
|
|
5
|
+
require_relative "furud/formula"
|
|
6
|
+
require_relative "furud/functions"
|
|
7
|
+
require_relative "furud/format"
|
|
8
|
+
require_relative "furud/engine"
|
|
9
|
+
|
|
10
|
+
module Furud
|
|
11
|
+
module Source; end
|
|
12
|
+
|
|
13
|
+
class Error < StandardError; end
|
|
14
|
+
class ParseError < Error; end
|
|
15
|
+
end
|
data/sig/furud.rbs
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
module Furud
|
|
2
|
+
VERSION: String
|
|
3
|
+
ERROR_CODES: Hash[Symbol, String]
|
|
4
|
+
|
|
5
|
+
class Error < StandardError
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class ParseError < Error
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class Reference
|
|
12
|
+
attr_reader sheet: String?
|
|
13
|
+
attr_reader row: Integer
|
|
14
|
+
attr_reader column: Integer
|
|
15
|
+
attr_reader absolute_row: bool
|
|
16
|
+
attr_reader absolute_column: bool
|
|
17
|
+
def initialize: (?sheet: String?, row: Integer, column: Integer, ?absolute_row: bool, ?absolute_column: bool) -> void
|
|
18
|
+
def with: (**untyped) -> Reference
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Area
|
|
22
|
+
attr_reader sheet: String?
|
|
23
|
+
attr_reader top: Integer
|
|
24
|
+
attr_reader left: Integer
|
|
25
|
+
attr_reader bottom: Integer
|
|
26
|
+
attr_reader right: Integer
|
|
27
|
+
def initialize: (?sheet: String?, top: Integer, left: Integer, bottom: Integer, right: Integer) -> void
|
|
28
|
+
def include?: (Reference) -> bool
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class ErrorValue
|
|
32
|
+
attr_reader code: Symbol
|
|
33
|
+
def initialize: (code: Symbol) -> void
|
|
34
|
+
def to_s: () -> String
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Node
|
|
38
|
+
attr_reader type: Symbol
|
|
39
|
+
attr_reader value: untyped
|
|
40
|
+
attr_reader children: Array[Node]
|
|
41
|
+
def initialize: (type: Symbol, ?value: untyped, ?children: Array[Node]) -> void
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class Adjustment
|
|
45
|
+
attr_reader type: Symbol
|
|
46
|
+
attr_reader sheet: String?
|
|
47
|
+
attr_reader at: Integer
|
|
48
|
+
attr_reader count: Integer
|
|
49
|
+
def initialize: (type: Symbol, ?sheet: String?, at: Integer, ?count: Integer) -> void
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class ArrayValue
|
|
53
|
+
attr_reader rows: Array[Array[untyped]]
|
|
54
|
+
def initialize: (rows: Array[Array[untyped]]) -> void
|
|
55
|
+
def height: () -> Integer
|
|
56
|
+
def width: () -> Integer
|
|
57
|
+
def []: (Integer row, Integer column) -> untyped
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
module Formula
|
|
61
|
+
def self.parse: (String source, ?origin: Reference | Hash[Symbol, untyped]?) -> Node
|
|
62
|
+
def self.render: (Node ast, ?origin: Reference | Hash[Symbol, untyped]?) -> String
|
|
63
|
+
def self.references: (Node ast) -> Array[Reference | Area]
|
|
64
|
+
def self.translate: (Node ast, from: Reference | Hash[Symbol, untyped], to: Reference | Hash[Symbol, untyped]) -> Node
|
|
65
|
+
def self.adjust: (Node ast, (Adjustment | Hash[Symbol, untyped]) operation) -> Node
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
module Source
|
|
69
|
+
def value_at: (Reference) -> untyped
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class Engine
|
|
73
|
+
attr_reader source: untyped
|
|
74
|
+
attr_reader locale: Symbol
|
|
75
|
+
def initialize: (?untyped source, ?functions: Functions::Registry, ?locale: Symbol, ?iterative: bool, ?max_iterations: Integer, ?epsilon: Numeric) -> void
|
|
76
|
+
def set: (Reference, untyped input) -> untyped
|
|
77
|
+
def clear: (Reference) -> nil
|
|
78
|
+
def value: (Reference) -> untyped
|
|
79
|
+
def formula: (Reference) -> String?
|
|
80
|
+
def recalculate: () -> Array[Reference]
|
|
81
|
+
def dirty: () -> Set[Reference]
|
|
82
|
+
def cycles: () -> Array[Array[Reference]]
|
|
83
|
+
def insert_rows: (String? sheet, Integer at, ?Integer count) -> self
|
|
84
|
+
def delete_rows: (String? sheet, Integer at, ?Integer count) -> self
|
|
85
|
+
def insert_columns: (String? sheet, Integer at, ?Integer count) -> self
|
|
86
|
+
def delete_columns: (String? sheet, Integer at, ?Integer count) -> self
|
|
87
|
+
def define_name: (String name, (Reference | Area) area) -> (Reference | Area)
|
|
88
|
+
def precedents: (Reference) -> Array[Reference | Area]
|
|
89
|
+
def dependents: (Reference) -> Array[Reference]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
module Functions
|
|
93
|
+
class Entry
|
|
94
|
+
attr_reader name: String
|
|
95
|
+
attr_reader arity: untyped
|
|
96
|
+
attr_reader volatile: bool
|
|
97
|
+
def call: (*untyped) -> untyped
|
|
98
|
+
def accepts?: (Integer) -> bool
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
class Registry
|
|
102
|
+
include Enumerable[Entry]
|
|
103
|
+
def register: (String name, arity: Integer | Range[Integer] | Array[Integer], ?volatile: bool) { (*untyped) -> untyped } -> self
|
|
104
|
+
def []: (String name) -> Entry?
|
|
105
|
+
def names: () -> Array[String]
|
|
106
|
+
def each: () { (Entry) -> void } -> self
|
|
107
|
+
def size: () -> Integer
|
|
108
|
+
def call: (String name, *untyped args) -> untyped
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def self.standard: () -> Registry
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
module Format
|
|
115
|
+
class Section
|
|
116
|
+
attr_reader pattern: String
|
|
117
|
+
attr_reader condition: [String, Numeric]?
|
|
118
|
+
attr_reader color: Symbol?
|
|
119
|
+
end
|
|
120
|
+
class Spec
|
|
121
|
+
attr_reader sections: Array[Section]
|
|
122
|
+
attr_reader locale: Symbol
|
|
123
|
+
end
|
|
124
|
+
def self.parse: (String pattern, ?locale: Symbol) -> Spec
|
|
125
|
+
def self.apply: (untyped value, Spec spec) -> [String, Hash[Symbol, untyped]]
|
|
126
|
+
def self.infer: (String text, locale: Symbol) -> [untyped, String]
|
|
127
|
+
end
|
|
128
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: furud
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yudai Takada
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Parses spreadsheet formulas, evaluates 192 standard functions, tracks
|
|
13
|
+
dependencies, recalculates incrementally, and formats values.
|
|
14
|
+
email:
|
|
15
|
+
- t.yudai92@gmail.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- CHANGELOG.md
|
|
21
|
+
- LICENSE.txt
|
|
22
|
+
- README.md
|
|
23
|
+
- lib/furud.rb
|
|
24
|
+
- lib/furud/engine.rb
|
|
25
|
+
- lib/furud/format.rb
|
|
26
|
+
- lib/furud/formula.rb
|
|
27
|
+
- lib/furud/functions.rb
|
|
28
|
+
- lib/furud/types.rb
|
|
29
|
+
- lib/furud/version.rb
|
|
30
|
+
- sig/furud.rbs
|
|
31
|
+
homepage: https://github.com/noxdea/furud
|
|
32
|
+
licenses:
|
|
33
|
+
- MIT
|
|
34
|
+
metadata:
|
|
35
|
+
allowed_push_host: https://rubygems.org
|
|
36
|
+
homepage_uri: https://github.com/noxdea/furud
|
|
37
|
+
source_code_uri: https://github.com/noxdea/furud/tree/main
|
|
38
|
+
changelog_uri: https://github.com/noxdea/furud/blob/main/CHANGELOG.md
|
|
39
|
+
rubygems_mfa_required: 'true'
|
|
40
|
+
rdoc_options: []
|
|
41
|
+
require_paths:
|
|
42
|
+
- lib
|
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: 3.2.0
|
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - ">="
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '0'
|
|
53
|
+
requirements: []
|
|
54
|
+
rubygems_version: 4.0.16
|
|
55
|
+
specification_version: 4
|
|
56
|
+
summary: Dependency-free spreadsheet formula and recalculation engine
|
|
57
|
+
test_files: []
|