rubocop-boochtek 0.2.0 → 0.2.1

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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +115 -5
  3. data/LICENSE.md +2 -2
  4. data/README.md +53 -106
  5. data/lib/disable_ssl_verify.rb +5 -0
  6. data/lib/extensions/argf.rb +8 -0
  7. data/lib/extensions/boolean.rb +48 -0
  8. data/lib/extensions/class.rb +10 -0
  9. data/lib/extensions/enumerable.rb +42 -0
  10. data/lib/extensions/integer.rb +7 -0
  11. data/lib/extensions/llvm_module.rb +101 -0
  12. data/lib/extensions/module.rb +34 -0
  13. data/lib/extensions/string.rb +29 -0
  14. data/lib/stone/ast/block.rb +88 -0
  15. data/lib/stone/ast/boolean_literal.rb +43 -0
  16. data/lib/stone/ast/computed_property_definition.rb +32 -0
  17. data/lib/stone/ast/constant_definition.rb +124 -0
  18. data/lib/stone/ast/expression.rb +12 -0
  19. data/lib/stone/ast/function_call.rb +291 -0
  20. data/lib/stone/ast/function_type_annotation.rb +31 -0
  21. data/lib/stone/ast/integer_literal.rb +46 -0
  22. data/lib/stone/ast/lambda.rb +126 -0
  23. data/lib/stone/ast/null_literal.rb +29 -0
  24. data/lib/stone/ast/program_unit/top_function.rb +209 -0
  25. data/lib/stone/ast/program_unit.rb +412 -0
  26. data/lib/stone/ast/property_access.rb +557 -0
  27. data/lib/stone/ast/record_definition.rb +180 -0
  28. data/lib/stone/ast/record_instantiation.rb +141 -0
  29. data/lib/stone/ast/reference.rb +145 -0
  30. data/lib/stone/ast/string_literal.rb +79 -0
  31. data/lib/stone/ast/two_phase_processing.rb +36 -0
  32. data/lib/stone/ast/type_annotation.rb +26 -0
  33. data/lib/stone/ast/type_declaration.rb +28 -0
  34. data/lib/stone/ast/type_of_expression.rb +41 -0
  35. data/lib/stone/ast/type_reference.rb +28 -0
  36. data/lib/stone/ast/union_type_annotation.rb +26 -0
  37. data/lib/stone/ast.rb +64 -0
  38. data/lib/stone/built_ins.rb +245 -0
  39. data/lib/stone/error/argument_error.rb +7 -0
  40. data/lib/stone/error/arity_error.rb +7 -0
  41. data/lib/stone/error/overflow.rb +18 -0
  42. data/lib/stone/error/property_error.rb +7 -0
  43. data/lib/stone/error/reference_error.rb +4 -0
  44. data/lib/stone/error/type_error.rb +7 -0
  45. data/lib/stone/error.rb +12 -0
  46. data/lib/stone/grammar.rb +124 -0
  47. data/lib/stone/libc.rb +27 -0
  48. data/lib/stone/prelude.stone +11 -0
  49. data/lib/stone/rtti.rb +182 -0
  50. data/lib/stone/scope.rb +85 -0
  51. data/lib/stone/transform.rb +410 -0
  52. data/lib/stone/type.rb +323 -0
  53. data/lib/stone/type_context.rb +38 -0
  54. data/lib/stone/type_registry.rb +73 -0
  55. data/lib/stone/types.rb +104 -0
  56. data/lib/stone.rb +70 -0
  57. metadata +54 -24
  58. data/CHANGELOG.md +0 -28
  59. data/Rakefile +0 -5
  60. data/config/default.yml +0 -277
  61. data/lib/rubocop/boochtek/plugin.rb +0 -43
  62. data/lib/rubocop/boochtek/version.rb +0 -7
  63. data/lib/rubocop/boochtek.rb +0 -18
  64. data/lib/rubocop/cop/boochtek/compact_endless_methods.rb +0 -103
  65. data/lib/rubocop-boochtek.rb +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b02de4ada297f8524fa07a7104ba7ba709df9bdf01293b644ee407b6a8459dd
4
- data.tar.gz: 79828971327eb8186d681f022f40028e0118c97bd81e287ea04c105276ebbbdf
3
+ metadata.gz: 3db02cc1d2130d7a3e39ddf0ab20bebceba68a3b59d292db8993c630d6d6727d
4
+ data.tar.gz: 9b7e264f064f8060b5de691c575e474fe422a44ba320040ead510dba586c403f
5
5
  SHA512:
6
- metadata.gz: b5189e192ba4e1cb7a33e4b1356de89fb468399d73983ea3573bab18d369434804426a74b2f6a01939838a11c543ebc03f4b0b5fc661af84f39695c50e2075de
7
- data.tar.gz: '08cc3863e0eb14aad60668b1d66d570e396f9d9d59e9f5eaea2e375c93030a3a62cecc711a03af319b7a1172cdb1a32040254b4b5dfbe1f7e8838aeee761b59c'
6
+ metadata.gz: '09cbd0212c65603d868df7bcde0fb7bb20b08c2bd8cca67c263a9fb5a58aca5a2dfe88f394afae2bf4aa6b7bd24efe5832270bd7dde5b159b334118102deb926'
7
+ data.tar.gz: 54b39ca84f94c52cf0b617269ba6d073450a8863ef8ddcd52bcb63ae075ee483fea9e41efd795746cd461c798f53b51c7d1887188cdb1abb0d497f3979104539
data/.rubocop.yml CHANGED
@@ -1,8 +1,118 @@
1
1
  ---
2
- # Use our own configuration for this gem.
3
- inherit_from:
4
- - config/default.yml
2
+ plugins:
3
+ - rubocop-boochtek
5
4
 
5
+ # Ignore some files. NOTE: Exclude does not add to any existing Exclude directives.
6
6
  AllCops:
7
- TargetRubyVersion: 3.1
8
- NewCops: enable
7
+ Exclude:
8
+ - "church*.rb"
9
+ - "vendor/**/*"
10
+ - "**/*.stone"
11
+ NewCops: disable
12
+
13
+ # Allow longer lines.
14
+ Layout/LineLength:
15
+ Max: 160
16
+
17
+ # Use `{}` for blocks that return a value; otherwise use `do` and `end`.
18
+ # We make a few exceptions, mostly for RSpec. And for grammar and transform rules.
19
+ Style/BlockDelimiters:
20
+ EnforcedStyle: semantic
21
+ FunctionalMethods:
22
+ - expect
23
+ AllowedMethods:
24
+ - let
25
+ - let!
26
+ - subject
27
+ - rule
28
+ - rule!
29
+ - terminal
30
+ - terminal!
31
+
32
+ # Allow non-ASCII identifiers, and in comments.
33
+ Naming/AsciiIdentifiers:
34
+ Enabled: false
35
+
36
+ # Some of our classes are going to be long.
37
+ Metrics/ClassLength:
38
+ Max: 250
39
+ Exclude:
40
+ - lib/stone/transform.rb
41
+ - lib/stone/ast/property_access.rb
42
+
43
+ # Sometimes we need to pass a block down recursively, so allow expressing that.
44
+ Performance/RedundantBlockCall:
45
+ Enabled: false
46
+
47
+ Naming/FileName:
48
+ Exclude:
49
+ - lib/stone/type/*
50
+
51
+ # Not sure why `∧` and `∨` are problematic, even after allowing non-ASCII identifiers.
52
+ # Also allow `builtin_ClassName` methods.
53
+ Naming/MethodName:
54
+ Exclude:
55
+ - lib/extensions/boolean.rb
56
+ - lib/stone/top.rb
57
+ AllowedPatterns:
58
+ - as_String
59
+
60
+ # Allow numbered variables in record field comparisons (field1_0, field2_0).
61
+ Naming/VariableNumber:
62
+ Exclude:
63
+ - lib/stone/ast/function_call.rb
64
+
65
+ # I don't believe `module_function` should be outdented like `private`, but there's no way to turn it off for just `module_function`.
66
+ Layout/AccessModifierIndentation:
67
+ EnforcedStyle: outdent
68
+ Exclude:
69
+ - lib/stone/top.rb
70
+
71
+ # Allow `expr = expr + x` in this particular file, to parallel `expr = x + expr` that's right next to it.
72
+ Style/SelfAssignment:
73
+ Exclude:
74
+ - lib/stone/ast/binary_operation.rb
75
+
76
+ # The `grammar` and `transforms` methods are DSLs; don't worry about their size/complexity.
77
+ Metrics/AbcSize:
78
+ AllowedMethods:
79
+ - grammar
80
+ - transforms
81
+ Metrics/MethodLength:
82
+ AllowedMethods:
83
+ - grammar
84
+ - transforms
85
+ Metrics/PerceivedComplexity:
86
+ AllowedMethods:
87
+ - grammar
88
+ - transforms
89
+ Metrics/CyclomaticComplexity:
90
+ AllowedMethods:
91
+ - grammar
92
+ - transforms
93
+
94
+ Style/PercentLiteralDelimiters:
95
+ Exclude:
96
+ - "lib/stone/ast/binary_operation.rb"
97
+
98
+ # Be a bit more lenient with RSpec: don't require filename to match class; allow more than 1 expectation per example.
99
+ RSpec/SpecFilePathFormat:
100
+ Enabled: false
101
+ RSpec/DescribedClass:
102
+ Enabled: false
103
+ RSpec/DescribeClass:
104
+ Enabled: false
105
+
106
+ # RuboCop doesn't like the way we use the `parse_as` custom matcher.
107
+ RSpec/ExpectActual:
108
+ Exclude:
109
+ - "spec/unit/parser/**/**.rb"
110
+
111
+ # I like to put 2 spaces before inline comments.
112
+ Layout/ExtraSpacing:
113
+ Enabled: false
114
+
115
+ # Allow use of a single `module_function` instead of prepending it to every method.
116
+ # Unfortunately, we can't turn this off only for `module_function`.
117
+ Style/AccessModifierDeclarations:
118
+ Enabled: false
data/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
- MIT License
1
+ # MIT License
2
2
 
