Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Core Development Principles

- **NEVER DUPLICATE CODE** - Edit in place, never create new versions
- **NEVER DUPLICATE CODE** - Edit in place, never create new versions. Actively remove duplicate code and always aim for conciseness. Always do a search before adding new code.
- **NO PLACEHOLDERS** - Fix existing placeholders or fail with error

⛔️ This is completely illegal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,20 @@ print(sum1)
let sum2 = range(10, 15) |> fold(0, add)
print(sum2)

// Map operations - transform each element
print("5. Map operations:")
range(1, 5) |> map(double) |> forEach(print)

// Filter operations - select elements
print("6. Filter operations:")
range(1, 10) |> filter(isEven) |> forEach(print)

// Chained map and filter
print("7. Chained map and filter:")
range(1, 6) |> map(double) |> filter(isEven) |> forEach(print)

// List operations with Hindley-Milner inference
print("5. List operations:")
print("8. List operations:")
let data = [10, 20, 30, 40, 50]
print("List created with HM inference")

Expand All @@ -43,12 +55,12 @@ match data[2] {
}

// Map operations require usage context for HM inference
print("6. Map operations would need constraints:")
print("9. Map operations would need constraints:")
// let cache = Map() - HM can't infer K,V without usage!
print("HM needs type constraints for Map<K,V> inference")

// List operations with functional style
print("7. List with functional operations:")
print("10. List with functional operations:")
let baseNumbers = [1, 2, 3, 4]
print("Base numbers created")

Expand All @@ -61,7 +73,7 @@ match baseNumbers[3] {
}

// More pipe operations on single values
print("8. Chained single value operations:")
print("11. Chained single value operations:")
let result = 2 |> double |> square
print(result)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,35 @@
4. Fold operations:
10
60
5. List operations:
5. Map operations:
2
4
6
8
6. Filter operations:
1
2
3
4
5
6
7
8
9
7. Chained map and filter:
2
4
6
8
10
8. List operations:
List created with HM inference
Third element: 30
6. Map operations would need constraints:
9. Map operations would need constraints:
HM needs type constraints for Map<K,V> inference
7. List with functional operations:
10. List with functional operations:
Base numbers created
Fourth number squared: 16
8. Chained single value operations:
11. Chained single value operations:
16
=== Examples Complete ===
=== Examples Complete ===
12 changes: 12 additions & 0 deletions compiler/examples/tested/basics/functional/functional_showcase.osp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ print("Example 7: Fold operations")
let sumResult = range(1, 6) |> fold(0, sum)
print(sumResult)

// Example 7a: Map transformations
print("Example 7a: Map transformations")
range(1, 5) |> map(triple) |> forEach(print)

// Example 7b: Filter selections
print("Example 7b: Filter selections")
range(1, 10) |> filter(isPositive) |> forEach(print)

// Example 7c: Combined map and filter
print("Example 7c: Combined map and filter")
range(1, 6) |> map(triple) |> filter(isPositive) |> forEach(print)

// Example 8: Map collections with HM inference
print("Example 8: Map collections")
let prices = { "apple": 2, "banana": 3, "cherry": 5 }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
=== Functional Programming Showcase ===
Example 1: Basic range iteration
1
2
3
4
5
Example 2: Single value pipe operations
18
Example 3: Business logic pipeline
88
Example 4: Range forEach
42
43
44
Example 5: Small range
10
11
12
Example 6: Range 0 to 4
0
1
2
3
4
Example 7: Fold operations
15
Example 7a: Map transformations
3
6
9
12
Example 7b: Filter selections
1
2
3
4
5
6
7
8
9
Example 7c: Combined map and filter
3
6
9
12
15
Example 8: Map collections
Created price map and inventory map via HM inference
Apple price: 2
6
Example 8: Chained single value operations
21
Example 9: Conditional operations
1
0
=== Showcase Complete ===
3 changes: 3 additions & 0 deletions compiler/internal/codegen/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ type LLVMGenerator struct {
// HINDLEY-MILNER FIX: Single source of truth for record field mappings
// Maps record type name to field name -> LLVM index mapping
recordFieldMappings map[string]map[string]int
// Stream Fusion: Track pending transformations for map/filter
pendingMapFunc *ast.Identifier // Pending map transformation function
pendingFilterFunc *ast.Identifier // Pending filter predicate function
}

