From bff8aa4ca6bb901a10c293711758c620ea3d24ed Mon Sep 17 00:00:00 2001 From: Lou Knauer <lou.knauer@gmx.de> Date: Mon, 11 Mar 2019 11:48:01 +0100 Subject: [PATCH] changes from friday, first try at new unification This should not be as important any more as philipp fixed this issue in the master branch allready using another aproach, but at some point in the future i would like to fix all of this. --- .gitmodules | 3 + dhall-lang | 1 + lib/ast.rb | 4 +- lib/stdlib.rb | 193 +++++++++++++++++++++++++++++++++++++++++++++++++- lib/types.rb | 166 ++++++++++++++++++++++++------------------- lib/utils.rb | 2 +- tests/test.rb | 11 ++- 7 files changed, 304 insertions(+), 76 deletions(-) create mode 100644 .gitmodules create mode 160000 dhall-lang diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6f9fbd8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "dhall-lang"] + path = dhall-lang + url = https://github.com/dhall-lang/dhall-lang.git diff --git a/dhall-lang b/dhall-lang new file mode 160000 index 0000000..b583925 --- /dev/null +++ b/dhall-lang @@ -0,0 +1 @@ +Subproject commit b583925f00ef787e201bb07bed0e6fef0db9463d diff --git a/lib/ast.rb b/lib/ast.rb index e4a5e35..8c57919 100644 --- a/lib/ast.rb +++ b/lib/ast.rb @@ -636,7 +636,7 @@ module Dhallish new_path = File.dirname src else opath = basedir.path - basedir.path = File.join basedir.path, (@prefix + src) + basedir.path = File.join basedir.path, (@prefix + src) src = basedir.to_s basedir.path = File.dirname basedir.path new_path = URI basedir.to_s @@ -652,6 +652,8 @@ module Dhallish new_path.path = File.dirname new_path.path end + # STDERR.puts "import src: #{src}" + file = open(src) text = file.read diff --git a/lib/stdlib.rb b/lib/stdlib.rb index 48a0505..cd5f24b 100644 --- a/lib/stdlib.rb +++ b/lib/stdlib.rb @@ -21,6 +21,12 @@ module Dhallish end module_function :make_fn_type + def define(ctx, name, value, type) + ctx["<#TYPES#>"][name.to_s] = type + ctx["<#VALS#>"][name.to_s] = value + end + module_function :define + def fill_context(globalctx, types) # Types: @@ -78,7 +84,7 @@ module Dhallish globalctx["Double/toInteger"] = douToInt types["Double/toInteger"] = Types::Function.new(Types::Double, Types::Integer) - +=begin # Lists: globalctx["List"] = BuiltinFunction.new { |a| Types::List.new(a) } types["List"] = make_fn_type([:list_a], Types::Type.new(Types::List.new(Types::Unresolved.new(:list_a)))) @@ -262,6 +268,191 @@ module Dhallish f.call(Types::Natural).call(succ).call(zero) } types["Natural/build"] = make_fn_type(make_fn_type([:nat_build_a], make_fn_type(:nat_build_a, :nat_build_a), :nat_build_a, :nat_build_a), Types::Natural) +=end + + # Lists: + globalctx["List"] = BuiltinFunction.new { |a| Types::List.new(a) } + types["List"] = make_fn_type([:a], Types::Type.new(Types::List.new(Types::Unresolved.new(:a)))) + + list_length_type = make_fn_type([:a], Types::List.new(Types::Unresolved.new(:a)), Types::Natural) + globalctx["List/length"] = BuiltinFunction.new{ |a| + BuiltinFunction.new { |list| + list.length + } + } + types["List/length"] = list_length_type + + list_fold_type = make_fn_type( + [:a], + Types::List.new(Types::Unresolved.new(:a)), + [:b], + make_fn_type(:a, :b, :b), + :b, :b) + globalctx["List/fold"] = BuiltinFunction.new { |a| + BuiltinFunction.new { |list| + BuiltinFunction.new { |b| + BuiltinFunction.new { |f| + BuiltinFunction.new { |x| + acc = x + list.reverse_each { |elm| acc = f.call(elm).call(acc) } + acc + } + } + } + } + } + types["List/fold"] = list_fold_type + + list_head_type = make_fn_type( + [:a], + Types::List.new(Types::Unresolved.new(:a)), + Types::Optional.new(Types::Unresolved.new(:a))) + globalctx["List/head"] = BuiltinFunction.new { |a| + BuiltinFunction.new { |list| + list.first + } + } + types["List/head"] = list_head_type + + list_last_type = make_fn_type( + [:a], + Types::List.new(Types::Unresolved.new(:a)), + Types::Optional.new(Types::Unresolved.new(:a))) + globalctx["List/last"] = BuiltinFunction.new { |a| + BuiltinFunction.new { |list| + list.last + } + } + types["List/last"] = list_last_type + + list_tail_type = make_fn_type( + [:a], + Types::List.new(Types::Unresolved.new(:a)), + Types::List.new(Types::Unresolved.new(:a))) + globalctx["List/tail"] = BuiltinFunction.new { |a| + BuiltinFunction.new { |list| + if list.empty? + [] + else + list[1..list.length] + end + } + } + types["List/tail"] = list_tail_type + + types["List/reverse"] = make_fn_type( + [:a], + Types::List.new(Types::Unresolved.new(:a)), + Types::List.new(Types::Unresolved.new(:a))) + globalctx["List/reverse"] = BuiltinFunction.new { |a| + BuiltinFunction.new { |list| list.reverse } + } + + types["List/build"] = make_fn_type( + [:a], + make_fn_type( + [:b], + make_fn_type(:a, :b, :b), :b, :b), + Types::List.new(Types::Unresolved.new(:a))) + globalctx["List/build"] = BuiltinFunction.new { |a| + BuiltinFunction.new { |f| + cons = BuiltinFunction.new { |x| + BuiltinFunction.new { |list| [x] + list } + } + f.call(Types::List.new(a)).call(cons).call([]) + } + } + + types["List/indexed"] = make_fn_type( + [:a], + Types::List.new(Types::Unresolved.new(:a)), + Types::List.new(Types::Record.new({ + "index" => Types::Natural, + "value" => Types::Unresolved.new(:a)}))) + globalctx["List/indexed"] = BuiltinFunction.new { |a| + BuiltinFunction.new { |list| + list.map.with_index { |val, idx| + { "index" => idx, "value" => val } + } + } + } + + # Optionals: + globalctx["Optional"] = BuiltinFunction.new { |a| + Types::Optional.new(a) + } + types["Optional"] = make_fn_type([:opt_a], Types::Type.new(Types::Optional.new(Types::Unresolved.new(:opt_a)))) + + optional_fold_type = make_fn_type( + [:a], + Types::Optional.new(Types::Unresolved.new(:a)), + [:b], + make_fn_type(:a, :b), + :b, :b) + globalctx["Optional/fold"] = BuiltinFunction.new { |a| + BuiltinFunction.new { |opt| + BuiltinFunction.new { |b| + BuiltinFunction.new { |f| + BuiltinFunction.new { |x| + if opt.nil? + x + else + f.call(opt) + end + } + } + } + } + } + types["Optional/fold"] = optional_fold_type + + types["Optional/build"] = make_fn_type( + [:a], + make_fn_type([:b], + make_fn_type(:a, :b), :b, :b), + Types::Optional.new(Types::Unresolved.new(:a))) + globalctx["Optional/build"] = BuiltinFunction.new { |a| + BuiltinFunction.new { |f| + just = BuiltinFunction.new { |x| x } + f.call(Types::Optional.new(a)).call(just).call(nil) + } + } + + + # Naturals: + natural_fold_type = make_fn_type( + Types::Natural, + [:a], + make_fn_type(:a, :a), + :a, :a) + globalctx["Natural/fold"] = BuiltinFunction.new { |n| + BuiltinFunction.new { |a| + BuiltinFunction.new { |succ| + BuiltinFunction.new { |zero| + res = zero + n.times { res = succ.call(res) } + res + } + } + } + } + types["Natural/fold"] = natural_fold_type + + types["Natural/even"] = make_fn_type(Types::Natural, Types::Bool) + globalctx["Natural/even"] = BuiltinFunction.new { |n| n % 2 == 0 } + + types["Natural/odd"] = make_fn_type(Types::Natural, Types::Bool) + globalctx["Natural/odd"] = BuiltinFunction.new { |n| n % 2 == 1 } + + types["Natural/isZero"] = make_fn_type(Types::Natural, Types::Bool) + globalctx["Natural/isZero"] = BuiltinFunction.new { |n| n == 0 } + + globalctx["Natural/build"] = BuiltinFunction.new { |f| + zero = 0 + succ = BuiltinFunction.new { |n| n + 1 } + f.call(Types::Natural).call(succ).call(zero) + } + types["Natural/build"] = make_fn_type(make_fn_type([:a], make_fn_type(:a, :a), :a, :a), Types::Natural) end module_function :fill_context diff --git a/lib/types.rb b/lib/types.rb index 1e6bbee..b36499e 100644 --- a/lib/types.rb +++ b/lib/types.rb @@ -23,7 +23,7 @@ module Dhallish end end def to_s() - if @metadata.nil? or @metadata.is_a? Unresolved + if @metadata.nil? "Type" else "Type(#{@metadata.to_s})" @@ -149,10 +149,16 @@ module Dhallish end end + # basically a substitution def resolve(orgtype, name, newtype) + puts "resolve[#{name} => #{newtype}]:\t#{orgtype}" case orgtype when Type - Type.new(resolve(orgtype.metadata, name, newtype)) + if !orgtype.metadata.nil? + Type.new(resolve(orgtype.metadata, name, newtype)) + else + Type.new(nil) + end when Unresolved if name == orgtype.name newtype @@ -160,17 +166,25 @@ module Dhallish orgtype end when Function - if orgtype.restype.nil? - Function.new(resolve(orgtype.argtype, name, newtype), nil, orgtype.unres) - else - restype = orgtype.restype - argtype = orgtype.argtype - if orgtype.unres.nil? or orgtype.unres != name - restype = resolve(orgtype.restype, name, newtype) - argtype = resolve(orgtype.argtype, name, newtype) - end - Function.new(argtype, restype, orgtype.unres) + restype = orgtype.restype + argtype = orgtype.argtype + unres = orgtype.unres + if orgtype.unres.nil? or orgtype.unres != name + restype = resolve(orgtype.restype, name, newtype) + argtype = resolve(orgtype.argtype, name, newtype) + elsif !orgtype.unres.nil? + assert("debug: #{orgtype}") { + argtype == Type.new(Unresolved.new(orgtype.unres)) + } + # TODO: resolve bug? + # Bug vielleicht in call node? + # `dhalltest.rb` + #puts "1--> #{Function.new(argtype, restype, unres)}" + #argtype = resolve(orgtype.argtype, name, newtype) + #unres = name + #puts "2--> #{Function.new(argtype, restype, unres)}" end + Function.new(argtype, restype, unres) when Optional Optional.new(resolve(orgtype.type, name, newtype)) when List @@ -180,6 +194,7 @@ module Dhallish when Union Union.new(orgtype.types.map{ |key, val| [key, resolve(val, name, newtype)] }.to_h) else + assert("debug: #{orgtype}") { is_a_type?(orgtype) } orgtype end end @@ -188,82 +203,87 @@ module Dhallish def unification(a, b, mapping={}) case a when Type - if b.is_a? Type + if !b.is_a? Type; return nil end + # I think those Kinds dhall has might be important here. + # `Type` is actually of type `Kind`? + # TODO: There is stuff to be done here i think... + if !a.metadata.nil? and !b.metadata.nil? unification(a.metadata, b.metadata, mapping) + elsif b.metadata.nil? + mapping else nil end + when Natural, Integer, Double, Text, Bool + if a == b; mapping else nil end + when List + if !b.is_a? List; return nil end + unification(a.type, b.type, mapping) + when Optional + if !b.is_a? Optional; return nil end + unification(a.type, b.type, mapping) + when Record + if !b.is_a? Record or a.types.length != b.types.length; return nil end + for key in a.types.keys + afield = a.types[key] + bfield = b.types[key] + if bfield.nil? or unification(afield, bfield, mapping).nil? + return nil + end + end + mapping + when Union + if !b.is_a? Union or a.types.length != b.types.length; return nil end + for key in a.types.keys + afield = a.types[key] + bfield = b.types[key] + if bfield.nil? or unification(afield, bfield, mapping).nil? + return nil + end + end + mapping when Unresolved - if b.is_a? Unresolved - if mapping[a.name] == nil - mapping[a.name] = b.name + if !b.is_a? Unresolved; return nil end + if mapping[a.name] != nil + if mapping[a.name] == b.name mapping - elsif mapping[a.name] != b.name - nil else - mapping + nil end else - nil + mapping[a.name] = b.name + mapping end when Function - if b.is_a? Function - if unification(a.argtype, b.argtype, mapping).nil? - nil - else - arestype = a.restype - brestype = b.restype - if !a.unres.nil?; arestype = resolve(a.restype, a.unres, Unresolved.new(get_new_sym())) end - if !b.unres.nil?; brestype = resolve(b.restype, b.unres, Unresolved.new(get_new_sym())) end - unification(arestype, brestype, mapping) + if !b.is_a? Function; return nil end + if !a.unres.nil? + assert("debug: #{a}") { a.argtype.is_a? Type and a.argtype.metadata == Unresolved.new(a.unres) } + if b.unres.nil?; return nil end + assert("debug: #{b}") { b.argtype.is_a? Type and b.argtype.metadata == Unresolved.new(b.unres) } + + # TODO: Update mapping? + # TODO: keys and values swapped? + + arestype = a.restype + brestype = b.restype + if mapping.keys.include? a.unres + sym = get_new_sym() + arestype = resolve(arestype, a.unres, Unresolved.new(sym)) end - else - nil - end - when Optional - if b.is_a? Optional - unification(a.type, b.type, mapping) - else - nil - end - when List - if b.is_a? List - unification(a.type, b.type, mapping) - else - nil - end - when Record - if b.is_a? Record and a.types.keys.length == b.types.keys.length - for key in a.types.keys - aelm = a.types[key] - belm = b.types[key] - if belm.nil?; return nil end - ret = unification(aelm, belm, mapping) - if ret.nil?; return nil end + if mapping.values.include? b.unres + sym = get_new_sym() + brestype = resolve(brestype, b.unres, Unresolved.new(sym)) end - mapping + unification(arestype, brestype, mapping) else - nil - end - when Union - if b.is_a? Union and a.types.keys.length == b.types.keys.length - for key in a.types.keys - aelm = a.types[key] - belm = b.types[key] - if belm.nil?; return nil end - ret = unification(aelm, belm, mapping) - if ret.nil?; return nil end + if !unification(a.argtype, b.argtype, mapping).nil? + unification(a.restype, b.restype, mapping) + else + nil end - mapping - else - nil end else - if a == b - mapping - else - nil - end + raise "New Type? a: #{a.inspect}, b: #{b.inspect}" end end module_function :unification @@ -307,9 +327,11 @@ module Dhallish "{#{ dhallval.map{ |key, val| "\"#{escape_str(key)}\": #{ to_json(val) }" }.join(", ") }}" when nil "null" + when Union + to_json(dhallval.init_val) else if Types::is_a_type? dhallval - "\"#{dhallval.to_s}\"" + "\"Dhallish::Type(#{dhallval.to_s})\"" else "\"<#{escape_str(dhallval.class.to_s)}##{dhallval.hash.abs.to_s(16)}>\"" end diff --git a/lib/utils.rb b/lib/utils.rb index b03bd3a..300fb1e 100644 --- a/lib/utils.rb +++ b/lib/utils.rb @@ -16,7 +16,7 @@ end $dhallish_internal_global_counter = 0 def get_new_sym() $dhallish_internal_global_counter += 1 - ("__newsym_#{$dhallish_internal_global_counter}").to_sym + ("__#{$dhallish_internal_global_counter}").to_sym end def escape_str(str) diff --git a/tests/test.rb b/tests/test.rb index 6e12055..c6cc00e 100755 --- a/tests/test.rb +++ b/tests/test.rb @@ -36,7 +36,7 @@ positive_tests = { "fallback for file import": ["let x = ./foobarfoobar.dhall as Text ? 1 in x", 1], "more type annotation": [ "let f = \\(a: Type) -> \\(l: List a) -> \\(x: a) -> x in (f: forall(b: Type) -> forall(l: List b) -> b -> b) Natural [1, 2, 3] 5", 5 ], "select from union": [ "let union = <a : Natural | b : Text>.a 17 in union.a : Optional Natural", 17 ], - "union merge": [ "merge { Left = \\(x: Natural) -> Natural/show x, Right = \\(y: Integer) -> Integer/show y } < Left = 42 | Right : Integer >", "42" ] + "union merge": [ "merge { Left = \\(x: Natural) -> Natural/show x, Right = \\(y: Integer) -> Integer/show y } < Left = 42 | Right : Integer >", "42" ] } negative_tests = { @@ -115,3 +115,12 @@ dhallfiles.each { |file| print_msg(file, true) end } + +puts "\n=== Testing Typecheck on dhall-lang/Prelude/package.dhall" +begin + Dhallish::evaluate("./" + File.join(File.dirname(__FILE__), "../dhall-lang/Prelude/package.dhall")) +rescue + print_msg("Prelude/package.dhall", false) +else + print_msg("Prelude/package.dhall", true) +end -- GitLab