defmodule VerbNet do @moduledoc ~S""" This module provides a lookup interface into the [VerbNet](https://verbs.colorado.edu/~mpalmer/projects/verbnet.html) semantic mapping dataset. """ # Some handy module attributes for locating assets. @external_resource verbnet_xml_path = Path.join([__DIR__, "..", "assets", "verbnet"]) # Start timer start = System.monotonic_time() # Load and parse each VerbNet class XML. Code.ensure_compiled(VerbNet.XML) classes = Path.join([verbnet_xml_path, "*.xml"]) |> Path.wildcard() |> Task.async_stream(VerbNet.XML, :process_xml, []) |> Enum.to_list() |> Enum.map(fn({:ok, class_list}) -> class_list end) |> List.flatten() # Process each class extracted from the XML. for {class_id, classdef, sections} <- classes do # Extract the sections into values we can unquote. members = sections |> Map.get(:members, %{}) roles = sections |> Map.get(:themroles, %{}) frames = sections |> Map.get(:frames, %{}) # Escape complex data structures for unquoting. classdef_esc = Macro.escape(classdef) members_esc = members |> Macro.escape() roles_esc = roles |> Macro.escape() frames_esc = frames |> Macro.escape() # Define basic lookup functions for this VerbNet class. def class(unquote(class_id)), do: unquote(classdef_esc) def members(unquote(class_id)), do: unquote(members_esc) def roles(unquote(class_id)), do: unquote(roles_esc) def frames(unquote(class_id)), do: unquote(frames_esc) # Define frame lookups based on POS tags + class members, if we have class members. if !Enum.empty?(members) do members_keys = members |> Map.keys() for {primary, frame} <- frames do frame_esc = frame |> Map.put(:class_id, class_id) |> Macro.escape() def find_frame(unquote(primary), member) when member in unquote(members_keys), do: unquote(frame_esc) end end end # End timer elapsed = (System.monotonic_time() - start) |> System.convert_time_unit(:native, :millisecond) IO.puts "Codified #{Enum.count(classes)} VerbNet classes in #{elapsed}ms" @doc ~S""" Return complete raw map of the entire VerbNet class. On failed lookup, returns :invalid_class. """ @spec class(class_id :: binary) :: map def class(class_id) do invalid_class(class_id) end @doc ~S""" Return list of member maps for VerbNet class, keyed by member name. On failed lookup, returns :invalid_class. ## Examples iex> VerbNet.members("wish-62") %{"aim" => %{grouping: "aim.02", wn: "aim%2:31:01"}, "dream" => %{grouping: "dream.01", wn: "dream%2:36:00"}, "expect" => %{grouping: "expect.01", wn: "expect%2:32:00"}, "imagine" => %{grouping: "imagine.02", wn: "imagine%2:31:01"}, "intend" => %{grouping: "intend.01", wn: "intend%2:31:00"}, "mean" => %{grouping: "mean.02", wn: "mean%2:31:00"}, "plan" => %{grouping: "plan.01", wn: "plan%2:31:01"}, "propose" => %{grouping: "propose.02", wn: "propose%2:31:01"}, "think" => %{grouping: "think.05", wn: "think%2:31:03"}, "wish" => %{grouping: "wish.02", wn: "wish%2:37:02"}, "yen" => %{grouping: "", wn: "yen%2:37:00"}} iex> VerbNet.members("foo") :invalid_class """ @spec members(class_id :: binary) :: map def members(class_id) do invalid_class(class_id) end @doc ~S""" Return list of thematic role maps for VerbNet class, keyed by role type. On failed lookup, returns :invalid_class. ## Examples iex> VerbNet.roles("wish-62") %{"Experiencer" => [{:selrestrs, %{logic: "or"}, [{:selrestr, %{type: "animate", value: "+"}, []}, {:selrestr, %{type: "organization", value: "+"}, []}]}], "Stimulus" => [{:selrestrs, %{}, []}]} iex> VerbNet.roles("foo") :invalid_class """ @spec roles(class_id :: binary) :: map def roles(class_id) do invalid_class(class_id) end @doc ~S""" Return list of semantic frames for VerbNet class, keyed by primary POS pattern. On failed lookup, returns :invalid_class. ## Examples iex> VerbNet.frames("wish-62") |> Map.keys() ["NP V NP", "NP V NP ADJ", "NP V NP to be ADJ", "NP V S_INF", "NP V for NP S_INF", "NP V that S"] iex> VerbNet.frames("foo") :invalid_class """ @spec frames(class_id :: binary) :: list def frames(class_id) do invalid_class(class_id) end @doc ~S""" Return semantic frame from VerbNet that matches the provided primary POS pattern and class member. On failed lookup, returns :no_match. ## Examples iex> VerbNet.find_frame("NP V NP", "wish") %{class_id: "wish-62", description: %{descriptionnumber: "8.1", primary: "NP V NP", secondary: "NP", xtag: "0.2"}, examples: [["I wished it."]], semantics: [{:pred, %{value: "desire"}, [{:args, %{}, [{:arg, %{type: "Event", value: "E"}, []}, {:arg, %{type: "ThemRole", value: "Experiencer"}, []}, {:arg, %{type: "ThemRole", value: "Stimulus"}, []}]}]}], syntax: [{:np, %{value: "Experiencer"}, [{:synrestrs, %{}, []}]}, {:verb, %{}, []}, {:np, %{value: "Stimulus"}, [{:synrestrs, %{}, [{:synrestr, %{type: "sentential", value: "-"}, []}]}]}]} iex> VerbNet.find_frame(["NP", "V", "NP"], "wish") %{class_id: "wish-62", description: %{descriptionnumber: "8.1", primary: "NP V NP", secondary: "NP", xtag: "0.2"}, examples: [["I wished it."]], semantics: [{:pred, %{value: "desire"}, [{:args, %{}, [{:arg, %{type: "Event", value: "E"}, []}, {:arg, %{type: "ThemRole", value: "Experiencer"}, []}, {:arg, %{type: "ThemRole", value: "Stimulus"}, []}]}]}], syntax: [{:np, %{value: "Experiencer"}, [{:synrestrs, %{}, []}]}, {:verb, %{}, []}, {:np, %{value: "Stimulus"}, [{:synrestrs, %{}, [{:synrestr, %{type: "sentential", value: "-"}, []}]}]}]} iex> VerbNet.find_frame("NP V NP", "foo") :no_match """ @spec find_frame(primary :: binary, member :: binary) :: map def find_frame(primary, member) when is_binary(primary) do no_match(primary, member) end @spec find_frame(primary :: list, member :: binary) :: map def find_frame(primary, member) when is_list(primary) do find_frame(Enum.join(primary, " "), member) end # Util method to return :invalid_class, yet allow function specs to provide useful help. # Reason: _class_id was generating "arg1" in REPL help and docs. defp invalid_class(_class_id) do :invalid_class end # Util method to return :no_match, yet allow function specs to provide useful help. # Reason: _primary, _member was generating "arg1, arg2" in REPL help and docs. defp no_match(_primary, _member) do :no_match end end