3
- Copyright (c) 2024 Craig Buchek / BoochTek, LLC
3
+ Copyright (c) 2018-2025 Craig Buchek
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,130 +1,77 @@
1
- # RuboCop BoochTek
1
+ # The Stone Programming Language
2
2
 
3
- BoochTek's shared RuboCop configuration for Ruby projects.
3
+ Stone is a multi-paradigm programming language. It combines the ideas of object-oriented,
4
+ functional, and actor-based languages. It is a typed language, with immutability by default.
4
5
 
5
- ## Installation
6
+ This is a *very* new language, but I've been thinking about the design of the language for
7
+ quite some time. The design documents can be found in [/docs/design](docs/design/README.md).
6
8
 
7
- Add to your `Gemfile`:
9
+ ## Goals
8
10
 
9
- ```ruby
10
- group :development, :test do
11
- gem "rubocop-boochtek", github: "boochtek/rubocop-boochtek"
12
- end
13
- ```
11
+ I have several goals that I keep in mind when designing and implementing Stone. My top-level goals are:
14
12
 
15
- Or install directly:
13
+ - Have fun learning and creating
14
+ - See if I can make something useful
15
+ - Correctness
16
16
 
17
- ```bash
18
- gem install rubocop-boochtek
19
- ```
17
+ For more details, see the [Goals](docs/design/Goals.md) in the design documents.
20
18
 
21
- ## Usage
19
+ ## Specifications
22
20
 
23
- Create a `.rubocop.yml` in your project root:
21
+ The language is described in Markdown, with code blocks showing example code.
22
+ The code blocks also show the expected result of evaluating the code.
23
+ This is also used as the language specification —
24
+ all the example code is checked to ensure that it evaluates to the expected result.
25
+ These can be found in [docs/specs](docs/specs/README.md).
24
26
 
25
- ```yaml
26
- plugins:
27
- - rubocop-boochtek
28
- ```
27
+ ## Building
29
28
 
30
- That's it! The gem automatically loads `rubocop-rspec` and `rubocop-performance`.
29
+ We're using standard `make` to build the system.
31
30
 
32
- ### Overriding Settings
31
+ Currently, we're using Ruby for all pieces of Stone, so you won't need to actually run a build step to get a `stone` binary.
33
32
 
34
- Override any settings in your project's `.rubocop.yml`:
33
+ You can verify that the specs are all passing:
35
34
 
36
- ```yaml
37
- plugins:
38
- - rubocop-boochtek
35
+ ~~~ shell
36
+ make verify-specs
37
+ ~~~
39
38
 
40
- Layout/LineLength:
41
- Max: 160
39
+ ## Running
42
40
 
43
- AllCops:
44
- NewCops: enable
45
- ```
41
+ Binaries are located in the bin directory:
46
42
 
47
- ## Key Style Decisions
43
+ - `stone`
44
+ - `stone parse` - Output parse tree
45
+ - `stone ast` - Output AST (abstract syntax tree)
46
+ - `stone mlir` - Output MLIR (multi-level intermediate representation)
47
+ - `stone llir` - Output LLIR (low-level intermediate representation)
48
+ - `stone compile` - Compile to an executable
49
+ - `stone run` - Compile to an executable and run it
50
+ - `stone eval` - Output the result of each top-level expression (non-interactive REPL)
51
+ - `stone verify` - Verify that results of top-level expressions match expectations in comments
52
+ - `stone repl` - Accept interactive manual input, and show the result of each top-level expression (default if no arguments are given)
53
+ - `stone specs` - Run tests/specs
54
+ - `stone build` - Compile an entire project
55
+ - `stone lint` - Check Stone code for issues
56
+ - `stone format` - Format Stone code
57
+ - `stone lsp` - Start the Language Server Protocol service
58
+ - `stone publish` - Publish to package repository
59
+ - `stone deps` - Manage project dependencies (package manager)
48
60
 
49
- ### `private def` (Inline Access Modifiers)
61
+ Note that most of these commands are still under development — or development has not even started on them yet.
50
62
 
51
- We prefer `private def` over a separate `private` section:
63
+ ## Changelog
52
64
 
53
- ```ruby
54
- # Good
55
- private def my_method
56
- # ...
57
- end
65
+ See the [CHANGELOG](CHANGELOG.md) file for information about what changes have been made in each release.
58
66
 
59
- # Avoided
60
- private
61
-
62
- def my_method
63
- # ...
64
- end
65
- ```
66
-
67
- This makes it easier to see at a glance which methods are private, and makes moving methods around simpler.
68
-
69
- ### Semantic Block Delimiters
70
-
71
- Use `{}` for blocks that return a value; use `do`/`end` otherwise:
72
-
73
- ```ruby
74
- # Good - returns a value
75
- names = users.map { |u| u.name }
76
-
77
- # Good - side effects only
78
- users.each do |user|
79
- send_email(user)
80
- end
81
- ```
82
-
83
- ### Double Quotes
84
-
85
- We use double quotes by default for strings, as they require fewer changes when you later need interpolation.
86
-
87
- ### Other Notable Preferences
88
-
89
- - Line length: 120 characters
90
- - No frozen string literal comments required
91
- - No documentation required for classes/modules
92
- - `fail` for raising exceptions, `raise` for re-raising
93
- - `%x[]` for shell commands (not backticks)
94
- - Unicode allowed in comments and identifiers
95
-
96
- ## Adding Custom Cops
97
-
98
- Create cops in `lib/rubocop/cop/boochtek/`:
99
-
100
- ```ruby
101
- # lib/rubocop/cop/boochtek/my_custom_cop.rb
102
- module RuboCop
103
- module Cop
104
- module Boochtek
105
- class MyCustomCop < Base
106
- MSG = "Custom message here"
107
-
108
- def on_send(node)
109
- # ...
110
- end
111
- end
112
- end
113
- end
114
- end
115
- ```
116
-
117
- Custom cops are auto-loaded when the gem is required.
67
+ ## License
118
68
 
119
- ## Development
69
+ Stone is released under the MIT license. See the [LICENSE](/LICENSE.txt) file for details.
120
70
 
121
- ```bash
122
- git clone https://github.com/boochtek/rubocop-boochtek
123
- cd rubocop-boochtek
124
- bundle install
125
- bundle exec rake
126
- ```
71
+ ## Thanks
127
72
 
128
- ## License
73
+ Thanks to some advice from my friends in the STL Polyglots group in selecting the name.
74
+ Especially Deech.
129
75
 
