Skip to content

Commit

Permalink
1. Add more functions to the Optional.
Browse files Browse the repository at this point in the history
2. The builtin type 'int|uint|float|boolean|string|array|tuple|hash'
could be extended using something like 'float.to_i()'.
  • Loading branch information
haifenghuang committed Dec 24, 2018
1 parent d569e6d commit c6aaef5
Show file tree
Hide file tree
Showing 5 changed files with 317 additions and 17 deletions.
26 changes: 26 additions & 0 deletions examples/extensionMehod.mp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
fn float.to_integer() {
return ( int( self ) );
}

printf("12.5.to_integer()=%d\n", 12.5.to_integer())

fn array.find(item) {
i = 0;
length = len(self);

while (i < length) {
if (self[i] == item) {
return i;
}
i++;
}

// if not found
return -1;
};

idx = [25,20,38].find(10);
printf("[25,20,38].find(10) = %d\n", idx) // not found, return -1

idx = [25,20,38].find(38);
printf("[25,20,38].find(38) = %d\n", idx) //found, returns 2
52 changes: 42 additions & 10 deletions examples/optional.mp
Original file line number Diff line number Diff line change
Expand Up @@ -57,23 +57,55 @@ class Person {
property Name { get; }

// Returns an Optional.
fn getAddress() {
if (this.address == nil) {
return optional.empty()
}
return optional.of(this.address)
}
// If a function returns an optional, Suggestion is that you use the '?' to denote it,
// though it is not mandatory.
fn getAddress?() {
// if (this.address == nil) {
// return optional.empty()
// }
// return optional.of(this.address)

//same as above
return optional.ofNullable(this.address)
}
}

person1 = new Person("hhf", new Address("block", "city", "state", "country"))
option1 = person1.getAddress();
option1.ifPresent(fn(addr) { // if has address, then, print the address
option1 = person1.getAddress?();
option1.ifPresent(fn(addr) { // if it has address, then print the address
println(addr.str())
})


let EMPTY_ADDRESS = new Address("", "", "", "");
person2 = new Person("hhf", nil)
addr = person2.getAddress().orElse(EMPTY_ADDRESS); //if person2's address is nil, then use EMPTY_ADDRESS
printf(addr.str())
addr = person2.getAddress?().orElse(EMPTY_ADDRESS); //if person2's address is nil, then use EMPTY_ADDRESS
println(addr.str())

option2 = person2.getAddress?();
option2.ifPresentOrElse(
(addr) => { println(addr.str()) },
() => { println("Address not present")}
)

option3 = optional.of(20)
filter = option3.filter(
(a) => { a < 10 }
)
if (filter == optional.empty()) {
println("a is not less than 10")
}

optionalOfMap = option3.filter( (a) => { a < 10 } )
.map( (a) => optional.of(a + 1));
if (optionalOfMap == optional.empty()) {
println("a is not less than 10")
}

currentValue = optional.of(20);
result = currentValue.filter((a) => { a < 30} )
.map( (a) => { a+1 });
orElse = result.orElse(555);
if (orElse == 21) {
println("orElse is equal to 21")
}
47 changes: 45 additions & 2 deletions src/magpie/eval/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"math"
"magpie/ast"
_ "magpie/lexer"
"magpie/token"
"os"
"path"
Expand Down Expand Up @@ -4267,10 +4268,35 @@ func evalMethodCallExpression(call *ast.MethodCallExpression, scope *Scope) Obje

default:
switch o := call.Call.(type) {
case *ast.Identifier: //e.g. method call like '[1,2,3].first'
case *ast.Identifier: //e.g. method call like '[1,2,3].first', 'float.to_integer'
// Check if it's a builtin type extension method, for example: "int.xxx()", "float.xxx()"
name := fmt.Sprintf("%s.%s", strings.ToLower(string(m.Type())), o.String())
if fn, ok := scope.Get(name); ok {
extendScope := NewScope(scope)
extendScope.Set("self", obj) // Set "self" to be the implicit object.
results := Eval(fn.(*Function).Literal.Body, extendScope)
if results.Type() == RETURN_VALUE_OBJ {
return results.(*ReturnValue).Value
}
return results
}

return obj.CallMethod(call.Call.Pos().Sline(), scope, o.String())
case *ast.CallExpression: //e.g. method call like '[1,2,3].first()'
case *ast.CallExpression: //e.g. method call like '[1,2,3].first()', 'float.to_integer()'
args := evalArgs(o.Arguments, scope)
// Check if it's a builtin type extension method, for example: "int.xxx()", "float.xxx()"
name := fmt.Sprintf("%s.%s", strings.ToLower(string(m.Type())), o.Function.String())
if fn, ok := scope.Get(name); ok {
extendScope := extendFunctionScope(fn.(*Function), args)
extendScope.Set("self", obj) // Set "self" to be the implicit object.

results := Eval(fn.(*Function).Literal.Body, extendScope)
if results.Type() == RETURN_VALUE_OBJ {
return results.(*ReturnValue).Value
}
return results
}

return obj.CallMethod(call.Call.Pos().Sline(), scope, o.Function.String(), args...)
}
}
Expand Down Expand Up @@ -5313,3 +5339,20 @@ func reportTypoSuggestionsMeth(line string, scope *Scope, objName string, miss s
panic(NewError(line, NOMETHODERROR, miss, objName))
}
}

func extendFunctionScope(fn *Function, args []Object) *Scope {
fl := fn.Literal
scope := NewScope(fn.Scope)

// Set the defaults
for k, v := range fl.Values {
scope.Set(k, Eval(v, scope))
}
for idx, param := range fl.Parameters {
if idx < len(args) { // default parameters must be in the last part.
scope.Set(param.(*ast.Identifier).Value, args[idx])
}
}

return scope
}
153 changes: 151 additions & 2 deletions src/magpie/eval/optional.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,18 @@ func (o *Optional) CallMethod(line string, scope *Scope, method string, args ...
return o.OrElse(line, args...)
case "orElseGet":
return o.OrElseGet(line, scope, args...)
case "orElseThrow":
return o.OrElseThrow(line, args...)
case "ifPresent":
return o.IfPresent(line, scope, args...)
case "ifPresentOrElse":
return o.IfPresentOrElse(line, scope, args...)
case "filter":
return o.Filter(line, scope, args...)
case "map":
return o.Map(line, scope, args...)
case "flatMap":
return o.FlatMap(line, scope, args...)
}

panic(NewError(line, NOMETHODERROR, method, o.Type()))
Expand Down Expand Up @@ -83,7 +93,7 @@ func (o *Optional) OfNullable(line string, args ...Object) Object {
panic(NewError(line, ARGUMENTERROR, "1", len(args)))
}

if o.Value == NIL {
if args[0] == NIL {
return EMPTY
}
return o.Of(line, args...)
Expand Down Expand Up @@ -120,7 +130,7 @@ func (o *Optional) Or(line string, scope *Scope, args ...Object) Object {
panic(NewError(line, ARGUMENTERROR, "1", len(args)))
}

if o.IsPresent(line, args...) == TRUE {
if o.IsPresent(line) == TRUE {
return o
}

Expand Down Expand Up @@ -170,6 +180,25 @@ func (o *Optional) OrElseGet(line string, scope *Scope, args ...Object) Object {
return ret
}

// If a value is present, returns the value, otherwise throws an exception
// produced by the exception supplying function.
func (o *Optional) OrElseThrow(line string, args ...Object) Object {
if len(args) != 1 {
panic(NewError(line, ARGUMENTERROR, "1", len(args)))
}

if o.Value != NIL {
return o.Value
}

exceptStr, ok := args[0].(*String)
if !ok {
panic(NewError(line, PARAMTYPEERROR, "first", "orElseThrow", "*String", args[0].Type()))
}

//just like 'evalThrowStatement's return.
return &Error{Kind: THROWNOTHANDLED, Message: exceptStr.String}
}

// If a value is present, performs the given action with the value,
// otherwise does nothing.
Expand All @@ -193,3 +222,123 @@ func (o *Optional) IfPresent(line string, scope *Scope, args ...Object) Object {

return ret
}

// If a value is present, performs the given action with the value,
// otherwise performs the given empty-based action.
func (o *Optional) IfPresentOrElse(line string, scope *Scope, args ...Object) Object {
if len(args) != 2 {
panic(NewError(line, ARGUMENTERROR, "2", len(args)))
}

if o.Value != NIL {
action, ok := args[0].(*Function)
if !ok {
panic(NewError(line, PARAMTYPEERROR, "first", "ifPresentOrElse", "*Function", args[0].Type()))
}

s := NewScope(scope)
s.Set(action.Literal.Parameters[0].(*ast.Identifier).Value, o.Value)
ret := Eval(action.Literal.Body, s) // run the function
return ret
} else {
emptyAction, ok := args[1].(*Function)
if !ok {
panic(NewError(line, PARAMTYPEERROR, "second", "ifPresentOrElse", "*Function", args[1].Type()))
}

s := NewScope(scope)
ret := Eval(emptyAction.Literal.Body, s) // run the function
return ret
}
}

// If a value is present, and the value matches the given predicate,
// returns an Optional describing the value, otherwise returns an
// empty Optional.
func (o *Optional) Filter(line string, scope *Scope, args ...Object) Object {
if len(args) != 1 {
panic(NewError(line, ARGUMENTERROR, "1", len(args)))
}

if o.IsPresent(line) == FALSE {
return o
}

predicate, ok := args[0].(*Function)
if !ok {
panic(NewError(line, PARAMTYPEERROR, "first", "filter", "*Function", args[0].Type()))
}

s := NewScope(scope)
s.Set(predicate.Literal.Parameters[0].(*ast.Identifier).Value, o.Value)
cond := Eval(predicate.Literal.Body, s) // run the function
if IsTrue(cond) {
return o
}
return EMPTY
}

// If a value is present, returns an Optional describing (as if by
// ofNullable) the result of applying the given mapping function to
// the value, otherwise returns an empty Optional.
//
// If the mapping function returns a nil result then this method
// returns an empty Optional.
func (o *Optional) Map(line string, scope *Scope, args ...Object) Object {
if len(args) != 1 {
panic(NewError(line, ARGUMENTERROR, "1", len(args)))
}

if o.IsPresent(line) == FALSE {
return EMPTY
}

mapper, ok := args[0].(*Function)
if !ok {
panic(NewError(line, PARAMTYPEERROR, "first", "map", "*Function", args[0].Type()))
}

s := NewScope(scope)
s.Set(mapper.Literal.Parameters[0].(*ast.Identifier).Value, o.Value)
r := Eval(mapper.Literal.Body, s) // run the function
if obj, ok := r.(*ReturnValue); ok {
r = obj.Value
}

return o.OfNullable(line, r)
}

// If a value is present, returns the result of applying the given
// Optional-bearing mapping function to the value, otherwise returns
// an empty Optional.
//
// This method is similar to the 'map' function, but the mapping
// function is one whose result is already an Optional, and if
// invoked, flatMap does not wrap it within an additional Optional.

func (o *Optional) FlatMap(line string, scope *Scope, args ...Object) Object {
if len(args) != 1 {
panic(NewError(line, ARGUMENTERROR, "1", len(args)))
}

if o.IsPresent(line) == FALSE {
return EMPTY
}

mapper, ok := args[0].(*Function)
if !ok {
panic(NewError(line, PARAMTYPEERROR, "first", "flatMap", "*Function", args[0].Type()))
}

s := NewScope(scope)
s.Set(mapper.Literal.Parameters[0].(*ast.Identifier).Value, o.Value)
r := Eval(mapper.Literal.Body, s) // run the function
if obj, ok := r.(*ReturnValue); ok {
r = obj.Value
}

if r.Type() != OPTIONAL_OBJ {
panic(NewError(line, GENERICERROR, "flatmap() function's return value not an optional."))
}
return r
}
Loading

0 comments on commit c6aaef5

Please sign in to comment.