Skip to content

Instantly share code, notes, and snippets.

@s-zeid
Last active August 29, 2015 14:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save s-zeid/c4ffd325a6603f0697db to your computer and use it in GitHub Desktop.
Save s-zeid/c4ffd325a6603f0697db to your computer and use it in GitHub Desktop.
.I <3 Nim. — Scott Zeid's Nim playground
.nimcache
*.nimc
*.swp
# From: <https://nim-by-example.github.io/oop_macro/>
# Public domain via:
# <https://github.com/nim-by-example/nim-by-example.github.io/blob/master/LICENCE>
# As of: 2015-01-11
import macros
macro class*(head: expr, body: stmt): stmt {.immediate.} =
# The macro is immediate so that it doesn't
# resolve identifiers passed to it
var typeName, baseName: PNimrodNode
if head.kind == nnkIdent:
# `head` is expression `typeName`
# echo head.treeRepr
# --------------------
# Ident !"Animal"
typeName = head
elif head.kind == nnkInfix and $head[0] == "of":
# `head` is expression `typeName of baseClass`
# echo head.treeRepr
# --------------------
# Infix
# Ident !"of"
# Ident !"Animal"
# Ident !"TObject"
typeName = head[1]
baseName = head[2]
else:
quit "Invalid node: " & head.lispRepr
# echo treeRepr(body)
# --------------------
# StmtList
# VarSection
# IdentDefs
# Ident !"name"
# Ident !"string"
# Empty
# IdentDefs
# Ident !"age"
# Ident !"int"
# Empty
# MethodDef
# Ident !"vocalize"
# Empty
# Empty
# FormalParams
# Ident !"string"
# Empty
# Empty
# StmtList
# StrLit ...
# MethodDef
# Ident !"age_human_yrs"
# Empty
# Empty
# FormalParams
# Ident !"int"
# Empty
# Empty
# StmtList
# DotExpr
# Ident !"this"
# Ident !"age"
# create a new stmtList for the result
result = newStmtList()
# var declarations will be turned into object fields
var recList = newNimNode(nnkRecList)
# Iterate over the statements, adding `this: T`
# to the parameters of functions
for node in body.children:
case node.kind:
of nnkMethodDef, nnkProcDef:
# inject `this: T` into the arguments
let p = copyNimTree(node.params)
p.insert(1, newIdentDefs(ident"this", typeName))
node.params = p
result.add(node)
of nnkVarSection:
# variables get turned into fields of the type.
for n in node.children:
recList.add(n)
else:
result.add(node)
# The following prints out the AST structure:
#
# import macros
# dumptree:
# type X = ref object of Y
# z: int
# --------------------
# TypeSection
# TypeDef
# Ident !"X"
# Empty
# RefTy
# ObjectTy
# Empty
# OfInherit
# Ident !"Y"
# RecList
# IdentDefs
# Ident !"z"
# Ident !"int"
# Empty
result.insert(0,
if baseName == nil:
quote do:
type `typeName` = ref object
else:
quote do:
type `typeName` = ref object of `baseName`
)
# Inspect the tree structure:
#
# echo result.treeRepr
# --------------------
# StmtList
# StmtList
# TypeSection
# TypeDef
# Ident !"Animal"
# Empty
# RefTy
# ObjectTy
# Empty
# OfInherit
# Ident !"TObject"
# Empty <= We want to replace this
# MethodDef
# ...
result[0][0][0][2][0][2] = recList
# Lets inspect the human-readable version of the output
# echo repr(result)
# Output:
# type
# Animal = ref object of TObject
# name: string
# age: int
#
# method vocalize(this: Animal): string =
# "..."
#
# method age_human_yrs(this: Animal): int =
# this.age
NIMFLAGS =
all: test.nimc
%.nimc: %.nim
nim c --nimcache:.nimcache $(NIMFLAGS) -o:"$@" "$^"
.PHONY: clean
clean:
rm -f test.nimc
# vim:se fdm=expr fde=getline(v\:lnum)=~'^[a-z]\\+\\(\ [^\:=]\\+\\)\\?[\:=]\\?$'?'>1'\:'=':
# (The modeline folds top-level blocks using a regular expression.
# Vim will execute the value of `fde` (short for `foldexpr`) in a sandbox:
# <http://vimhelp.appspot.com/options.txt.html#'foldexpr'>)
## Ceci n'est pas une comment. It is a documentation comment, since it starts
## with two #s, and it is part of the syntax tree, unlike a regular comment.
# Acts like `when false`
template note(label: expr=nil, body: stmt=nil) {.immediate.} = discard
# For organizational purposes.
# The label is stringified and stored in currentSection.
# Unlike `block`, no new scope is created, and our label can be any expression
# that can be stringified, not just an identifier.
var currentSection: string = nil
template section(body: auto) = section "": body
template section(label: expr, body: auto) =
currentSection = $(label)
body
currentSection = nil
section "Allows us to cleanly keep track of our main procs":
proc main()
type
MainProc = tuple[name: string, `proc`: proc() {.locks: 0.}, section: string]
MainProcs = seq[MainProc]
var mainProcs: MainProcs = @[]
template `&=`(main: proc(), `proc`: expr) =
block:
var mainProc: MainProc = (astToStr(`proc`), `proc`, currentSection)
mainProcs.add(mainProc)
note "Usage":
proc someProc() =
echo "Hello world"
main &= someProc
# then at runtime...
# %% someProc %%
# Hello world
# (see `proc main` at the end of the file)
# (Overloading `=` is on the roadmap for Nim 1.0.)
section "stuffs":
# not quite a comment; indentation and syntax still matter
note "ELF symbol table specimens":
proc debug(args: varargs[string]) = debug_104004
proc bnay() = bnay_104015
class HelloWorld of RootObj: # not in symbol table
method name: string = name_104020 name_104028
method `name=`(name) = nameHEX3D_104046 nameHEX3D_104064
method readName() = readname_104040 readname_104070
method greet() = greet_105058 greet_105069
proc fuckOff() {.exportc.} = fuckOff
class HelloPerson of HelloWorld: # likewise not in symbol table
method greet() = greet_105124
proc fuckOff() = fuckoff_104174
proc hello() {.exportc.} = hello
import classes; note:
# The classes.nim file in this repo contains a macro that allows us to do:
class ClassName of Superclass:
var x: string
method methodName: string = this.x
# instead of:
type ClassName = ref object of SuperClass
x: string
method methodName(this: ClassName) = this.x
# ... as in the first part literally becomes the second part
proc debug(args: varargs[string]) =
stderr.write("%")
for s in args.items:
stderr.write(" " & s)
stderr.write("\n")
proc bnay() =
discard 1992
section "Hello world":
class HelloWorld of RootObj:
# Properties!
var F_name: string; note:
# Convention would be FName, but I don't like capitalizing field names.
# However, only the *first* character of a Nim identifier is case-sensitive,
# and underscores are *ignored* in Nim identifiers, so `F_name` is actually
# the same as `FName`.
##
method name: string =
debug("getting name (in HelloWorld)")
result = if this.F_name != nil: this.F_name else: "world"
method `name=`(name) =
debug("setting name to", repr(name))
this.FName = name
# Methods! (NB: `method` is dynamic binding; `proc` is static binding)
method readName() =
discard
method greet() =
echo("Hello ", this.name, "!")
proc fuckOff() {.exportc.} =
this.readName()
this.greet()
class HelloPerson of HelloWorld: # Inheritance!
method name: string =
debug("getting name (in HelloPerson)")
if this.F_name != nil: this.F_name else: "person"
method readName() =
var name: string
while name in [nil, ""]:
stdout.write("What's your name? ")
name = readLine(stdin)
this.name = name
method greet() =
echo("Hi, ", this.name, "!")
proc fuckOff() =
HelloWorld(this).fuckOff() # super calls!
echo("Now fuck off, you stupid nimrod!")
var hp: HelloPerson = nil
proc hello() {.exportc.} =
bnay()
var hw = HelloWorld()
hw.fuckOff()
hp = HelloPerson()
hp.fuckOff()
main &= hello
section "JSON Spam sketch":
import json
proc `.`(node: json.JsonNode, key: string): json.JsonNode = node[key]
proc `.=`(node: json.JsonNode, key: string, value: json.JsonNode) = node[key] = value
template times(n: expr, body: stmt): stmt =
for i in 1..n:
body
proc spamSketch() =
var sketch = json.parseJson("""{"spam": "Spam, spam, spam, spam,"}""")
2.times:
echo sketch.spam.str
sketch.spam = json.newJString"LOVELY SPAM, WONDERFUL SPAM"
3.times:
echo sketch.spam.str
sketch.spam = json.newJString"SHUT UP!"
3.times:
echo sketch.spam.str
main &= spamSketch
section "HTTP Spam menu":
import strutils
type Header = tuple[name: string, value: string]
proc `&=`(req: var string, header: Header) =
req &= header.name & ": " & header.value & "\r\l"
proc httpRequest(`method` = "GET", host, path: string, headers: varargs[Header]): string =
var path = (if path.startsWith("/"): path else: "/" & path)
var req: string = ""
req &= `method`.toUpper() & " " & path & " HTTP/1.0\r\l"
req &= ("Host", host)
req &= ("User-Agent", "test.nim/1.0")
for header in headers.items:
req &= header
req &= "\r\l"
return req
import net
from rawsockets import AF_INET6
proc getSpamMenu() =
var name = (if hp != nil: hp.name else: "nil")
var req = httpRequest("HEAD", host="www.greenmidgetcafe.com", path="menu",
("X-User-Name", name))
echo req
var sock = net.newSocket(AF_INET6, buffered=false)
sock.connect("www.greenmidgetcafe.com", net.Port(80), AF_INET6)
sock.send(req)
var data: string = "";
while sock.recv(data, 1) != 0:
stdout.write(data)
sock.close()
main &= getSpamMenu
section:
proc inUnnamedSection() =
echo "This proc is defined in an unnamed section."
main &= inUnnamedSection
proc notInSection() =
echo "This proc is not in a section."
main &= notInSection
proc main() =
for i in 1..mainProcs.len:
var mainProc = mainProcs[i-1]
var procSyntax = "proc " & mainProc.name & "()"
var sectionDesc =
if mainProc.section != "": mainProc.section
else: "(unnamed section)"
stderr.write("%")
if unlikely(mainProc.section == nil):
debug(procSyntax, "%%")
else:
debug(sectionDesc, "-", procSyntax, "%%")
mainProc.`proc`()
if i != mainProcs.len:
echo ""
when isMainModule:
setControlCHook(proc() {.noconv.} = quit(1))
main()
note "Python equivalent":
# if __name__ == "__main__":
# try:
# main()
# except KeyboardInterrupt:
# pass
##
%% Hello world - proc hello() %%
% getting name (in HelloWorld)
Hello world!
What's your name? Scott
% setting name to 0x7fc96f206050"Scott"
% getting name (in HelloPerson)
Hi, Scott!
Now fuck off, you stupid nimrod!
%% JSON Spam sketch - proc spamSketch() %%
Spam, spam, spam, spam,
Spam, spam, spam, spam,
LOVELY SPAM, WONDERFUL SPAM
LOVELY SPAM, WONDERFUL SPAM
LOVELY SPAM, WONDERFUL SPAM
SHUT UP!
SHUT UP!
SHUT UP!
%% HTTP Spam menu - proc getSpamMenu() %%
% getting name (in HelloPerson)
HEAD /menu HTTP/1.0
Host: www.greenmidgetcafe.com
User-Agent: test.nim/1.0
X-User-Name: Scott
HTTP/1.0 404 Not Found
Content-Type: text/html
Content-Length: 345
Connection: close
Date: Mon, 12 Jan 2015 09:48:58 GMT
Server: lighttpd/bnay
%% (unnamed section) - proc inUnnamedSection() %%
This proc is defined in an unnamed section.
%% proc notInSection() %%
This proc is not in a section.
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment