How to expand multiple macros in Elixir? - macros

I'm starting my adventure with Elixir and I need a little bit o help.
I'm trying to simplify my structs definition and validation by using macros. The goal is to automatically inject defstruct and Vex library validators based on provided options in modules using it.
I've come up with the code as follows:
defmodule PdfGenerator.BibTypes.TypeDefinition do
#callback valid?(%{}) :: boolean
defmacro __using__(mod: mod, style: style, required: required, optional: optional) do
required_props = required |> Enum.map(&{:"#{&1}", nil})
optional_props = optional |> Enum.map(&{:"#{&1}", nil})
quote location: :keep do
defstruct unquote([{:style, style}] ++ required_props ++ optional_props)
#behaviour PdfGenerator.BibTypes.TypeDefinition
use Vex.Struct
def cast(%{} = map) do
styled_map = Map.put(map, :style, unquote(style))
struct_from_map(styled_map, as: %unquote(mod){})
end
defp struct_from_map(a_map, as: a_struct) do
keys =
Map.keys(a_struct)
|> Enum.filter(fn x -> x != :__struct__ end)
processed_map =
for key <- keys, into: %{} do
value = Map.get(a_map, key) || Map.get(a_map, to_string(key))
{key, value}
end
a_struct = Map.merge(a_struct, processed_map)
a_struct
end
validates(
:style,
presence: true,
inclusion: [unquote(style)]
)
end
Enum.each(required, fn prop ->
quote location: :keep do
validates(
unquote(prop),
presence: true
)
end
end)
end
end
And I'm using this macro in another module:
defmodule PdfGenerator.BibTypes.Booklet do
use PdfGenerator.BibTypes.TypeDefinition,
mod: __MODULE__,
style: "booklet",
required: [:title],
optional: [:author, :howpublished, :address, :month, :year, :note]
end
I want PdfGenerator.BibTypes.Booklet module, after macro expansion, to look as follows:
defmodule PdfGenerator.BibTypes.Booklet do
defstruct style: "booklet",
title: nil,
author: nil,
howpublished: nil,
address: nil,
month: nil,
year: nil,
note: nil
#behaviour PdfGenerator.BibTypes.TypeDefinition
use Vex.Struct
def cast(%{} = map) do
styled_map = Map.put(map, :style, "booklet")
struct_from_map(styled_map, as: %PdfGenerator.BibTypes.Booklet{})
end
defp struct_from_map(a_map, as: a_struct) do
keys =
Map.keys(a_struct)
|> Enum.filter(fn x -> x != :__struct__ end)
processed_map =
for key <- keys, into: %{} do
value = Map.get(a_map, key) || Map.get(a_map, to_string(key))
{key, value}
end
a_struct = Map.merge(a_struct, processed_map)
a_struct
end
validates(
:style,
presence: true,
inclusion: ["booklet"]
)
validates(
:title,
presence: true
)
end
As you can see, based on required option, I'm trying to expand to Vex-specific macro (which in turn should be expanded further on in Vex.Struct macro definition) validates(:<PROP_NAME>, presence: true) for every value in required list.
This macro code works (but without these validators for required values) when I remove last block from __using__ macro:
Enum.each(required, fn prop ->
quote location: :keep do
validates(
unquote(prop),
presence: true
)
end
end)
But with it, when I'm trying to issue following command in the iex console: %PdfGenerator.BibTypes.Booklet{}
I get:
** (CompileError) iex:1: PdfGenerator.BibTypes.Booklet.__struct__/1 is undefined, cannot expand struct PdfGenerator.BibTypes.Booklet
Any idea, what am I doing wrong? Any hint would be greatly appreciated as I'm pretty new to the whole Elixir and macros world.

Since you did not provide the MCVE, it’s extremely hard to test the solution, but at the first glance the issue is you expect some magic from Kernel.SpecialForms.quote/2, while it does not implicitly inject anything anywhere, it just produces an AST.
When you call
Enum.each(...)
as the last line of quote do block, the result of this call is returned as AST from quote do. That said, the current __using__ implementation injects the result of the call to quote do: :ok, which is apparently :ok. What you need, is to build the list of clauses to be injected:
defmacro __using__(mod: mod, ...) do
# preparation
ast_defstruct =
quote location: :keep do
# whole stuff for defstruct
end
# NB! last term will be returned from `__using__`!
[
ast_defstruct |
Enum.map(required, fn prop ->
quote location: :keep,
do: validates(unquote(prop), presence: true)
end)
]
By using Enum.map/2 we collect quoted ASTs for each element, and append them to already built AST for defstruct creation. We return a list (which is a proper AST,) containing many clauses.
Still, I am unsure if this is the only glitch due to lack of MCVE, but this is definitely the proper fix to start with.

Related

Return keyword inside the inline function in Scala

I've heard about to not use Return keyword in Scala, because it might change the flow of the program like;
// this will return only 2 because of return keyword
List(1, 2, 3).map(value => return value * 2)
Here is my case; I've recursive case class, and using DFS to do some calculation on it. So, maximum depth could be 5. Here is the model;
case class Line(
lines: Option[Seq[Line]],
balls: Option[Seq[Ball]],
op: Option[String]
)
I'm using DFS approach to search this recursive model. But at some point, if a special value exist in the data, I want to stop iterating over the data left and return the result directly instead. Here is an example;
Line(
lines = Some(Seq(
Line(None, Some(Seq(Ball(1), Ball(3))), Some("s")),
Line(None, Some(Seq(Ball(5), Ball(2))), Some("d")),
Line(None, Some(Seq(Ball(9))), None)
)),
balls = None,
None
)
In this data, I want to return as like "NOT_OKAY" if I run into the Ball(5), which means I do not need to any operation on Ball(2) and Ball(9) anymore. Otherwise, I will apply a calculation to the each Ball(x) with the given operator.
I'm using this sort of DFS method;
def calculate(line: Line) = {
// string here is the actual result that I want, Boolean just keeps if there is a data that I don't want
def dfs(line: Line): (String, Boolean) = {
line.balls.map{_.map { ball =>
val result = someCalculationOnBall(ball)
// return keyword here because i don't want to iterate values left in the balls
if (result == "NOTREQUIRED") return ("NOT_OKAY", true)
("OKAY", false)
}}.getOrElse(
line.lines.map{_.map{ subLine =>
val groupResult = dfs(subLine)
// here is I'm using return because I want to return the result directly instead of iterating the values left in the lines
if (groupResult._2) return ("NOT_OKAY", true)
("OKAY", false)
}}
)
}
.... rest of the thing
}
In this case, I'm using return keyword in the inline functions, and change the behaviour of the inner map functions completely. I've just read somethings about not using return keyword in Scala, but couldn't make sure this will create a problem or not. Because in my case, I don't want to do any calculation if I run into a value that I don't want to see. Also I couldn't find the functional way to get rid of return keyword.
Is there any side effect like stack exception etc. to use return keyword here? I'm always open to the alternative ways. Thank you so much!

lua inheritance on existing object

I am writing a new constructor and I have something like this:
function Map:new(path, world, debug)
local map = sti(path, { "box2d" })
return map
end
function Map:update(dt)
print('call this')
end
sti is some thirdparty library that constructs a class object.
What I am trying to do is make it so when I call:
map:update(dt)
it calls the functions I have declared. If not found, it calls the actual function that sti sets up on the object.
I've tried stuffing around with metatables but can't seem to get my functions to take priority over the third party library supplied functions....
Reading the source code for what I believe is the library you're using (Simple-Tiled-Implementation), I figured out it actually overrides your metatable with another one:
local function new(map, plugins, ox, oy)
local dir = ""
if type(map) == "table" then
map = setmetatable(map, Map) -- Here
else
-- Check for valid map type
local ext = map:sub(-4, -1)
assert(ext == ".lua", string.format(
"Invalid file type: %s. File must be of type: lua.",
ext
))
-- Get directory of map
dir = map:reverse():find("[/\\]") or ""
if dir ~= "" then
dir = map:sub(1, 1 + (#map - dir))
end
-- Load map
map = setmetatable(assert(love.filesystem.load(map))(), Map) -- Or here
end
map:init(dir, plugins, ox, oy)
return map
end
The function above is defined here
You'll need to pass a table argument as map instead of the path, there you can define update(), which will take precedence over the metatable provided by STI.
I believe you can copy the procedure STI does to load your map and provide it with a table containing the function you wish to define inside:
-- Check for valid map type
local ext = map:sub(-4, -1)
assert(ext == ".lua", string.format(
"Invalid file type: %s. File must be of type: lua.",
ext
))
-- Get directory of map
dir = map:reverse():find("[/\\]") or ""
if dir ~= "" then
dir = map:sub(1, 1 + (#map - dir))
end
-- Load map
local map = assert(love.filesystem.load(map))()
function map:update()
-- Do things
end
sti(map, { "box2d" })
unfortunately sti declares 'local dir' at the top of the function so copying the code did not work.
I found a solution how ever I have made myself some way to easily set a class as a proxy in lua:
-- forward a function call from oldSelf:fn(...) to newSelf:fn(...)
function utils.forwardFunc(fn, newSelf)
return function(oldSelf, ...)
local function __NULL__() end
return (fn or __NULL__)(newSelf, ...)
end
end
-- make a function fn(...) call newSelf:fn(...)
function utils.func(fn, newSelf)
return function(...)
local function __NULL__() end
return (fn or __NULL__)(newSelf, ...)
end
end
-- forward any undefined functions called on 'from' to 'to'
-- if 'to' is a function, it acts as a dynamic proxy, incase you are changing what class you are proxying to
-- on the fly. For example, a state machine proxies to the current state
function utils.proxyClass(from, to)
local mt = getmetatable(from)
setmetatable(from, {__index = function(_, func)
if mt and mt[func] then
return mt[func]
end
local forwardTo = to
if type(to) == 'function' then
forwardTo = to(from)
end
if type(forwardTo[func]) == 'function' then
return utils.forwardFunc(forwardTo[func], forwardTo)
else
return forwardTo[func]
end
end})
end

How to do a case insensitive match for command line arguments in scala?

I'm working on a command line tool written in Scala which is executed as:
sbt "run --customerAccount 1234567"
Now, I wish to make this flexible to accept "--CUSTOMERACCOUNT" or --cUsToMerAccount or --customerACCOUNT ...you get the drift
Here's what the code looks like:
lazy val OptionsParser: OptionParser[Args] = new scopt.OptionParser[Args]("scopt") {
head(
"XML Generator",
"Creates XML for testing"
)
help("help").text(s"Prints this usage message. $envUsage")
opt[String]('c', "customerAccount")
.text("Required: Please provide customer account number as -c 12334 or --customerAccount 12334")
.required()
.action { (cust, args) =>
assert(cust.nonEmpty, "cust is REQUIRED!!")
args.copy(cust = cust)
}
}
I assume the opt[String]('c', "customerAccount") does the pattern matching from the command line and will match with "customerAccount" - how do I get this to match with "--CUSTOMERACCOUNT" or --cUsToMerAccount or --customerACCOUNT? What exactly does the args.copy (cust = cust) do?
I apologize if the questions seem too basic. I'm incredibly new to Scala, have worked in Java and Python earlier so sometimes I find the syntax a little hard to understand as well.
You'd normally be parsing the args with code like:
OptionsParser.parse(args, Args())
So if you want case-insensitivity, probably the easiest way is to canonicalize the case of args with something like
val canonicalized = args.map(_.toLowerCase)
OptionsParser.parse(canonicalized, Args())
Or, if you for instance wanted to only canonicalize args starting with -- and before a bare --:
val canonicalized =
args.foldLeft(false -> List.empty[String]) { (state, arg) =>
val (afterDashes, result) = state
if (afterDashes) true -> (arg :: result) // pass through unchanged
else {
if (arg == "==") true -> (arg :: result) // move to afterDash state & pass through
else {
if (arg.startsWith("--")) false -> (arg.toLowerCase :: result)
else false -> (arg :: result) // pass through unchanged
}
}
}
._2 // Extract the result
.reverse // Reverse it back into the original order (if building up a sequence, your first choice should be to build a list in reversed order and reverse at the end)
OptionsParser.parse(canonicalized, Args())
Re the second question, since Args is (almost certainly) a case class, it has a copy method which constructs a new object with (most likely, depending on usage) different values for its fields. So
args.copy(cust = cust)
creates a new Args object, where:
the value of the cust field in that object is the value of the cust variable in that block (this is basically a somewhat clever hack that works with named method arguments)
every other field's value is taken from args

How can I get the name of procedure in Nim?

I am trying to write a macro for debug print in the Nim language.
Currently this macro adds filename andline to the output by instantiationInfo().
import macros
macro debugPrint(msg: untyped): typed =
result = quote do:
let pos = instantiationInfo()
echo pos.filename, ":", pos.line, ": ", `msg`
proc hello() =
debugPrint "foo bar"
hello()
currently output:
debug_print.nim:9: foo bar
I would like to add the name of the procedure (or iterator) of the place where the macro was called.
desired output:
debug_print.nim:9(proc hello): foo bar
How can I get the name of procedure (or iterator) in Nim, like __func__ in C?
At runtime you can do getFrame().procname, but it only works with stacktrace enabled (not in release builds).
At compile-time surprisingly I can't find a way to do it. There is callsite() in macros module, but it doesn't go far enough. It sounds like something that might fit into the macros.LineInfo object.
A hacky solution would be to also use __func__ and parse that back into the Nim proc name:
template procName: string =
var name: cstring
{.emit: "`name` = __func__;".}
($name).rsplit('_', 1)[0]
building on answer from #def- but making it more robust to handle edge cases of functions containing underscores, and hashes containing trailing _N or not
also using more unique names as otherwise macro would fail if proc defines a variable name
import strutils
proc procNameAux*(name:cstring): string =
let temp=($name).rsplit('_', 2)
#CHECKME: IMPROVE; the magic '4' chosen to be enough for most cases
# EG: bar_baz_9c8JPzPvtM9azO6OB23bjc3Q_3
if temp.len>=3 and temp[2].len < 4:
($name).rsplit('_', 2)[0]
else:
# EG: foo_9c8JPzPvtM9azO6OB23bjc3Q
($name).rsplit('_', 1)[0]
template procName*: string =
var name2: cstring
{.emit: "`name2` = __func__;".}
procNameAux(name2)
proc foo_bar()=
echo procName # prints foo_bar
foo_bar()
NOTE: this still has some issues that trigger in complex edge cases, see https://github.com/nim-lang/Nim/issues/8212

Macro matching a single expr after [expr*], path[tt*] and ident[tt*] branches

I'm trying to make a macro that I can call in the following manner:
mactest!(some::Path[1, 2, AnotherName[3, 4]])
Which would be equivalent to the following:
make_result(
"some::Path",
1.convert(),
2.convert(),
make_result(
"AnotherName",
3.convert(),
4.convert()
)
)
where convert is some trait that will be implemented for a bunch of types. (convert and make_result has the same result type).
This is as far as I've come:
// Note: u32 is used as an example result type.
// The real code attempts to create a more complicated object.
trait Foo {
fn convert(&self) -> u32;
}
fn make_result(name: &str, data: Vec<u32>) -> u32 {
// This example ignores name and makes a meaningless result
data.iter().fold(0,|a, &b| a + b)
}
#[macro_export]
macro_rules! mactest {
( [ $($inner:expr),* ] ) => {{
let mut result = Vec::new();
$(
// Process each element.
result.push(mactest!($inner));
)*
result
}};
($name:path [ $($inner:tt),* ] ) => {
make_result(stringify!($name), mactest!([$($inner),*]))
};
($name:ident [ $($inner:tt),* ] ) => {
make_result(stringify!($name), mactest!([$($inner),*]))
};
// Process single value. This is never matched?
($x:expr) => {
$x.convert()
};
}
The first matching branch of the macro is supposed to match each element of a list to either the path/ident[items] or the single item .convert branch at the end. But the final branch is never reached, with rust complaining error: expected ident, found '1' when single items enter the macro, i.e. mactest!(1).
My reasoning as a beginner rust user is that the macro has four patterns: [expr*], path[tt*], ident[tt*] and expr. When I pass something like 1 into the macro, I don't see why any of the above patterns should match/interfere.
Can someone explain why this doesn't work? Is there a workaround to get the intended result?
macro rules are tried by starting with the first one and going down from there. So if you want to prevent your other rules from triggering in special cases, you need to put the special case rule first.
Try it out in the playground