// SecurityConfig defines security policies for the code generator.
Expand Down
115 changes: 93 additions & 22 deletions compiler/internal/codegen/iterator_generation.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,23 +112,26 @@ func (g *LLVMGenerator) extractRangeBounds(rangeValue value.Value) (value.Value,

// ForEachLoopBlocks holds the basic blocks for a forEach loop.
type ForEachLoopBlocks struct {
LoopCond *ir.Block
LoopBody *ir.Block
LoopEnd *ir.Block
LoopCond *ir.Block
LoopBody *ir.Block
LoopIncrement *ir.Block
LoopEnd *ir.Block
}

// createForEachLoopBlocks creates the basic blocks needed for a forEach loop.
func (g *LLVMGenerator) createForEachLoopBlocks(callExpr *ast.CallExpression) *ForEachLoopBlocks {
blockSuffix := fmt.Sprintf("_%p", callExpr)

return &ForEachLoopBlocks{
LoopCond: g.function.NewBlock("loop_cond" + blockSuffix),
LoopBody: g.function.NewBlock("loop_body" + blockSuffix),
LoopEnd: g.function.NewBlock("loop_end" + blockSuffix),
LoopCond: g.function.NewBlock("loop_cond" + blockSuffix),
LoopBody: g.function.NewBlock("loop_body" + blockSuffix),
LoopIncrement: g.function.NewBlock("loop_increment" + blockSuffix),
LoopEnd: g.function.NewBlock("loop_end" + blockSuffix),
}
}

// generateForEachLoop generates the actual loop logic for forEach.
// generateForEachLoop generates the actual loop logic for forEach with stream fusion.
// Applies pending map/filter transformations inline for zero-cost abstractions.
func (g *LLVMGenerator) generateForEachLoop(
start, end value.Value,
funcIdent *ast.Identifier,
Expand All @@ -147,11 +150,55 @@ func (g *LLVMGenerator) generateForEachLoop(
g.builder = blocks.LoopBody
counterValue := g.builder.NewLoad(types.I64, counterPtr)

_, err := g.callFunctionWithValue(funcIdent, counterValue)
if err != nil {
return err
// STREAM FUSION: Apply pending transformations inline
var processedValue value.Value = counterValue

// Apply map transformation if present
if g.pendingMapFunc != nil {
mapped, err := g.callFunctionWithValue(g.pendingMapFunc, processedValue)
if err != nil {
return err
}
processedValue = mapped
}

// Apply filter transformation if present
if g.pendingFilterFunc != nil {
predicateResult, err := g.callFunctionWithValue(g.pendingFilterFunc, counterValue)
if err != nil {
return err
}
// Check if predicate returned non-zero (true)
zero := constant.NewInt(types.I64, 0)
// Create conditional blocks for filter with unique names
blockSuffix := fmt.Sprintf("_%p", blocks)
filterPassBlock := g.function.NewBlock("filter_pass" + blockSuffix)
filterSkipBlock := g.function.NewBlock("filter_skip" + blockSuffix)
isNonZero := g.builder.NewICmp(enum.IPredNE, predicateResult, zero)
g.builder.NewCondBr(isNonZero, filterPassBlock, filterSkipBlock)

// Filter pass: call the function
g.builder = filterPassBlock
_, err = g.callFunctionWithValue(funcIdent, processedValue)
if err != nil {
return err
}
g.builder.NewBr(blocks.LoopIncrement)

// Filter skip: just continue
g.builder = filterSkipBlock
g.builder.NewBr(blocks.LoopIncrement)
} else {
// No filter, always call the function
_, err := g.callFunctionWithValue(funcIdent, processedValue)
if err != nil {
return err
}
g.builder.NewBr(blocks.LoopIncrement)
}

// Increment counter in the common increment block
g.builder = blocks.LoopIncrement
one := constant.NewInt(types.I64, 1)
incrementedValue := g.builder.NewAdd(counterValue, one)
g.builder.NewStore(incrementedValue, counterPtr)
Expand All @@ -160,45 +207,69 @@ func (g *LLVMGenerator) generateForEachLoop(

g.builder = blocks.LoopEnd

// STREAM FUSION: Clear pending transformations after consuming
g.pendingMapFunc = nil
g.pendingFilterFunc = nil

return nil
}

// generateMapCall handles map function calls.
// generateMapCall handles map function calls using stream fusion.
// Stores the transformation function and returns the range unchanged.
// The transformation will be fused into forEach/fold when they consume the iterator.
func (g *LLVMGenerator) generateMapCall(callExpr *ast.CallExpression) (value.Value, error) {
if len(callExpr.Arguments) != TwoArgs {
return nil, WrapBuiltInFunctionWrongArgs(MapFunc, len(callExpr.Arguments))
err := validateBuiltInArgs(MapFunc, callExpr)
if err != nil {
return nil, err
}

// Get the range struct from first argument (iterator)
rangeValue, err := g.generateExpression(callExpr.Arguments[0])
if err != nil {
return nil, err
}

if _, ok := callExpr.Arguments[1].(*ast.Identifier); !ok {
// Get the transformation function
funcArg := callExpr.Arguments[1]

funcIdent, ok := funcArg.(*ast.Identifier)
if !ok {
return nil, ErrMapNotFunction
}

// TODO: Implement proper lazy map
// STREAM FUSION: Store the map function for later fusion with forEach/fold
g.pendingMapFunc = funcIdent

return rangeValue, nil
}

// generateFilterCall handles filter function calls.
// generateFilterCall handles filter function calls using stream fusion.
// Stores the predicate function and returns the range unchanged.
// The filter will be fused into forEach/fold when they consume the iterator.
func (g *LLVMGenerator) generateFilterCall(callExpr *ast.CallExpression) (value.Value, error) {
if len(callExpr.Arguments) != TwoArgs {
return nil, WrapBuiltInFunctionWrongArgs(FilterFunc, len(callExpr.Arguments))
err := validateBuiltInArgs(FilterFunc, callExpr)
if err != nil {
return nil, err
}

iterator, err := g.generateExpression(callExpr.Arguments[0])
// Get the range struct from first argument (iterator)
rangeValue, err := g.generateExpression(callExpr.Arguments[0])
if err != nil {
return nil, err
}

// Get the predicate function
funcArg := callExpr.Arguments[1]
if funcIdent, ok := funcArg.(*ast.Identifier); ok {
return g.callFunctionWithValue(funcIdent, iterator)

funcIdent, ok := funcArg.(*ast.Identifier)
if !ok {
return nil, ErrFilterNotFunction
}

return nil, ErrFilterNotFunction
// STREAM FUSION: Store the filter predicate for later fusion with forEach/fold
g.pendingFilterFunc = funcIdent

return rangeValue, nil
}

// generateFoldCall handles fold function calls.
Expand Down
Loading
Loading