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,88 @@
|
|
|
1
|
+
require "stone/ast"
|
|
2
|
+
require "stone/ast/two_phase_processing"
|
|
3
|
+
require "stone/types"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module Stone
|
|
7
|
+
class AST
|
|
8
|
+
class Block < Stone::AST::Expression
|
|
9
|
+
include TwoPhaseProcessing
|
|
10
|
+
|
|
11
|
+
attr_reader :statements
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
attr_accessor :block_count
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
@block_count = 0
|
|
18
|
+
|
|
19
|
+
def initialize(statements)
|
|
20
|
+
@name = :block
|
|
21
|
+
@statements = statements
|
|
22
|
+
@block_id = next_block_id
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_llir(_builder, mod, scope = Stone::Scope.top_level)
|
|
26
|
+
# Check if function already exists (happens if to_llir called multiple times on same block).
|
|
27
|
+
mod.functions[function_name] || create_function(mod, function_name, function_type, scope)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_s
|
|
31
|
+
"{ #{statements.join("\n")} }"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def type(context = nil)
|
|
35
|
+
last_expression = statements.reverse.find { |stmt| stmt.type(context) }
|
|
36
|
+
last_expression&.type(context)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private def next_block_id
|
|
40
|
+
self.class.block_count += 1
|
|
41
|
+
self.class.block_count
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private def function_type
|
|
45
|
+
# Blocks take no parameters and return an i64.
|
|
46
|
+
LLVM::Type.function([], I64)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private def function_name
|
|
50
|
+
"__#{function_prefix}_#{@block_id}__"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private def function_prefix
|
|
54
|
+
"block"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private def create_function(mod, function_name, function_type, scope)
|
|
58
|
+
mod.functions.add(function_name, function_type).tap do |func|
|
|
59
|
+
build_function_body(func, mod, scope)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private def build_function_body(func, mod, scope)
|
|
64
|
+
func.basic_blocks.append("entry").build do |block_builder|
|
|
65
|
+
evaluate_body_and_return(block_builder, mod, scope)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Evaluate all statements in block and return value of last statement.
|
|
70
|
+
def evaluate_body_and_return(builder, mod, scope)
|
|
71
|
+
child_scope = scope.child
|
|
72
|
+
register_type_declarations(child_scope)
|
|
73
|
+
last_result = compile_statements(builder, mod, child_scope)
|
|
74
|
+
builder.ret(last_result)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private def compile_statements(builder, mod, scope)
|
|
78
|
+
results = other_statements.map { |stmt| stmt.to_llir(builder, mod, scope) }
|
|
79
|
+
results.compact.last || LLVM::Int64.from_i(0)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private def all_statements
|
|
83
|
+
@all_statements ||= Array(statements).compact
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require "stone/ast/expression"
|
|
2
|
+
|
|
3
|
+
# TODO: Eventually, TRUE and FALSE should be top-level constants defined as
|
|
4
|
+
# __BUILTIN__.Boolean.TRUE and __BUILTIN__.Boolean.FALSE rather than literals.
|
|
5
|
+
|
|
6
|
+
module Stone
|
|
7
|
+
class AST
|
|
8
|
+
class BooleanLiteral < Stone::AST::Expression
|
|
9
|
+
|
|
10
|
+
TRUE = 1
|
|
11
|
+
FALSE = 0
|
|
12
|
+
|
|
13
|
+
def self.parse(text, location)
|
|
14
|
+
new(text)
|
|
15
|
+
rescue ex
|
|
16
|
+
raise Stone::Error(ex.message, location) # TODO: Make a more specific error?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_reader :value
|
|
20
|
+
|
|
21
|
+
def initialize(value)
|
|
22
|
+
@name = :boolean_literal
|
|
23
|
+
case value
|
|
24
|
+
when "TRUE"
|
|
25
|
+
@value = TRUE
|
|
26
|
+
when "FALSE"
|
|
27
|
+
@value = FALSE
|
|
28
|
+
else
|
|
29
|
+
fail "expected TRUE or FALSE"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def to_llir(_builder, _mod, _scope = Stone::Scope.top_level)
|
|
34
|
+
@value == TRUE ? LLVM::TRUE : LLVM::FALSE
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def type(_context = nil)
|
|
38
|
+
Stone::Type::Bool
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require "stone/ast"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Stone
|
|
5
|
+
class AST
|
|
6
|
+
class ComputedPropertyDefinition < Stone::AST
|
|
7
|
+
|
|
8
|
+
attr_reader :type_name, :property_name, :lambda
|
|
9
|
+
|
|
10
|
+
def initialize(type_name, property_name, lambda)
|
|
11
|
+
@name = :computed_property_definition
|
|
12
|
+
@type_name = type_name
|
|
13
|
+
@property_name = property_name
|
|
14
|
+
@lambda = lambda
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_llir(builder, mod, scope = Stone::Scope.top_level)
|
|
18
|
+
# Generate the lambda function
|
|
19
|
+
func = @lambda.to_llir(builder, mod, scope)
|
|
20
|
+
|
|
21
|
+
# Register as a function alias with the name "Type@property"
|
|
22
|
+
# This allows lookup via mod.lookup_function("Type@property")
|
|
23
|
+
function_name = "#{@type_name}@#{@property_name}"
|
|
24
|
+
mod.register_function_alias(function_name, func)
|
|
25
|
+
|
|
26
|
+
# Return nil (computed properties are definitions, not expressions)
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
require "stone/ast/expression"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Stone
|
|
5
|
+
class AST
|
|
6
|
+
class ConstantDefinition < Stone::AST::Expression
|
|
7
|
+
|
|
8
|
+
attr_reader :identifier, :value_expression
|
|
9
|
+
|
|
10
|
+
def initialize(identifier, value_expression)
|
|
11
|
+
@identifier = identifier
|
|
12
|
+
@value_expression = value_expression
|
|
13
|
+
@name = :constant_definition
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_llir(builder, mod, scope = Stone::Scope.top_level)
|
|
17
|
+
return register_record_type(mod, builder, scope) if value_expression.is_a?(Stone::AST::RecordDefinition)
|
|
18
|
+
|
|
19
|
+
llvm_value = value_expression.to_llir(builder, mod, scope)
|
|
20
|
+
|
|
21
|
+
return handle_record_definition(mod, llvm_value) if value_expression.is_a?(Stone::AST::RecordDefinition)
|
|
22
|
+
return register_function_alias(mod, llvm_value, scope) if llvm_value.is_a?(LLVM::Function)
|
|
23
|
+
|
|
24
|
+
handle_value_expression(mod, scope)
|
|
25
|
+
create_global(mod, builder, llvm_value, scope)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def type(_context = nil)
|
|
29
|
+
# ConstantDefinition doesn't have a value itself, it defines a binding
|
|
30
|
+
# Return nil to indicate this isn't an expression
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private def create_global(mod, builder, llvm_value, scope)
|
|
35
|
+
global = mod.globals.add(llvm_value.type, identifier)
|
|
36
|
+
global.linkage = :internal
|
|
37
|
+
|
|
38
|
+
if literal_constant?
|
|
39
|
+
initialize_as_constant(global, llvm_value)
|
|
40
|
+
else
|
|
41
|
+
initialize_at_runtime(global, builder, llvm_value)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Register in scope for lexical lookup
|
|
45
|
+
scope.define(identifier, value: llvm_value)
|
|
46
|
+
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private def handle_record_definition(mod, llvm_value)
|
|
51
|
+
mod.register_record_type(identifier, value_expression)
|
|
52
|
+
mod.register_function_alias(identifier, llvm_value)
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Register type information for expressions whose types cannot be inferred from LLVM types.
|
|
57
|
+
# - Strings: LLVM value is ptr-as-i64, need explicit String type tracking
|
|
58
|
+
# - Records: User-defined types, need explicit type name tracking
|
|
59
|
+
# - Integers/Booleans: Types inferred from LLVM types (i64, i1), no special handling needed
|
|
60
|
+
private def handle_value_expression(mod, scope)
|
|
61
|
+
if value_expression.is_a?(Stone::AST::StringLiteral)
|
|
62
|
+
mod.register_string_constant(identifier, value_expression)
|
|
63
|
+
scope.declare_type(identifier, type: Stone::Type::String)
|
|
64
|
+
end
|
|
65
|
+
register_record_instance(mod, scope)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Register a record type definition and generate its type constant
|
|
69
|
+
private def register_record_type(mod, builder = nil, scope = Stone::Scope.top_level)
|
|
70
|
+
mod.register_record_type(identifier, value_expression)
|
|
71
|
+
# Generate constructor function and type constant
|
|
72
|
+
value_expression.to_llir(builder, mod, scope) if builder
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Register a function (lambda) as an alias so it can be called by the constant name
|
|
77
|
+
private def register_function_alias(mod, function, scope)
|
|
78
|
+
mod.register_function_alias(identifier, function)
|
|
79
|
+
scope.define(identifier, value: function)
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Use true LLVM constants, when we can
|
|
84
|
+
private def initialize_as_constant(global, llvm_value)
|
|
85
|
+
global.global_constant = true
|
|
86
|
+
global.initializer = llvm_value
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Runtime initialization for non-constant expressions
|
|
90
|
+
private def initialize_at_runtime(global, builder, llvm_value)
|
|
91
|
+
global.global_constant = false # Allow runtime update
|
|
92
|
+
global.initializer = llvm_value.type.null
|
|
93
|
+
builder.store(llvm_value, global)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private def literal_constant?
|
|
97
|
+
value_expression.is_a?(Stone::AST::IntegerLiteral) ||
|
|
98
|
+
value_expression.is_a?(Stone::AST::BooleanLiteral) ||
|
|
99
|
+
value_expression.is_a?(Stone::AST::StringLiteral)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private def register_record_instance(mod, scope)
|
|
103
|
+
type_name = record_type_name(mod)
|
|
104
|
+
return unless type_name
|
|
105
|
+
|
|
106
|
+
mod.register_record_instance(identifier, type_name)
|
|
107
|
+
record_type = Stone::TypeRegistry.instance.lookup(type_name)
|
|
108
|
+
scope.declare_type(identifier, type: record_type) if record_type
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private def record_type_name(mod)
|
|
112
|
+
return value_expression.record_type_name if value_expression.is_a?(Stone::AST::RecordInstantiation)
|
|
113
|
+
return value_expression.function_name if record_constructor_call?(mod)
|
|
114
|
+
|
|
115
|
+
nil
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private def record_constructor_call?(mod)
|
|
119
|
+
value_expression.is_a?(Stone::AST::FunctionCall) && mod.record_type?(value_expression.function_name)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
require "stone/ast/expression"
|
|
2
|
+
require "stone/libc"
|
|
3
|
+
require "stone/error/argument_error"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module Stone
|
|
7
|
+
class AST
|
|
8
|
+
class FunctionCall < Stone::AST::Expression
|
|
9
|
+
|
|
10
|
+
attr_reader :function_name, :arguments
|
|
11
|
+
|
|
12
|
+
def initialize(function_name, arguments)
|
|
13
|
+
@function_name = function_name
|
|
14
|
+
@arguments = arguments
|
|
15
|
+
@name = :function_call
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_llir(builder, mod, scope = Stone::Scope.top_level)
|
|
19
|
+
# NULL comparisons with non-NULL values are always unequal (different types)
|
|
20
|
+
return null_comparison_result if mixed_null_comparison?
|
|
21
|
+
|
|
22
|
+
# TODO: Record equality should be delegated to the type system.
|
|
23
|
+
# When the type system is refactored, equality should be polymorphic:
|
|
24
|
+
# - Global `==` checks that both args are the same type
|
|
25
|
+
# - If same type, delegate to type-specific comparison
|
|
26
|
+
# - This special case should be removed
|
|
27
|
+
return generate_record_equality(builder, mod, scope) if record_equality_comparison?(mod)
|
|
28
|
+
# TODO: Record instantiation should not be special-cased here.
|
|
29
|
+
# When the type system is refactored, record constructors should be
|
|
30
|
+
# regular functions, and this check should be removed.
|
|
31
|
+
return instantiate_record(builder, mod, scope) if mod.record_type?(function_name)
|
|
32
|
+
|
|
33
|
+
# For chained comparisons, generate inline comparison logic
|
|
34
|
+
return generate_chained_comparison(builder, mod, scope) if chained_comparison?
|
|
35
|
+
|
|
36
|
+
# For chained boolean operations, generate inline logic
|
|
37
|
+
return generate_chained_boolean(builder, mod, scope) if chained_boolean?
|
|
38
|
+
|
|
39
|
+
# Regular function call
|
|
40
|
+
generate_function_call(builder, mod, scope)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def to_s
|
|
44
|
+
"#{function_name}(#{arguments.join(', ')})"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def type(context = nil)
|
|
48
|
+
return Stone::Type::Bool if comparison_operator? || boolean_operator?
|
|
49
|
+
return record_constructor_type if context&.record_type?(function_name)
|
|
50
|
+
|
|
51
|
+
function_return_type
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private def record_constructor_type
|
|
55
|
+
Stone::Type::Registry.lookup(function_name)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private def function_return_type
|
|
59
|
+
Stone::Type::Registry.lookup(function_name)&.return_type
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private def generate_function_call(builder, mod, scope)
|
|
63
|
+
func = mod.lookup_function(function_name)
|
|
64
|
+
fail Stone::ReferenceError, "undefined function: #{function_name}" unless func
|
|
65
|
+
|
|
66
|
+
validate_argument_count(func.function_type.argument_types.size)
|
|
67
|
+
args = evaluate_arguments(builder, mod, scope)
|
|
68
|
+
builder.call(func, *args, "#{function_name}_result")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private def chained_comparison?
|
|
72
|
+
comparison_operator? && arguments.length > 2
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private def comparison_operator?
|
|
76
|
+
%w[== != ≠ < <= ≤ > >= ≥].include?(function_name)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private def boolean_operator?
|
|
80
|
+
%w[∧ ∨ ⊻ ¬].include?(function_name)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private def chained_boolean?
|
|
84
|
+
boolean_operator? && arguments.length > 2
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private def generate_chained_boolean(builder, mod, scope)
|
|
88
|
+
# Generate inline code for chained boolean operations
|
|
89
|
+
# ∧(a, b, c) generates: (a && b) && c
|
|
90
|
+
func = lookup_and_validate_function(mod)
|
|
91
|
+
args = evaluate_arguments(builder, mod, scope)
|
|
92
|
+
build_chained_boolean_op(builder, func, args)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private def lookup_and_validate_function(mod)
|
|
96
|
+
func = mod.lookup_function(function_name)
|
|
97
|
+
fail Stone::ReferenceError, "undefined function: #{function_name}" unless func
|
|
98
|
+
|
|
99
|
+
func
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private def build_chained_boolean_op(builder, func, args)
|
|
103
|
+
# Compute first pair using the function
|
|
104
|
+
result = builder.call(func, args[0], args[1], "bool_0")
|
|
105
|
+
|
|
106
|
+
# Chain remaining operands left-to-right: (a ⊻ b) ⊻ c
|
|
107
|
+
# This produces: ((a op b) op c) op d ...
|
|
108
|
+
# NOT overlapping pairs like: (a op b) and (b op c)
|
|
109
|
+
remaining_args = args[2..]
|
|
110
|
+
remaining_args.each_with_index do |arg, i|
|
|
111
|
+
result = chain_with_next_operand(builder, result, arg, i + 1)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
result
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private def chain_with_next_operand(builder, current_result, next_arg, index)
|
|
118
|
+
# NOTE: NOT (¬) is unary and never reaches this code path
|
|
119
|
+
# because chained_boolean? requires arguments.length > 2
|
|
120
|
+
case function_name
|
|
121
|
+
when "∧"
|
|
122
|
+
builder.and(current_result, next_arg, "and_#{index}")
|
|
123
|
+
when "∨"
|
|
124
|
+
builder.or(current_result, next_arg, "or_#{index}")
|
|
125
|
+
when "⊻"
|
|
126
|
+
builder.xor(current_result, next_arg, "xor_#{index}")
|
|
127
|
+
else
|
|
128
|
+
fail "Unsupported boolean operator for chaining: #{function_name}"
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private def generate_chained_comparison(builder, mod, scope)
|
|
133
|
+
# Generate inline code for chained comparisons
|
|
134
|
+
# <(a, b, c) generates: (a < b) && (b < c)
|
|
135
|
+
func = lookup_and_validate_function(mod)
|
|
136
|
+
args = evaluate_arguments(builder, mod, scope)
|
|
137
|
+
build_comparison_conjunction(builder, func, args)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
private def build_comparison_conjunction(builder, func, args)
|
|
142
|
+
result = builder.call(func, args[0], args[1], "cmp_0")
|
|
143
|
+
|
|
144
|
+
(1...args.length - 1).each do |i|
|
|
145
|
+
pair_result = builder.call(func, args[i], args[i + 1], "cmp_#{i}")
|
|
146
|
+
result = builder.and(result, pair_result, "and_#{i}")
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
result
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
private def validate_argument_count(expected)
|
|
153
|
+
return if arguments.length == expected
|
|
154
|
+
|
|
155
|
+
fail Stone::ArgumentError, "wrong number of arguments for #{function_name} (given #{arguments.length}, expected #{expected})"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
private def evaluate_arguments(builder, mod, scope)
|
|
159
|
+
arguments.map { |arg| arg.to_llir(builder, mod, scope) }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
private def record_equality_comparison?(mod)
|
|
163
|
+
return false unless equality_operator?
|
|
164
|
+
return false unless arguments.size == 2
|
|
165
|
+
|
|
166
|
+
both_arguments_are_records?(mod)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
private def both_arguments_are_records?(mod)
|
|
170
|
+
Stone::AST::RecordHelpers.record_instance?(arguments[0], mod) &&
|
|
171
|
+
Stone::AST::RecordHelpers.record_instance?(arguments[1], mod)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
private def instantiate_record(builder, mod, scope)
|
|
175
|
+
record_instantiation = Stone::AST::RecordInstantiation.new(function_name, arguments)
|
|
176
|
+
record_instantiation.to_llir(builder, mod, scope)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
private def equality_operator?
|
|
180
|
+
%w[== != ≠].include?(function_name)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
private def mixed_null_comparison?
|
|
184
|
+
return false unless equality_operator?
|
|
185
|
+
return false unless arguments.size == 2
|
|
186
|
+
|
|
187
|
+
null_args = arguments.count { |arg| arg.is_a?(Stone::AST::NullLiteral) }
|
|
188
|
+
return false unless null_args == 1 # Exactly one NULL
|
|
189
|
+
|
|
190
|
+
# If the non-NULL operand returns a pointer, it's a valid pointer comparison
|
|
191
|
+
non_null_arg = arguments.find { |arg| !arg.is_a?(Stone::AST::NullLiteral) }
|
|
192
|
+
!returns_pointer?(non_null_arg)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
private def returns_pointer?(node)
|
|
196
|
+
# PropertyAccess to a recursive field returns a pointer
|
|
197
|
+
return true if node.is_a?(Stone::AST::PropertyAccess)
|
|
198
|
+
|
|
199
|
+
false
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
private def null_comparison_result
|
|
203
|
+
# For ==: different types are not equal, return false
|
|
204
|
+
# For != or ≠: different types are not equal, return true
|
|
205
|
+
function_name == "==" ? LLVM::FALSE : LLVM::TRUE
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
private def generate_record_equality(builder, mod, scope)
|
|
209
|
+
records = evaluate_record_arguments(builder, mod, scope)
|
|
210
|
+
type1, type2 = get_record_types(mod)
|
|
211
|
+
|
|
212
|
+
return different_types_result if type1 != type2
|
|
213
|
+
|
|
214
|
+
result = compare_all_fields(builder, records, mod.record_types[type1], mod)
|
|
215
|
+
apply_not_operator(builder, result)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
private def evaluate_record_arguments(builder, mod, scope)
|
|
219
|
+
[arguments[0].to_llir(builder, mod, scope), arguments[1].to_llir(builder, mod, scope)]
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
private def get_record_types(mod)
|
|
223
|
+
[mod.record_instance_type(arguments[0].identifier), mod.record_instance_type(arguments[1].identifier)]
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
private def different_types_result
|
|
227
|
+
function_name == "==" ? LLVM::Int1.from_i(0) : LLVM::Int1.from_i(1)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
private def apply_not_operator(builder, result)
|
|
231
|
+
function_name == "==" ? result : builder.not(result, "not_equal")
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
private def compare_all_fields(builder, records, record_def, mod)
|
|
235
|
+
fields = record_def.fields
|
|
236
|
+
result = compare_fields_at_index(builder, records, fields, mod, 0)
|
|
237
|
+
|
|
238
|
+
(1...fields.size).each do |i|
|
|
239
|
+
field_equal = compare_fields_at_index(builder, records, fields, mod, i)
|
|
240
|
+
result = builder.and(result, field_equal, "and_#{i}")
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
result
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
private def compare_fields_at_index(builder, records, fields, mod, index)
|
|
247
|
+
field = fields[index]
|
|
248
|
+
val1 = builder.extract_value(records[0], index, "field1_#{field[:name]}")
|
|
249
|
+
val2 = builder.extract_value(records[1], index, "field2_#{field[:name]}")
|
|
250
|
+
|
|
251
|
+
compare_field_values(builder, val1, val2, field[:type_name], mod)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
private def compare_field_values(builder, val1, val2, type_name, mod)
|
|
255
|
+
case type_name
|
|
256
|
+
when "Int", "Bool"
|
|
257
|
+
builder.icmp(:eq, val1, val2, "field_eq")
|
|
258
|
+
when "String"
|
|
259
|
+
# Stone represents strings as i64 pointers to null-terminated C strings
|
|
260
|
+
# We need to compare the string contents, not just the pointers
|
|
261
|
+
compare_strings(builder, val1, val2, mod)
|
|
262
|
+
else
|
|
263
|
+
fail "Unknown type for comparison: #{type_name}"
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
private def compare_strings(builder, str_ptr1, str_ptr2, mod)
|
|
268
|
+
# Optimization: check if pointers are equal first
|
|
269
|
+
# If pointers differ, we still need strcmp for content comparison
|
|
270
|
+
ptrs_equal = builder.icmp(:eq, str_ptr1, str_ptr2, "ptrs_eq")
|
|
271
|
+
|
|
272
|
+
# Convert i64 pointers back to i8* for strcmp
|
|
273
|
+
ptr1 = builder.int2ptr(str_ptr1, LLVM::Type.pointer(LLVM::Int8), "ptr1")
|
|
274
|
+
ptr2 = builder.int2ptr(str_ptr2, LLVM::Type.pointer(LLVM::Int8), "ptr2")
|
|
275
|
+
|
|
276
|
+
# Declare or get strcmp function
|
|
277
|
+
strcmp_func = Stone::LibC.get_or_declare_strcmp(mod)
|
|
278
|
+
strcmp_result = builder.call(strcmp_func, ptr1, ptr2, "strcmp_result")
|
|
279
|
+
|
|
280
|
+
# strcmp returns 0 if strings are equal
|
|
281
|
+
strings_equal = builder.icmp(:eq, strcmp_result, LLVM::Int32.from_i(0), "strings_eq")
|
|
282
|
+
|
|
283
|
+
# Return true if pointers are equal OR string contents are equal
|
|
284
|
+
# Note: This always calls strcmp, but LLVM's optimizer will likely eliminate
|
|
285
|
+
# the strcmp call when ptrs_equal is true at compile time
|
|
286
|
+
builder.or(ptrs_equal, strings_equal, "str_cmp_result")
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "stone/ast"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Stone
|
|
5
|
+
class AST
|
|
6
|
+
class FunctionTypeAnnotation < Stone::AST
|
|
7
|
+
|
|
8
|
+
attr_reader :param_types, :return_type
|
|
9
|
+
|
|
10
|
+
def initialize(param_types, return_type)
|
|
11
|
+
@param_types = param_types
|
|
12
|
+
@return_type = return_type
|
|
13
|
+
@name = :function_type_annotation
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_s
|
|
17
|
+
params = @param_types.map(&:to_s).join(", ")
|
|
18
|
+
return_str = @return_type.is_a?(FunctionTypeAnnotation) ? "(#{@return_type})" : @return_type.to_s
|
|
19
|
+
"(#{params}) -> #{return_str}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Convert to Stone::Type for type checking
|
|
23
|
+
def to_type(registry)
|
|
24
|
+
param_stone_types = @param_types.map { |t| t.to_type(registry) }
|
|
25
|
+
return_stone_type = @return_type.to_type(registry)
|
|
26
|
+
Stone::Type.function(param_types: param_stone_types, return_type: return_stone_type)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require "stone/ast/expression"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Stone
|
|
5
|
+
class AST
|
|
6
|
+
class IntegerLiteral < Stone::AST::Expression
|
|
7
|
+
|
|
8
|
+
BASES = {
|
|
9
|
+
"0b" => 2,
|
|
10
|
+
"0o" => 8,
|
|
11
|
+
"0x" => 16
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
# TODO: This looks like a good place to set a good example: refactor into a Method Object.
|
|
15
|
+
def self.parse(text, location)
|
|
16
|
+
sign = text.start_with?("-") ? -1 : 1
|
|
17
|
+
unsigned_text = text.sub(/^[+-]/, "")
|
|
18
|
+
base = BASES.fetch(unsigned_text[0..1], 10)
|
|
19
|
+
digits = base == 10 ? unsigned_text : unsigned_text[2..]
|
|
20
|
+
value = sign * Integer(digits, base)
|
|
21
|
+
fail Stone::Error::Overflow.new(location:, literal: text) unless in_range?(value)
|
|
22
|
+
new(value)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.in_range?(value)
|
|
26
|
+
value >= Stone::Type::Int.min && value <= Stone::Type::Int.max
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
attr_reader :value
|
|
30
|
+
|
|
31
|
+
def initialize(value)
|
|
32
|
+
@value = value
|
|
33
|
+
@name = :integer_literal
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_llir(_builder, _mod, _scope = Stone::Scope.top_level)
|
|
37
|
+
LLVM::Int64.from_i(@value)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def type(_context = nil)
|
|
41
|
+
Stone::Type::Int
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|