How to create Procs with variable arguments in Crystal? - crystal-lang

I want to pass arguments to a Proc like using splat, but it returns an error wrong number of block arguments (1 for 0)
$callbacks = {} of String => Array(->)
def add_callback(event, &block)
begin
$callbacks[event.to_s] << block
rescue
$callbacks[event.to_s] = [block]
end
end
add_callback(:event) do |arg|
puts "event!"
end
$callbacks["event"].first.call
Error in line 11: wrong number of block arguments (1 for 0) http://carc.in/#/r/7gw

You need to specify the argument list everywhere.
class EventManager
def initialize
#callbacks = Hash(String, Array(String ->)).new {|h, k| h[k] = [] of String -> }
end
def add(event, &callback : String ->)
#callbacks[event] << callback
end
def fire(event, argument : String)
#callbacks[event].each &.call(argument)
end
end
callbacks = EventManager.new
callbacks.add("foo") do |argument|
puts "Got #{argument}"
end
callbacks.add("bar") do
puts "I was called"
end
callbacks.fire "foo", "Ping"
callbacks.fire "bar", "Pong"

Related

Adding a nilable variable to a non-nilable array

I have a string array named current_todos and am trying to add a variable of type (String | Nil) named new_task by doing the following:
current_todos << new_task if typeof(new_task) == String
I get the error Error: no overload matches 'Array(String)#<<' with type (String | Nil).
How can I add a nilable string to current_todos after doing a type check?
Edit: here is the full code:
require "option_parser"
current_todos = [
"Laundry",
"Walk dog",
"Sing in shower"
]
new_task = nil
OptionParser.parse do |parser|
parser.banner = "Welcome to my todo app!"
parser.on "-a TASK", "--add TASK", "Type a new task" do |task|
new_task = task
end
parser.on "-h", "--help" do
puts parser
exit
end
end
current_todos << new_task if typeof(new_task) == String
current_todos.each do |todo|
puts todo
end
If new_task is of type String|Nil, you can test if it is non-nil. Then the compiler will know that it is a string. That here should work:
current_todos << new_task if new_task
Another way that the compiler will understand, which is closer to your original code, is to use is_a?:
current_todos << new_task if new_task.is_a?(String)
typeof(var) gives the compile time type, not the type at runtime. For example:
def check_type(var : String?)
puts typeof(var)
puts var.class
puts var.is_a? String
puts var.is_a? Nil
puts var.nil?
end
check_type(gets)
If you give input, it will print:
(String | Nil)
String
true
false
false
If you don't give input (gets returns nil) then it will print:
(String | Nil)
Nil
false
true
true
Looks like the reason something like current_todos << new_task if new_task.is_a?(String) doesn't work is because the new assignment of new_task happens within the .on function of the parser. Since the compiler doesn't know when/if this is called as it is inside a closure, it resorts to nil.
To force it to work I would need to use .not_nil!

How do I get a word inside a file at given position?

