require File.join(File.dirname(__FILE__), 'ast.rb') require File.join(File.dirname(__FILE__), 'types.rb') require File.join(File.dirname(__FILE__), 'utils.rb')
grammar DhallishGrammar
rule root space? exp:expression space? { def to_node() exp.to_node() end } end rule expression exp:if_then_else_expression { def to_node() exp.to_node() end } / exp:let_expression { def to_node() exp.to_node() end } / exp:import_alternative_expression { def to_node() exp.to_node() end } end rule eol "\r"? "\n" end ## Comments rule single_line_comment "--" (!"\n" .)* "\n" end rule multi_line_comment "{-" multi_line_comment_continue end rule multi_line_comment_chunk multi_line_comment / (!"{-" .) end rule multi_line_comment_continue "-}" / multi_line_comment_chunk multi_line_comment_continue end rule space (" " / "\t" / eol / single_line_comment / multi_line_comment)+ end ## Number Literals rule integer_literal ("+" / "-") [0-9]+ { def to_node() Dhallish::Ast::Literal_Node.new(text_value.to_i, Dhallish::Types::Integer) end } end rule exponent "e" ("+" / "-")? [0-9]+ end rule double_literal ("+" / "-")? [0-9]+ (("." [0-9]+ exponent?) / exponent) { def to_node() Dhallish::Ast::Literal_Node.new(text_value.to_f, Dhallish::Types::Double) end } end rule natural_literal [0-9]+ { def to_node() Dhallish::Ast::Literal_Node.new(text_value.to_i, Dhallish::Types::Natural) end } end ## Bools rule bool_literal "True" { def to_node() Dhallish::Ast::Literal_Node.new(true, Dhallish::Types::Bool) end } / "False" { def to_node() Dhallish::Ast::Literal_Node.new(false, Dhallish::Types::Bool) end } end ## Lists rule empty_list_literal "[" space? "]" space? ":" space? "List" space type:expression { def to_node() Dhallish::Ast::ListNode.new [], type.to_node() end } end rule non_empty_list_literal "[" space? fst:expression tail:(space? "," space? exp:expression)* space? "]" annot:(space? ":" space? "List" space type:expression)? { def to_node() list = [] list.append fst.to_node() tail.elements.each { |node| list.append node.exp.to_node() } if annot.respond_to? :type Dhallish::Ast::ListNode.new list, annot.type.to_node() else Dhallish::Ast::ListNode.new list, nil end end } end rule list_literal exp:empty_list_literal { def to_node() exp.to_node() end } / exp:non_empty_list_literal { def to_node() exp.to_node() end } end ## Records rule record_type_literal "{" space? "}" { def to_node() Dhallish::Ast::RecordTypeNode.new({}) end } / "{" space? fstkey:label space? ":" space? fstexp:expression tail:(space? "," space? key:label space? ":" space? exp:expression)* space? "}" { def to_node() data = { fstkey.text_value => fstexp.to_node() } tail.elements.each { |node| key = node.key.text_value assert("no key should apeare multiple times in a record: `#{key}`") { !data.key?(key) } data[key] = node.exp.to_node() } Dhallish::Ast::RecordTypeNode.new data end } end rule record_literal "{" space? "=" space? "}" { def to_node() Dhallish::Ast::RecordNode.new({}) end } / "{" space? fstkey:label space? "=" space? fstexp:expression tail:(space? "," space? key:label space? "=" space? exp:expression)* space? "}" { def to_node() data = { fstkey.text_value => fstexp.to_node() } tail.elements.each { |node| key = node.key.text_value assert("no key should apeare multiple times in a record: `#{key}`") { !data.key?(key) } data[key] = node.exp.to_node() } Dhallish::Ast::RecordNode.new data end } end ## Strings rule text_literal '"' tail:(exp:('${' space? innerexp:expression space? '}') / esc:('\"') / any:(!'"' .))* '"' { def to_node() parts = [] tail.elements.each { |node| if node.respond_to?(:exp) parts.push node.exp.innerexp.to_node() elsif node.respond_to?(:esc) parts.push "\"" else parts.push node.any.text_value end } Dhallish::Ast::TextInterpolationNode.new parts end } end ## Optionals rule optional_literal prefix:("Some" / "None") space exp:or_expression { def to_node() Dhallish::Ast::OptionalNode.new prefix.text_value, exp.to_node() end } end ## Type Literals rule forall_literal ("forall" / "∀") space? "(" space? lb:label space? ":" space? type:expression space? ")" space? ("->" / "→") space? res_type:expression { def to_node() Dhallish::Ast::FunctionType.new(type.to_node(), res_type.to_node(), lb.text_value) end } end ## Union Literals rule union_literal "<" space? start:(lb:label space? ":" space? type:record_merge_expression space? "|" space?)* lb:label space? "=" space? expr:add_sub_expression tail:(space? "|" space? lb:label space? ":" space? type:record_merge_expression)* space? ">" { def to_node() typed_labels = {} start.elements.each { |node| typed_labels[node.lb.text_value] = node.type.to_node() } tail.elements.each { |node| typed_labels[node.lb.text_value] = node.type.to_node() } Dhallish::Ast::UnionLiteral.new(typed_labels, lb.text_value, expr.to_node()) end } end rule empty_union_type_literal "<" space? ">" end rule union_type_literal empty_union_type_literal { def to_node() Dhallish::Ast::UnionType.new({}) end } / "<" space? lb:label space? ":" space? type:record_merge_expression tail:(space? "|" space? lb:label space? ":" space? type:record_merge_expression)* space? ">" { def to_node() type_map = {lb.text_value => type.to_node()} tail.elements.each { |node| type_map[node.lb.text_value] = node.type.to_node() } Dhallish::Ast::UnionType.new(type_map) end } end ## Rules for operator expressions ## rules are reversely ordered by their precedence ## e.g. if rule X is over rule Y, the operator associated with rule Y has a stronger precedence than Xs operator rule import_alternative_expression exp:annotated_expression tail:(space? "?" space? alternative:annotated_expression)* { def to_node() tail.elements.reduce(exp.to_node()) { |tree, node| Dhallish::Ast::Import_Alternative.new tree, node.alternative.to_node() } end } end rule annotated_expression exp:function_type_expression tail:(space? ":" space? type_expr:function_type_expression)* { def to_node() tail.elements.reduce(exp.to_node()) { |tree, node| Dhallish::Ast::TypeAnnotationNode.new tree, node.type_expr.to_node() } end } end rule function_type_expression arg_type:or_expression tail:(space? ("->" / "→") space? res_type:or_expression)* { def to_node() if tail.elements.size == 0 arg_type.to_node() else tree = nil tail.elements.reverse.each { |node| if tree.nil? tree = node.res_type.to_node() else tree = Dhallish::Ast::FunctionType.new node.res_type.to_node(), tree end } Dhallish::Ast::FunctionType.new arg_type.to_node(), tree end end } end rule or_expression exp:and_expression tail:(space? "||" space? exp:and_expression)* { def to_node() tail.elements.reduce(exp.to_node()) { |tree, node| Dhallish::Ast::BinaryArithOpNode.new([Dhallish::Types::Bool], tree, node.exp.to_node(), "||") { |x, y| x || y } } end } end rule and_expression exp:comparison_expression tail:(space? "&&" space? exp:comparison_expression)* { def to_node() tail.elements.reduce(exp.to_node()) { |tree, node| Dhallish::Ast::BinaryArithOpNode.new([Dhallish::Types::Bool], tree, node.exp.to_node(), "&&") { |x, y| x && y } } end } end rule comparison_expression exp:add_sub_expression tail:(space? op:("==" / "!=" / "<=" / "<" / ">=" / ">") space? exp:add_sub_expression)* { def to_node() tail.elements.reduce(exp.to_node()) { |tree, node| Dhallish::Ast::ComparisonOpNode.new(tree, node.exp.to_node(), node.op.text_value) { |lhs_val, rhs_val| lhs_val.send(node.op.text_value, rhs_val )} } end } end rule add_sub_expression exp:mult_div_expression tail:(space? op:("+"/"-") space? exp:mult_div_expression)* { def to_node() tail.elements.reduce(exp.to_node()) { |tree, node| exp = node.exp.to_node() if node.op.text_value == "+" Dhallish::Ast::BinaryArithOpNode.new(Dhallish::Types::Numbers, tree, exp, "+") { |x, y| x + y } else Dhallish::Ast::BinaryArithOpNode.new([Dhallish::Types::Double, Dhallish::Types::Integer], tree, exp, "-") { |x, y| x - y } end } end } end rule mult_div_expression exp:list_concat_expression tail:(space? op:("*"/"/") space? exp:list_concat_expression)* { def to_node() tail.elements.reduce(exp.to_node()) { |tree, node| exp = node.exp.to_node() if node.op.text_value == "*" Dhallish::Ast::BinaryArithOpNode.new(Dhallish::Types::Numbers, tree, exp, "*") { |x, y| x * y } else Dhallish::Ast::BinaryArithOpNode.new([Dhallish::Types::Double, Dhallish::Types::Integer], tree, exp, "/") { |x, y| x / y } end } end } end rule list_concat_expression exp:text_append_expression tail:(space? "#" space? exp:text_append_expression)* { def to_node() tail.elements.reduce(exp.to_node()) { |tree, node| Dhallish::Ast::ListConcatNode.new(tree, node.exp.to_node()) } end } end rule text_append_expression exp:record_merge_expression tail:(space? "++" space? exp:record_merge_expression)* { def to_node() tail.elements.reduce(exp.to_node()) { |tree, node| Dhallish::Ast::BinaryArithOpNode.new([Dhallish::Types::Text], tree, node.exp.to_node(), "+") { |x, y| x + y } } end } end rule record_merge_expression exp:application_expression tail:(space? op:("//\\\\" / "⩓" / "/\\" / "∧" / "//" / "⫽") space? exp:application_expression)* { def to_node() tail.elements.reduce(exp.to_node()) { |tree, node| if node.op.text_value == "//\\\\" or node.op.text_value == "⩓" Dhallish::Ast::RecordTypeRecursiveMergeNode.new tree, node.exp.to_node() elsif node.op.text_value == "/\\" or node.op.text_value == "∧" Dhallish::Ast::RecordRecursiveMergeNode.new tree, node.exp.to_node() else Dhallish::Ast::RecordNonRecursiveMergeNode.new tree, node.exp.to_node() end } end } end rule application_expression fn:proj_sel_expression tail:(space? exp:(!reserved arg:proj_sel_expression))* { def to_node() tail.elements.reduce(fn.to_node()) { |tree, node| Dhallish::Ast::FunctionCallNode.new(tree, node.exp.arg.to_node()) } end } end rule key_list "{" space? "}" { def to_list() [] end } / "{" space? head:label tail:(space? "," space? lbl:label)* space? "}" { def to_list() tail.elements.reduce([head.text_value]) { |list, node| list.push node.lbl.text_value list } end } end rule proj_sel_expression pe:primitive_expression tail:(space? "." space? sel:(a:label / b:key_list))* { def to_node() tail.elements.reduce(pe.to_node()) { |tree, node| if node.sel.respond_to? :a Dhallish::Ast::RecordUnionSelector.new tree, node.sel.a.text_value else Dhallish::Ast::RecordProjection.new tree, node.sel.b.to_list() end } end } end rule primitive_expression exp:bool_literal { def to_node() exp.to_node() end } / exp:double_literal { def to_node() exp.to_node() end } / exp:natural_literal { def to_node() exp.to_node() end } / exp:integer_literal { def to_node() exp.to_node() end } / exp:text_literal { def to_node() exp.to_node() end } / exp:list_literal { def to_node() exp.to_node() end } / exp:function_definition { def to_node() exp.to_node() end } / exp:optional_literal { def to_node() exp.to_node() end } / exp:forall_literal { def to_node() exp.to_node() end } / exp:import_expression { def to_node() exp.to_node() end } / exp:union_merge { def to_node() exp.to_node() end } / exp:label { def to_node() exp.to_node() end } / exp:record_literal { def to_node() exp.to_node() end } / exp:record_type_literal { def to_node() exp.to_node() end } / exp:union_literal { def to_node() exp.to_node() end } / exp:union_type_literal { def to_node() exp.to_node() end } / "(" space? exp:expression space? ")" { def to_node() exp.to_node() end } / "???" { def to_node() Dhallish::Ast::GetContext.new end } end ## End of operator expressions rule if_then_else_expression "if" space cond:expression space "then" space exp_true:expression space "else" space exp_false:expression { def to_node() Dhallish::Ast::IfThenElseNode.new cond.to_node(), exp_true.to_node(), exp_false.to_node() end } end rule label ("_" / [a-zA-Z]) ("_" / "-" / "/" / [a-zA-Z0-9])* { def to_node() Dhallish::Ast::VariableNode.new text_value end } / "`" lb:(([a-zA-Z0-9] / "-" / "/" / "_" / ":" / "." / "$")+) "`" { def to_node() Dhallish::Ast::VariableNode.new lb.text_value end } end rule let_expression declarations:("let" space var:label space? annot:(":" space? exp:expression space?)? "=" space? val:expression space)+ "in" space in_expr:expression { def to_node() vars = [] declarations.elements.each { |node| typeannot = nil if node.respond_to? :annot and node.annot.respond_to? :exp typeannot = node.annot.exp.to_node() end vars.append [node.var.text_value, typeannot, node.val.to_node()] } Dhallish::Ast::LetInNode.new vars, in_expr.to_node() end } end rule function_definition ("\\" / "λ") space? "(" space? lb:label space? ":" space? type_exp:expression space? ")" space? ("->" / "→") space? exp:expression { def to_node() Dhallish::Ast::FunctionDefinitionNode.new lb.text_value, type_exp.to_node(), exp.to_node() end } end rule forbidden_label reserved / "Text" / "Bool" / "Optional" / "None" / "Some" / "True" / "False" / "Type" / "Natural" ("/" ("show" / "toInteger" / "toDouble"))? / "Integer" ("/" ("show" / "toNatural" / "toDouble"))? / "Double" ("/" ("show" / "toNatural" / "toInteger"))? / "List" ("/" ("length" / "reduce" / "head" / "tail"))? / "Optional" ("/" ("fold"))? end rule reserved "if" / "then" / "else" / "let" / "in" / "as" / "?" end rule import_expression prefix:("../" / "./" / "~/" / "env:" / "http:" / "https:") src:import_source as_type:(space "as" space import_type:"Text")? { def to_node() import_as_text = (as_type.respond_to? :import_type and as_type.import_type.text_value == "Text") Dhallish::Ast::Import.new prefix.text_value, src.text_value, import_as_text end } end rule import_source # can be an environment variable, url or path (!([\s] / "\\" / "<" / ">" / "?" / "," / "[" / "]" / "{" / "}" / "#" / "(" / ")") .)* end rule union_merge "merge" space? handler:primitive_expression space? union:primitive_expression { def to_node() Dhallish::Ast::UnionMerge.new(handler.to_node(), union.to_node()) end } end
end