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.
- checksums.yaml +4 -4
- data/.rubocop.yml +115 -5
- data/LICENSE.md +2 -2
- data/README.md +53 -106
- data/lib/disable_ssl_verify.rb +5 -0
- data/lib/extensions/argf.rb +8 -0
- data/lib/extensions/boolean.rb +48 -0
- data/lib/extensions/class.rb +10 -0
- data/lib/extensions/enumerable.rb +42 -0
- data/lib/extensions/integer.rb +7 -0
- data/lib/extensions/llvm_module.rb +101 -0
- data/lib/extensions/module.rb +34 -0
- data/lib/extensions/string.rb +29 -0
- data/lib/stone/ast/block.rb +88 -0
- data/lib/stone/ast/boolean_literal.rb +43 -0
- data/lib/stone/ast/computed_property_definition.rb +32 -0
- data/lib/stone/ast/constant_definition.rb +124 -0
- data/lib/stone/ast/expression.rb +12 -0
- data/lib/stone/ast/function_call.rb +291 -0
- data/lib/stone/ast/function_type_annotation.rb +31 -0
- data/lib/stone/ast/integer_literal.rb +46 -0
- data/lib/stone/ast/lambda.rb +126 -0
- data/lib/stone/ast/null_literal.rb +29 -0
- data/lib/stone/ast/program_unit/top_function.rb +209 -0
- data/lib/stone/ast/program_unit.rb +412 -0
- data/lib/stone/ast/property_access.rb +557 -0
- data/lib/stone/ast/record_definition.rb +180 -0
- data/lib/stone/ast/record_instantiation.rb +141 -0
- data/lib/stone/ast/reference.rb +145 -0
- data/lib/stone/ast/string_literal.rb +79 -0
- data/lib/stone/ast/two_phase_processing.rb +36 -0
- data/lib/stone/ast/type_annotation.rb +26 -0
- data/lib/stone/ast/type_declaration.rb +28 -0
- data/lib/stone/ast/type_of_expression.rb +41 -0
- data/lib/stone/ast/type_reference.rb +28 -0
- data/lib/stone/ast/union_type_annotation.rb +26 -0
- data/lib/stone/ast.rb +64 -0
- data/lib/stone/built_ins.rb +245 -0
- data/lib/stone/error/argument_error.rb +7 -0
- data/lib/stone/error/arity_error.rb +7 -0
- data/lib/stone/error/overflow.rb +18 -0
- data/lib/stone/error/property_error.rb +7 -0
- data/lib/stone/error/reference_error.rb +4 -0
- data/lib/stone/error/type_error.rb +7 -0
- data/lib/stone/error.rb +12 -0
- data/lib/stone/grammar.rb +124 -0
- data/lib/stone/libc.rb +27 -0
- data/lib/stone/prelude.stone +11 -0
- data/lib/stone/rtti.rb +182 -0
- data/lib/stone/scope.rb +85 -0
- data/lib/stone/transform.rb +410 -0
- data/lib/stone/type.rb +323 -0
- data/lib/stone/type_context.rb +38 -0
- data/lib/stone/type_registry.rb +73 -0
- data/lib/stone/types.rb +104 -0
- data/lib/stone.rb +70 -0
- metadata +54 -24
- data/CHANGELOG.md +0 -28
- data/Rakefile +0 -5
- data/config/default.yml +0 -277
- data/lib/rubocop/boochtek/plugin.rb +0 -43
- data/lib/rubocop/boochtek/version.rb +0 -7
- data/lib/rubocop/boochtek.rb +0 -18
- data/lib/rubocop/cop/boochtek/compact_endless_methods.rb +0 -103
- data/lib/rubocop-boochtek.rb +0 -3
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require "stone/ast/expression"
|
|
2
|
+
require "stone/rtti"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
module Stone
|
|
6
|
+
class AST
|
|
7
|
+
class TypeReference < Stone::AST::Expression
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@name = :type_reference
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def type(_context = nil)
|
|
14
|
+
Stone::Type::Type
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_llir(_builder, mod, _scope = Stone::Scope.top_level)
|
|
18
|
+
# Return pointer to the Type type constant
|
|
19
|
+
Stone::RTTI.type_constant_for(mod, Stone::Type::Type)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_s
|
|
23
|
+
"Type"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require "stone/ast"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Stone
|
|
5
|
+
class AST
|
|
6
|
+
class UnionTypeAnnotation < Stone::AST
|
|
7
|
+
|
|
8
|
+
attr_reader :alternatives
|
|
9
|
+
|
|
10
|
+
def initialize(alternatives)
|
|
11
|
+
@alternatives = alternatives
|
|
12
|
+
@name = :union_type_annotation
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_s
|
|
16
|
+
@alternatives.map(&:to_s).join(" | ")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_type(registry)
|
|
20
|
+
alternative_types = @alternatives.map { |t| t.to_type(registry) }.compact
|
|
21
|
+
Stone::Type.union(alternatives: alternative_types)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/stone/ast.rb
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module Stone
|
|
2
|
+
class AST
|
|
3
|
+
|
|
4
|
+
attr_reader :name, :children
|
|
5
|
+
|
|
6
|
+
def initialize(name, children = nil)
|
|
7
|
+
@name = name
|
|
8
|
+
@children = children
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Helper module for checking if a node represents a record instance
|
|
12
|
+
module RecordHelpers
|
|
13
|
+
# Check if a node is a Reference to a record instance
|
|
14
|
+
module_function def record_instance?(node, mod)
|
|
15
|
+
node.is_a?(Stone::AST::Reference) && mod.record_instance?(node.identifier)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Shared type resolution logic for AST nodes
|
|
21
|
+
module TypeResolver
|
|
22
|
+
module_function def resolve_node_type(node, mod)
|
|
23
|
+
case node
|
|
24
|
+
when Stone::AST::Reference then node.type(mod)
|
|
25
|
+
when Stone::AST::FunctionCall then node.type(mod)
|
|
26
|
+
when Stone::AST::PropertyAccess then resolve_property_type(node, mod)
|
|
27
|
+
else node.type
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
module_function def resolve_property_type(node, mod)
|
|
32
|
+
receiver_type = resolve_node_type(node.receiver, mod)
|
|
33
|
+
return nil unless receiver_type
|
|
34
|
+
|
|
35
|
+
receiver_type.property_return_type(node.property)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Shared utilities for working with field annotations
|
|
40
|
+
module FieldHelpers
|
|
41
|
+
# Check if an annotation represents a union type
|
|
42
|
+
module_function def union_annotation?(annotation)
|
|
43
|
+
annotation.is_a?(Stone::AST::UnionTypeAnnotation)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Resolve a field's annotation to a Stone::Type
|
|
47
|
+
# Returns nil if the type cannot be resolved
|
|
48
|
+
module_function def resolve_field_type(field, registry = Stone::Type::Registry)
|
|
49
|
+
annotation = field[:type]
|
|
50
|
+
if annotation.respond_to?(:to_type)
|
|
51
|
+
annotation.to_type(registry)
|
|
52
|
+
else
|
|
53
|
+
registry.lookup(field[:type_name] || field[:type])
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Get the string representation of a field's type
|
|
58
|
+
module_function def field_type_name(field)
|
|
59
|
+
field[:type_name] || field[:type].to_s
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
require "llvm/core"
|
|
2
|
+
require "stone/types"
|
|
3
|
+
require "stone/rtti"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module Stone
|
|
7
|
+
# Handles registration of built-in constants (including functions and operators).
|
|
8
|
+
class BuiltIns
|
|
9
|
+
|
|
10
|
+
def initialize(mod)
|
|
11
|
+
@mod = mod
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def setup
|
|
15
|
+
setup_rtti
|
|
16
|
+
define_predefined_constants
|
|
17
|
+
define_builtin_functions
|
|
18
|
+
register_builtin_function_types
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private def setup_rtti
|
|
22
|
+
Stone::RTTI.new(@mod).setup
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private def define_predefined_constants
|
|
26
|
+
define_constant("ZERO", 0)
|
|
27
|
+
define_constant("ONE", 1)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private def define_builtin_functions
|
|
31
|
+
define_if_function
|
|
32
|
+
define_sum_function
|
|
33
|
+
define_comparison_operators
|
|
34
|
+
define_boolean_operators
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private def define_constant(name, value)
|
|
38
|
+
@mod.globals.add(LLVM::Int64, name).tap do |global|
|
|
39
|
+
global.initializer = LLVM::Int64.from_i(value)
|
|
40
|
+
global.linkage = :internal
|
|
41
|
+
global.global_constant = true
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private def define_comparison_operators
|
|
46
|
+
define_comparison("==", :eq)
|
|
47
|
+
define_comparison("!=", :ne)
|
|
48
|
+
define_comparison("≠", :ne)
|
|
49
|
+
define_comparison("<", :slt)
|
|
50
|
+
define_comparison("<=", :sle)
|
|
51
|
+
define_comparison("≤", :sle)
|
|
52
|
+
define_comparison(">", :sgt)
|
|
53
|
+
define_comparison(">=", :sge)
|
|
54
|
+
define_comparison("≥", :sge)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private def define_comparison(name, predicate)
|
|
58
|
+
# Define binary comparison function
|
|
59
|
+
# <(a, b) checks a < b
|
|
60
|
+
# Chained comparisons like <(a, b, c) are handled inline in FunctionCall#to_llir
|
|
61
|
+
@mod.functions.add(name, comparison_function_type).tap { |func| build_icmp_body(func, predicate) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private def comparison_function_type
|
|
65
|
+
@comparison_function_type ||= LLVM::Type.function([LLVM::Int64.type, LLVM::Int64.type], LLVM::Int1.type)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private def build_icmp_body(func, predicate)
|
|
69
|
+
func.basic_blocks.append("entry").build do |builder|
|
|
70
|
+
result = builder.icmp(predicate, func.params[0], func.params[1], "cmp_result")
|
|
71
|
+
builder.ret(result)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private def define_boolean_operators
|
|
76
|
+
define_boolean_binary("and", :and)
|
|
77
|
+
define_boolean_binary("or", :or)
|
|
78
|
+
define_boolean_binary("xor", :xor)
|
|
79
|
+
define_boolean_unary("not", :xor)
|
|
80
|
+
|
|
81
|
+
define_boolean_binary("∧", :and)
|
|
82
|
+
define_boolean_binary("∨", :or)
|
|
83
|
+
define_boolean_binary("⊻", :xor)
|
|
84
|
+
define_boolean_unary("¬", :xor)
|
|
85
|
+
|
|
86
|
+
# TODO: Overload `==` and `!=` for Bool type once we support function overloading.
|
|
87
|
+
# Currently these operators only work for Int. For Booleans, use `xor` for XOR/inequality,
|
|
88
|
+
# or the integer comparison operators will work since TRUE=1 and FALSE=0.
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private def define_boolean_binary(name, instruction)
|
|
92
|
+
@mod.functions.add(name, boolean_binary_function_type).tap do |func|
|
|
93
|
+
build_boolean_binary_body(func, instruction)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private def define_boolean_unary(name, instruction)
|
|
98
|
+
@mod.functions.add(name, boolean_unary_function_type).tap do |func|
|
|
99
|
+
build_boolean_unary_body(func, instruction)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private def boolean_binary_function_type
|
|
104
|
+
@boolean_binary_function_type ||= LLVM::Type.function([LLVM::Int1.type, LLVM::Int1.type], LLVM::Int1.type)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private def boolean_unary_function_type
|
|
108
|
+
@boolean_unary_function_type ||= LLVM::Type.function([LLVM::Int1.type], LLVM::Int1.type)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private def build_boolean_binary_body(func, instruction)
|
|
112
|
+
func.basic_blocks.append("entry").build do |builder|
|
|
113
|
+
result = builder.send(instruction, func.params[0], func.params[1], "bool_result") # rubocop:disable Style/Send
|
|
114
|
+
builder.ret(result)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private def build_boolean_unary_body(func, instruction)
|
|
119
|
+
func.basic_blocks.append("entry").build do |builder|
|
|
120
|
+
# NOT is implemented as XOR with TRUE (i.e., not(a) = a xor TRUE)
|
|
121
|
+
# The instruction parameter should always be :xor for unary boolean operations
|
|
122
|
+
result = builder.send(instruction, func.params[0], LLVM::TRUE, "not_result") # rubocop:disable Style/Send
|
|
123
|
+
builder.ret(result)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Define the sum function: sum(i64, i64) -> i64.
|
|
128
|
+
# Uses LLVM's sadd.with.overflow intrinsic for overflow detection.
|
|
129
|
+
private def define_sum_function
|
|
130
|
+
i64 = LLVM::Int64.type
|
|
131
|
+
function_type = LLVM::Type.function([i64, i64], i64)
|
|
132
|
+
@mod.functions.add("sum", function_type).tap { |func| build_sum_body(func) }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private def build_sum_body(func)
|
|
136
|
+
func.basic_blocks.append("entry").build do |builder|
|
|
137
|
+
intrinsic = sadd_with_overflow_intrinsic
|
|
138
|
+
result = builder.call(intrinsic, func.params[0], func.params[1], "sadd_result")
|
|
139
|
+
# TODO: Check for overflow (something like `builder.extract_value(result, 0) == 1`) and branch to return a Stone::Type::Error if true.
|
|
140
|
+
# overflowed = builder.extract_value(result, 1)
|
|
141
|
+
sum_value = builder.extract_value(result, 0, "sum")
|
|
142
|
+
builder.ret(sum_value)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
private def sadd_with_overflow_intrinsic
|
|
147
|
+
intrinsic_name = "llvm.sadd.with.overflow.i64"
|
|
148
|
+
return @mod.functions[intrinsic_name] if @mod.functions[intrinsic_name]
|
|
149
|
+
|
|
150
|
+
i64 = LLVM::Int64.type
|
|
151
|
+
i1 = LLVM::Int1.type
|
|
152
|
+
result_type = LLVM::Type.struct([i64, i1], false)
|
|
153
|
+
function_type = LLVM::Type.function([i64, i64], result_type)
|
|
154
|
+
@mod.functions.add(intrinsic_name, function_type)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Define the if function: if(i1 condition, block* then_block, block* else_block) -> i64.
|
|
158
|
+
# Takes a boolean condition and two block pointers, executes the appropriate block.
|
|
159
|
+
private def define_if_function
|
|
160
|
+
i1 = LLVM::Int1.type
|
|
161
|
+
i64 = LLVM::Int64.type
|
|
162
|
+
# Block type: () -> i64
|
|
163
|
+
block_type = LLVM::Type.function([], i64)
|
|
164
|
+
block_ptr = LLVM::Type.pointer(block_type)
|
|
165
|
+
# if takes: (i1 condition, block* then_block, block* else_block) -> i64
|
|
166
|
+
function_type = LLVM::Type.function([i1, block_ptr, block_ptr], i64)
|
|
167
|
+
@mod.functions.add("if", function_type).tap { |func| build_if_body(func) }
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
private def build_if_body(func)
|
|
171
|
+
blocks = create_if_basic_blocks(func)
|
|
172
|
+
build_if_entry(blocks, func.params[0])
|
|
173
|
+
then_result = build_if_branch(blocks[:then_bb], blocks[:merge_bb], func.params[1], "then_result")
|
|
174
|
+
else_result = build_if_branch(blocks[:else_bb], blocks[:merge_bb], func.params[2], "else_result")
|
|
175
|
+
build_if_merge(blocks, then_result, else_result)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
private def create_if_basic_blocks(func)
|
|
179
|
+
{
|
|
180
|
+
entry_bb: func.basic_blocks.append("entry"),
|
|
181
|
+
then_bb: func.basic_blocks.append("then"),
|
|
182
|
+
else_bb: func.basic_blocks.append("else"),
|
|
183
|
+
merge_bb: func.basic_blocks.append("merge")
|
|
184
|
+
}
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
private def build_if_entry(blocks, condition)
|
|
188
|
+
blocks[:entry_bb].build { |builder| builder.cond(condition, blocks[:then_bb], blocks[:else_bb]) }
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
private def build_if_branch(branch_bb, merge_bb, block_ptr, result_name)
|
|
192
|
+
result = nil
|
|
193
|
+
branch_bb.build do |builder|
|
|
194
|
+
result = builder.call2(block_func_type, block_ptr, result_name)
|
|
195
|
+
builder.br(merge_bb)
|
|
196
|
+
end
|
|
197
|
+
result
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
private def build_if_merge(blocks, then_result, else_result)
|
|
201
|
+
blocks[:merge_bb].build do |builder|
|
|
202
|
+
phi = builder.phi(LLVM::Int64.type, {blocks[:then_bb] => then_result, blocks[:else_bb] => else_result}, "if_result")
|
|
203
|
+
builder.ret(phi)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
private def block_func_type
|
|
208
|
+
@block_func_type ||= LLVM::Type.function([], LLVM::Int64.type)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
212
|
+
private def register_builtin_function_types
|
|
213
|
+
int = Stone::Type::Int
|
|
214
|
+
bool = Stone::Type::Bool
|
|
215
|
+
|
|
216
|
+
# sum(Int, Int) -> Int
|
|
217
|
+
Stone::Type::Registry.register_as("sum", Stone::Type.function(param_types: [int, int], return_type: int))
|
|
218
|
+
|
|
219
|
+
# if(Bool, Block, Block) -> Int
|
|
220
|
+
# Note: Block type not yet in type system, so we just record param count conceptually
|
|
221
|
+
Stone::Type::Registry.register_as("if", Stone::Type.function(param_types: [bool], return_type: int))
|
|
222
|
+
|
|
223
|
+
# Boolean operators
|
|
224
|
+
# and(Bool, Bool) -> Bool
|
|
225
|
+
Stone::Type::Registry.register_as("and", Stone::Type.function(param_types: [bool, bool], return_type: bool))
|
|
226
|
+
|
|
227
|
+
# or(Bool, Bool) -> Bool
|
|
228
|
+
Stone::Type::Registry.register_as("or", Stone::Type.function(param_types: [bool, bool], return_type: bool))
|
|
229
|
+
|
|
230
|
+
# xor(Bool, Bool) -> Bool
|
|
231
|
+
Stone::Type::Registry.register_as("xor", Stone::Type.function(param_types: [bool, bool], return_type: bool))
|
|
232
|
+
|
|
233
|
+
# not(Bool) -> Bool
|
|
234
|
+
Stone::Type::Registry.register_as("not", Stone::Type.function(param_types: [bool], return_type: bool))
|
|
235
|
+
|
|
236
|
+
# Unicode operator aliases
|
|
237
|
+
Stone::Type::Registry.register_as("∧", Stone::Type.function(param_types: [bool, bool], return_type: bool))
|
|
238
|
+
Stone::Type::Registry.register_as("∨", Stone::Type.function(param_types: [bool, bool], return_type: bool))
|
|
239
|
+
Stone::Type::Registry.register_as("⊻", Stone::Type.function(param_types: [bool, bool], return_type: bool))
|
|
240
|
+
Stone::Type::Registry.register_as("¬", Stone::Type.function(param_types: [bool], return_type: bool))
|
|
241
|
+
end
|
|
242
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
243
|
+
|
|
244
|
+
end
|
|
245
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require "stone/error"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Stone
|
|
5
|
+
class Error
|
|
6
|
+
class Overflow < Stone::Error
|
|
7
|
+
|
|
8
|
+
attr_reader :literal
|
|
9
|
+
|
|
10
|
+
def initialize(message = nil, location:, literal:)
|
|
11
|
+
@literal = literal
|
|
12
|
+
message ||= "Overflow Error: #{literal} falls outside 64-bit Int range at line #{location.line}, column #{location.column}"
|
|
13
|
+
super(message, location: location)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/stone/error.rb
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
require "grammy/grammar"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Stone
|
|
5
|
+
class Grammar < Grammy::Grammar
|
|
6
|
+
|
|
7
|
+
# Identifier and operator patterns
|
|
8
|
+
ALPHA_IDENTIFIER = /[a-zA-Z_][a-zA-Z0-9_]*[!?]?/
|
|
9
|
+
COMPUTED_PROPERTY_ID = /[A-Z][a-zA-Z0-9_]*@[a-zA-Z_][a-zA-Z0-9_]*[!?]?/
|
|
10
|
+
COMPARISON_OPERATOR = /(==|!=|<=|>=|≠|≤|≥|<|>)/
|
|
11
|
+
BOOLEAN_OPERATOR = /(∧|∨|⊻|¬)/
|
|
12
|
+
|
|
13
|
+
start :program_unit
|
|
14
|
+
|
|
15
|
+
# Program structure
|
|
16
|
+
rule(:program_unit) { statement_list + eof }
|
|
17
|
+
rule(:statement_list) { list(statement, separated_by: statement_separator, allow_repeated_separator: true) }
|
|
18
|
+
rule(:statement_separator) { newline | semi }
|
|
19
|
+
rule(:statement) { (definition | expression | comment) + (ws_no_nl? + comment)[0..1] }
|
|
20
|
+
rule(:definition) { (computed_property_id | identifier) + ws! + define_op + ws! + expression }
|
|
21
|
+
|
|
22
|
+
# Expressions
|
|
23
|
+
# Expression hierarchy (from lowest to highest precedence):
|
|
24
|
+
# 1. comparison_operation (lowest - binary/variadic operators)
|
|
25
|
+
# 2. boolean_operation (binary/variadic boolean operators)
|
|
26
|
+
# 3. postfix (function_call, property_access)
|
|
27
|
+
# 4. primary (highest - atoms)
|
|
28
|
+
#
|
|
29
|
+
# Comparisons operate on boolean operations, which operate on postfix expressions.
|
|
30
|
+
# boolean_operation with zero operators is equivalent to postfix_expression.
|
|
31
|
+
# This allows mixing with parentheses: (5 < 3) ∧ TRUE
|
|
32
|
+
# Both can chain: 1 < 2 < 3 desugars to <(1, 2, 3)
|
|
33
|
+
rule(:expression) { type_declaration | comparison_operation | boolean_operation }
|
|
34
|
+
rule(:type_declaration) { identifier + ws! + str("::") + ws! + type_annotation }
|
|
35
|
+
rule(:type_annotation) { type_union }
|
|
36
|
+
rule(:type_union) { type_term + (ws? + str("|") + ws? + type_term)[0..] }
|
|
37
|
+
rule(:type_term) { type_function | parens(type_annotation) | type_name }
|
|
38
|
+
rule(:type_function) { type_params + ws? + str("->") + ws? + type_return }
|
|
39
|
+
rule(:type_params) { parens(comma_separated(type_annotation, allow_trailing: false)) }
|
|
40
|
+
rule(:type_return) { type_return_union | parens(type_annotation) }
|
|
41
|
+
rule(:type_return_union) { type_name + (ws? + str("|") + ws? + type_name)[0..] }
|
|
42
|
+
rule(:type_name) { identifier }
|
|
43
|
+
rule(:comparison_operation) { boolean_operation + (ws! + comparison_operator + ws! + boolean_operation)[1..] }
|
|
44
|
+
rule(:boolean_operation) { postfix_expression + (ws! + boolean_operator + ws! + postfix_expression)[0..] }
|
|
45
|
+
rule(:postfix_expression) { primary + (argument_list | property_accessor)[0..] }
|
|
46
|
+
rule(:property_accessor) { str(".") + identifier }
|
|
47
|
+
rule(:primary) { parens(expression) | type_of_expression | record_definition | literal | type_reference | reference | lambda | block }
|
|
48
|
+
rule(:type_of_expression) { str("Type.of") + parens(expression) }
|
|
49
|
+
rule(:type_reference) { str("Type") }
|
|
50
|
+
rule(:argument_list) { parens(comma_separated(argument)) }
|
|
51
|
+
rule(:argument) { expression }
|
|
52
|
+
rule(:literal) { literal_null | literal_boolean | literal_string | literal_i64 }
|
|
53
|
+
rule(:reference) { identifier }
|
|
54
|
+
rule(:lambda) { lambda_op + parameter_list + ws? + block }
|
|
55
|
+
rule(:parameter_list) { parens(comma_separated(parameter, allow_trailing: false)) }
|
|
56
|
+
rule(:parameter) { identifier }
|
|
57
|
+
rule(:block) { braces(statement_list) }
|
|
58
|
+
rule(:record_definition) { str("Record") + parens(comma_separated(type_declaration, allow_trailing: false)) }
|
|
59
|
+
|
|
60
|
+
# Custom Matchers/Combinators
|
|
61
|
+
# Match the passed-in matchers/combinators within parentheses, with whitespace allowed.
|
|
62
|
+
def parens(submatchers)
|
|
63
|
+
str("(") + ws? + submatchers + ws? + str(")")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Match the passed-in matchers/combinators within curly braces, with whitespace allowed.
|
|
67
|
+
def braces(submatchers)
|
|
68
|
+
str("{") + ws? + submatchers + ws? + str("}")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Match a comma-separated list, optionally allowing a trailing comma.
|
|
72
|
+
def comma_separated(submatchers, allow_trailing: true)
|
|
73
|
+
list(submatchers, separated_by: str(","), allow_trailing: allow_trailing)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Match a list of matchers/combinators, separated by a specified separator.
|
|
77
|
+
# - Supports allowing trailing separator (default: true)
|
|
78
|
+
# - Supports allowing repeated consecutive separators (default: false)
|
|
79
|
+
# - Supports minimum item count (default: 0)
|
|
80
|
+
def list(items, separated_by:, allow_trailing: true, allow_repeated_separator: false, min: 0)
|
|
81
|
+
list_repetition = min.zero? ? [0..] : [min..]
|
|
82
|
+
items_with_separators = ws? + items + (separated_by + ws? + items)[0..]
|
|
83
|
+
if allow_trailing
|
|
84
|
+
trailing_repetition = allow_repeated_separator ? [0..] : [0..1]
|
|
85
|
+
(items_with_separators + (separated_by + ws?)[*trailing_repetition])[*list_repetition]
|
|
86
|
+
else
|
|
87
|
+
items_with_separators[*list_repetition]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Identifiers
|
|
92
|
+
terminal(:computed_property_id) { COMPUTED_PROPERTY_ID }
|
|
93
|
+
terminal(:identifier) { Regexp.union(ALPHA_IDENTIFIER, BOOLEAN_OPERATOR, COMPARISON_OPERATOR) }
|
|
94
|
+
|
|
95
|
+
# Operators
|
|
96
|
+
terminal(:comparison_operator) { COMPARISON_OPERATOR }
|
|
97
|
+
terminal(:boolean_operator) { BOOLEAN_OPERATOR }
|
|
98
|
+
terminal(:lambda_op) { "λ" }
|
|
99
|
+
terminal(:define_op) { ":=" }
|
|
100
|
+
|
|
101
|
+
# Literals
|
|
102
|
+
rule(:literal_null) { str("NULL") }
|
|
103
|
+
rule(:literal_boolean) { reg(/(TRUE|FALSE)/) }
|
|
104
|
+
# WARNING: String literal rule must come before comment rule to handle `#` inside strings correctly.
|
|
105
|
+
rule(:literal_string) { reg(/"(?:[^"\\]|\\.)*"/) }
|
|
106
|
+
# NOTE: Decimal must be last to avoid consuming `0` from the prefixes.
|
|
107
|
+
rule(:literal_i64) { literal_i64_binary | literal_i64_octal | literal_i64_hex | literal_i64_decimal }
|
|
108
|
+
terminal(:literal_i64_decimal) { /[+-]?\d+/ }
|
|
109
|
+
terminal(:literal_i64_binary) { /[+-]?0b[01]+/ }
|
|
110
|
+
terminal(:literal_i64_octal) { /[+-]?0o[0-7]+/ }
|
|
111
|
+
terminal(:literal_i64_hex) { /[+-]?0x[0-9a-fA-F]+/ }
|
|
112
|
+
|
|
113
|
+
# Whitespace and separators
|
|
114
|
+
terminal(:comment) { /#[^\n\r]*/ } # Does **NOT** include trailing EOL.
|
|
115
|
+
terminal(:newline) { /(\n|\r\n)/ }
|
|
116
|
+
terminal(:semi) { ";" }
|
|
117
|
+
terminal(:ws) { /[ \t\n\r]+/ }
|
|
118
|
+
terminal(:ws_no_nl) { /[ \t]+/ }
|
|
119
|
+
rule(:ws?) { ws[0..] } # White space is **allowed**.
|
|
120
|
+
rule(:ws!) { ws[1..] } # White space is **required**.
|
|
121
|
+
rule(:ws_no_nl?) { ws_no_nl[0..] }
|
|
122
|
+
|
|
123
|
+
end
|
|
124
|
+
end
|
data/lib/stone/libc.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require "llvm/core"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Stone
|
|
5
|
+
# Module for declaring C standard library functions in LLVM
|
|
6
|
+
module LibC
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def get_or_declare_strcmp(mod)
|
|
11
|
+
return mod.functions["strcmp"] if mod.functions["strcmp"]
|
|
12
|
+
|
|
13
|
+
# Declare strcmp: i32 strcmp(i8*, i8*)
|
|
14
|
+
strcmp_type = LLVM::Type.function([LLVM::Type.pointer(LLVM::Int8), LLVM::Type.pointer(LLVM::Int8)], LLVM::Int32)
|
|
15
|
+
mod.functions.add("strcmp", strcmp_type)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def get_or_declare_strlen(mod)
|
|
19
|
+
return mod.functions["strlen"] if mod.functions["strlen"]
|
|
20
|
+
|
|
21
|
+
# Declare strlen: i64 strlen(i8*)
|
|
22
|
+
strlen_type = LLVM::Type.function([LLVM::Type.pointer(LLVM::Int8)], LLVM::Int64)
|
|
23
|
+
mod.functions.add("strlen", strlen_type)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Stone Standard Library Prelude
|
|
2
|
+
# This file is automatically loaded before user code.
|
|
3
|
+
# It defines standard properties for built-in types.
|
|
4
|
+
|
|
5
|
+
# Boolean properties
|
|
6
|
+
Bool@not := λ(this) { if(this, { FALSE }, { TRUE }) }
|
|
7
|
+
|
|
8
|
+
# Integer properties
|
|
9
|
+
Int@positive? := λ(this) { this > 0 }
|
|
10
|
+
Int@negative? := λ(this) { this < 0 }
|
|
11
|
+
Int@zero? := λ(this) { this == 0 }
|