How to get a word inside a file at given position
def get_word(file, position)
File.each_line(file).with_index do |line, line_number|
if (line_number + 1) == position.line_number
# How to get a word at position.column_number ?
end
end
end
This should work like this:
File: message.md
Dear people:
My name is [Ángeliño](#angelino).
Bye!
Calls: get_word
record Position, line_number : Int32, column_number : Int32
get_word("message.md", Position.new(1, 9)) # => people
get_word("message.md", Position.new(3, 20)) # => Ángeliño
get_word("message.md", Position.new(5, 3)) # => Bye!
Maybe, this will give you a hint. Please, be advised that this implementation never treats a punctuation mark as a part of a word, so the last example returns Bye instead of Bye!.
def get_word_of(line : String, at position : Int)
chunks = line.split(/(\p{P}|\p{Z})/)
edge = 0
hashes = chunks.map do |chunk|
next if chunk.empty?
{chunk => (edge + 1)..(edge += chunk.size)}
end.compact
candidate = hashes.find { |hash| hash.first_value.covers?(position) }
.try &.first_key
candidate unless (candidate =~ /\A(?:\p{P}|\p{Z})+\Z/)
end
p get_word_of("Dear people:", 9) # => people
p get_word_of("My name is [Ángeliño](#angelino).", 20) # => Ángeliño
p get_word_of("Bye!", 3) # => Bye
A handy way to get the numerical position of a section of a string is to use a regex like so:
filename = "/path/to/file"
File.each_line(filename).each_with_index do |line, line_number|
term = "search-term"
column = line =~ %r{#{term}}
p "Found #{term} at line #{line_number}, column #{column}." if column
end
Outputs: "Found search-term at line 38, column 6"

Scala CSV parser with comments

First of all: credits. This code is based on the solution from here: Use Scala parser combinator to parse CSV files
The CSV files I want to parse can have comments, lines starting with #. And to avoid confusion: The CSV files are tabulator-separated. There are more constraints which would make the parser a lot easier, but since I am completly new to Scala I thought it would be best to stay as close to the (working) original as possible.
The problem I have is that I get a type mismatch. Obviously the regex for a comment does not yield a list. I was hoping that Scala would interpret a comment as a 1-element-list, but this is not the case.
So how would I need to modify my code that I can handle this comment lines? And closly related: Is there an elegant way to query the parser result so I can write in myfunc something like
if (isComment(a)) continue
So here is the actual code:
import org.apache.spark.SparkContext
import org.apache.spark.SparkContext._
import org.apache.spark.SparkConf
import scala.util.parsing.combinator._
object MyParser extends RegexParsers {
override val skipWhitespace = false // meaningful spaces in CSV
def COMMA = ","
def TAB = "\t"
def DQUOTE = "\""
def HASHTAG = "#"
def DQUOTE2 = "\"\"" ^^ { case _ => "\"" } // combine 2 dquotes into 1
def CRLF = "\r\n" | "\n"
def TXT = "[^\",\r\n]".r
def SPACES = "[ ]+".r
def file: Parser[List[List[String]]] = repsep((comment|record), CRLF) <~ (CRLF?)
def comment: Parser[List[String]] = HASHTAG<~TXT
def record: Parser[List[String]] = "[^#]".r<~repsep(field, TAB)
def field: Parser[String] = escaped|nonescaped
def escaped: Parser[String] = {
((SPACES?)~>DQUOTE~>((TXT|COMMA|CRLF|DQUOTE2)*)<~DQUOTE<~(SPACES?)) ^^ {
case ls => ls.mkString("")
}
}
def nonescaped: Parser[String] = (TXT*) ^^ { case ls => ls.mkString("") }
def applyParser(s: String) = parseAll(file, s) match {
case Success(res, _) => res
case e => throw new Exception(e.toString)
}
def myfunc( a: (String, String)) = {
val parserResult = applyParser(a._2)
println("APPLY PARSER FOR " + a._1)
for( a <- parserResult ){
a.foreach { println }
}
}
def main(args: Array[String]) {
val filesPath = "/home/user/test/*.txt"
val conf = new SparkConf().setAppName("Simple Application")
val sc = new SparkContext(conf)
val logData = sc.wholeTextFiles(filesPath).cache()
logData.foreach( x => myfunc(x))
}
}
Since the parser for comment and the parser for record are "or-ed" together they must be of the same type.
You need to make the following changes:
def comment: Parser[List[String]] = HASHTAG<~TXT ^^^ {List()}
By using ^^^ we are converting the result of the parser (which is the result returned by HASHTAG parser) to an empty List.
Also change:
def record: Parser[List[String]] = repsep(field, TAB)
Note that because comment and record parser are or-ed and because comment comes first, if the row begins with a "#" it will be parsed by the comment parser.
Edit:
In order to keep the comments text as an output of the parser (say if you want to print them later), and because you are using | you can do the following:
Define the following classes:
trait Line
case class Comment(text: String) extends Line
case class Record(elements: List[String]) extends Line
Now define comment, record & file parsers as follows:
val comment: Parser[Comment] = "#" ~> TXT ^^ Comment
val record :Parser[Line]= repsep(field, TAB) ^^ Record
val file: Parser[List[Line]] = repsep(comment | record, CRLF) <~ (CRLF?)
Now you can define the printing function myFunc:
def myfunc( a: (String, String)) = {
parseAll(file, a._2).map { lines =>
lines.foreach{
case Comment(t) => println(s"This is a comment: $t")
case Record(elems) => println(s"This is a record: ${elems.mkString(",")}")
}
}
}

scala repsep with new non empty line separator

I've got 3 lines for parsing
John Smith
Nick Jackson
Liza Ashwood
i want to use repsep scala function with new line separator like this:
def parseLines: Parser[Map[String, String]] = repsep(parse, lineSeparator)
where lineSeparator is just "\n"
but sometimes there can be an empty lines without any names, just empty lines. in this case, i want to ignore this function
i was trying to use opt(repsep) which seems logical but this doesn't work
what can i do here? thx
By empty lines, I'm assuming you can have data like this:
John Smith
<-- empty line
Nick Jackson
Liza Ashwood
In this case, it means that there's two times the lineSeparator. So you can use the rep1 combinator to tell that there should be at least 1 but there can be more line separators between each line of data:
def parseLines: Parser[Map[String, String]] = repsep(line, rep1("\n")) ^^ (ListMap() ++_ )
For example:
object MyParser extends RegexParsers {
override def skipWhitespace = false
def parse(input: java.io.Reader) = parse(parseLines, input) match {
case Success(res, _) => res
case e => throw new RuntimeException(e.toString)
}
def parseLines: Parser[Map[String, String]] = repsep(line, rep1("\n")) ^^ (ListMap() ++_ )
def oneOrMoreletters = "[a-zA-Z]+".r
def line:Parser[(String, String)] = (oneOrMoreletters <~ " ") ~ oneOrMoreletters ^^ (t => (t._1, t._2))
}

How to encode a constraint on the format of String values

As I frequently observe and how I often implement a name attribute, is to simply model it as String.
What now, if the name has to follow a certain syntax, i.e. format? In Java I probably would define a constructor with a check on its arguments, something like:
public Name(str: String) {
if (str == null) throw new IllegalArgumentException("Str must not be null.");
if (!str.matches("name format expressed as regex")) throw new IllegalArgumentException("Str must match 'regex' but was " + str);
this.str = str;
}
In Scala I came up with the following solution:
import StdDef.Str
import StdDef.Bol
import StdDef.?
import scala.util.parsing.combinator.RegexParsers
final case class Name private (pfx: ?[Str] = None, sfx: Str) {
override def toString = pfx.mkString + sfx
}
object Name extends RegexParsers {
implicit def apply(str: Str): Name = parseAll(syntax, str) match {
case Success(res, _) => Name(res._1, res._2)
case rej: NoSuccess => error(rej.toString)
}
lazy val syntax = (prefix ?) ~! suffix
lazy val prefix = (("x" | "X") ~! hyph) ^^ { case a ~ b => a + b }
lazy val suffix = alpha ~! (alpha | digit | hyph *) ^^ { case a ~ b => a + b.mkString }
lazy val alpha: Parser[Str] = """\p{Alpha}""".r
lazy val digit: Parser[Str] = """\p{Digit}""".r
lazy val hyph: Parser[Str] = "-"
override lazy val skipWhitespace = false
}
My intents here are:
Compose a Name from its natural representation, i.e. a String value
Check whether its natural representation forms a valid Name at construction time.
Disallow any other construction than through the factory method apply:(str:Str)Str.
Make the construction from its natural representation implicit, e.g. val a: Name = "ISBN 978-0-9815316-4-9".
Decompose a Name into its parts according to its syntactical elements.
Have errors being thrown with messages, such as:
===
--
^
[1.3] error: string matching regex `\p{Alpha}' expected but end of source found
I would like to know what solutions you come up with.
After giving the topic some more thoughts, I am currently taking the following approach.
Token.scala:
abstract class Token {
val value: Str
}
object Token {
def apply[A <: Token](ctor: Str => A, syntax: Regex) = (value: Str) => value match {
case syntax() => ctor(value)
case _ => error("Value must match '" + syntax + "' but was '" + value + "'.")
}
}
Tokens.scala:
final case class Group private (val value: Str) extends Token
final case class Name private (val value: Str) extends Token
trait Tokens {
import foo.{ bar => outer }
val Group = Token(outer.Group, """(?i)[a-z0-9-]++""".r)
val Name = Token(outer.Name, """(?i)(?:x-)?+[a-z0-9-]++""".r)
}
Given that you'd be comfortable using a regex in Java, it seems like overkill to then try and solve the same problem with a parser in Scala.
Stick with what you know here, but add a Scala twist to clean up the solution a bit. Regexes in Scala also define extractors, allowing them to be used in a pattern match:
//triple-quote to make escaping easier, the .r makes it a regex
//Note how the value breaks normal naming conventions and starts in uppercase
//This is to avoid backticks when pattern matching
val TestRegex = """xxyyzz""".r
class Name(str: String) {
str match {
case Null => throw new IllegalArgumentException("Str must not be null")
case TestRegex => //do nothing
case _ => throw new IllegalArgumentException(
"Str must match 'regex' but was " + str)
}
}
disclaimer: I didn't actually test this code, it may contain typos