130
- MIT License. See [LICENSE](LICENSE) for details.
76
+ Thanks to Claude, ChatGPT, and GitHub Copilot for lots of good advice.
77
+ And lots of frustration!
@@ -0,0 +1,5 @@
1
+ # Temporary workaround for SSL certificate verification issues
2
+ # Used by Makefile when downloading remote RuboCop configs.
3
+ require "openssl"
4
+ OpenSSL::SSL.__send__(:remove_const, :VERIFY_PEER)
5
+ OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
@@ -0,0 +1,8 @@
1
+ # Iterate through ARGF input files. Can still use ARGF.filename and ARGF.lineno.
2
+ def ARGF.each_file
3
+ until ARGF.closed?
4
+ yield ARGF.file
5
+ ARGF.skip
6
+ return if ARGV.empty? # Ensure we stop if we were reading from STDIN.
7
+ end
8
+ end
@@ -0,0 +1,48 @@
1
+ class TrueClass
2
+
3
+ include Comparable
4
+
5
+ def ∧(other)
6
+ !!other
7
+ end
8
+
9
+ def ∨(_other)
10
+ true
11
+ end
12
+
13
+ def <=>(other)
14
+ return 1 if other.instance_of?(FalseClass)
15
+ return 0 if other.instance_of?(TrueClass)
16
+ fail ArgumentError, "comparison of Boolean with #{other.class} failed"
17
+ end
18
+
19
+ def ==(other)
20
+ other.instance_of?(TrueClass)
21
+ end
22
+
23
+ end
24
+
25
+
26
+ class FalseClass
27
+
28
+ include Comparable
29
+
30
+ def ∧(_other)
31
+ false
32
+ end
33
+
34
+ def ∨(other)
35
+ !!other
36
+ end
37
+
38
+ def <=>(other)
39
+ return -1 if other.instance_of?(TrueClass)
40
+ return 0 if other.instance_of?(FalseClass)
41
+ fail ArgumentError, "comparison of Boolean with #{other.class} failed"
42
+ end
43
+
44
+ def ==(other)
45
+ other.instance_of?(FalseClass)
46
+ end
47
+
48
+ end
@@ -0,0 +1,10 @@
1
+ class Class
2
+
3
+ def descendants
4
+ ObjectSpace.each_object(Class).select { |klass| klass < self }
5
+ end
6
+
7
+ def subclass(&)
8
+ Class.new(self, &)
9
+ end
10
+ end
@@ -0,0 +1,42 @@
1
+ # TODO: Get these added to the `pretty_ruby` gem, and use that. (Note that it uses refinements, not monkey-patching.)
2
+
3
+ module Enumerable
4
+
5
+ # Allow Scheme-style `rest`.
6
+ def rest(count = 1)
7
+ drop(count)
8
+ end
9
+
10
+ # Allow Scheme-style `tail`.
11
+ # FIXME: Shouldn't this be the same as `rest`?
12
+ def tail
13
+ return self if empty?
14
+ last(size - 1)
15
+ end
16
+
17
+ # Returns `true` iff all the elements are equal. Returns `false` if there are no elements.
18
+ def same?
19
+ uniq.size == 1
20
+ end
21
+
22
+ # Like `first` or `last`, but *requires* that there be exactly one element.
23
+ def only
24
+ fail IndexError, "expected to have exactly 1 element" unless size == 1
25
+ first
26
+ end
27
+
28
+ # Allow Rails-style `second`.
29
+ def second
30
+ drop(1).first
31
+ end
32
+
33
+ # Prefer 1-based indexing to get the `nth` element.
34
+ def nth(n) # rubocop:disable Naming/MethodParameterName
35
+ drop(n - 1).first
36
+ end
37
+
38
+ def map_dig(*)
39
+ map{ |a| a.dig(*) }
40
+ end
41
+
42
+ end
@@ -0,0 +1,7 @@
1
+ class Integer
2
+
3
+ def sign
4
+ ["-", "", "+"][(self <=> 0) + 1]
5
+ end
6
+
7
+ end
@@ -0,0 +1,101 @@
1
+ require "llvm/core"
2
+
3
+
4
+ # Extensions to LLVM::Module to support Stone-specific features
5
+ #
6
+ # NOTE: Many of these registries are now duplicated in Stone::Scope for lexical scoping:
7
+ # - string_constants: types also stored via scope.declare_type(name, type: Stone::Type::String)
8
+ # - record_instances: types also stored via scope.declare_type(name, type: record_type)
9
+ # - function_aliases: values also stored via scope.define(name, value: function)
10
+ #
11
+ # The module registries remain necessary for:
12
+ # - Computed properties (Int@abs) which are global, not lexically scoped
13
+ # - Type inference via the `type` method (which doesn't have access to scope)
14
+ # - Property access lookups that need the actual AST node (not just the type)
15
+ #
16
+ # Future work: migrate fully to scope by updating `type` method signatures to accept scope.
17
+ module Stone
18
+ module LLVMModuleExtensions
19
+
20
+ # Get the function aliases hash, creating it if it doesn't exist
21
+ def function_aliases
22
+ @function_aliases ||= {}
23
+ end
24
+
25
+ # Register a function alias (e.g., for lambdas assigned to constants)
26
+ def register_function_alias(name, function)
27
+ function_aliases[name] = function
28
+ end
29
+
30
+ # Look up a function by name, checking aliases if not found in the module's function table
31
+ def lookup_function(name)
32
+ functions[name] || function_aliases[name]
33
+ end
34
+
35
+ # Get the current lambda parameter storage context
36
+ def lambda_param_storage
37
+ @lambda_param_storage
38
+ end
39
+
40
+ # Set the lambda parameter storage context (used during lambda compilation)
41
+ def lambda_param_storage=(storage)
42
+ @lambda_param_storage = storage
43
+ end
44
+
45
+ # Track which constants are strings (for type checking during returns)
46
+ def string_constants
47
+ @string_constants ||= {}
48
+ end
49
+
50
+ def register_string_constant(name, string_literal)
51
+ string_constants[name] = string_literal
52
+ end
53
+
54
+ def string_constant?(name)
55
+ string_constants.key?(name)
56
+ end
57
+
58
+ # TODO: Type system refactor needed.
59
+ # This ad-hoc tracking of record types and instances should be replaced
60
+ # with a proper Type class hierarchy where:
61
+ # - All types (Bool, Int, String, Records) are Type instances
62
+ # - Types are global constants accessible at runtime
63
+ # - Types have vtables for properties and polymorphic operations
64
+ # - typeof() can get the type of any value
65
+ # - User-defined types work the same as built-in types
66
+
67
+ # Track record type definitions
68
+ def record_types
69
+ @record_types ||= {}
70
+ end
71
+
72
+ def register_record_type(name, record_definition)
73
+ record_types[name] = record_definition
74
+ end
75
+
76
+ def record_type?(name)
77
+ record_types.key?(name)
78
+ end
79
+
80
+ # Track which variables hold record instances (maps variable name -> record type name)
81
+ def record_instances
82
+ @record_instances ||= {}
83
+ end
84
+
85
+ def register_record_instance(variable_name, record_type_name)
86
+ record_instances[variable_name] = record_type_name
87
+ end
88
+
89
+ def record_instance?(variable_name)
90
+ record_instances.key?(variable_name)
91
+ end
92
+
93
+ def record_instance_type(variable_name)
94
+ record_instances[variable_name]
95
+ end
96
+
97
+ end
98
+ end
99
+
100
+ # Extend LLVM::Module with our extensions
101
+ LLVM::Module.include(Stone::LLVMModuleExtensions)
@@ -0,0 +1,34 @@
1
+ class Module
2
+
3
+ # Document methods that MUST be overridden by child classes.
4
+ def abstract(method_name)
5
+ define_method(method_name) do
6
+ fail NotImplementedError
7
+ end
8
+ end
9
+
10
+ # Document methods that CAN or SHOULD be overridden by child classes.
11
+ def overridable(method_name)
12
+ method_name
13
+ end
14
+
15
+ # Document methods that are overriding a parent class.
16
+ def override(method_name)
17
+ method_name
18
+ end
19
+
20
+ # Delegate a set of methods to another object.
21
+ # This is meant to be a simpler version of Rails' ActiveSupport implementation.
22
+ def delegate(*methods, to: nil)
23
+ fail ArgumentError, "Must specify target of `delegate` using `to` keyword argument." if to.nil?
24
+ to = to.to_sym
25
+ to = "self.#{to}" unless to.start_with?("@")
26
+ methods.each do |method|
27
+ define_method(method) do |*args|
28
+ receiver = instance_eval(to)
29
+ receiver.__send__(method, *args)
30
+ end
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,29 @@
1
+ # TODO: Get these added to the `pretty_ruby` gem, and use that. (Note that it uses refinements, not monkey-patching.)
2
+
3
+ class String
4
+
5
+ # Allow Scheme-style `first`.
6
+ def first(count = 1)
7
+ self[0..(count - 1)]
8
+ end
9
+
10
+ # Allow Scheme-style `rest`.
11
+ def rest(count = 1)
12
+ drop(count)
13
+ end
14
+
15
+ def drop(count = 1)
16
+ self.chars.drop(count).join
17
+ end
18
+
19
+ # Allow Scheme-style `tail`.
20
+ def tail
21
+ return self if size.zero?
22
+ last(size - 1)
23
+ end
24
+
25
+ def last(count = 1)
26
+ self.chars.last(count).join
27
+ end
28
+
29
+ end