├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── ast.nim ├── examples ├── animation-1.gif ├── fonts │ ├── NotoEmoji-Regular.ttf │ ├── Roboto-Bold.ttf │ ├── Roboto-Light.ttf │ ├── Roboto-Regular.ttf │ └── entypo.ttf ├── images │ ├── image1.jpg │ ├── image10.jpg │ ├── image11.jpg │ ├── image12.jpg │ ├── image2.jpg │ ├── image3.jpg │ ├── image4.jpg │ ├── image5.jpg │ ├── image6.jpg │ ├── image7.jpg │ ├── image8.jpg │ └── image9.jpg └── screenshot-1.png ├── idents.nim ├── interpolator.nim ├── keywords.nim ├── kiwi.nim ├── kiwi ├── constraint.nim ├── expression.nim ├── relational_operator.nim ├── row.nim ├── solver.nim ├── strength.nim ├── symbol.nim ├── symbolics.nim ├── term.nim ├── util.nim └── variable.nim ├── layout.nim ├── lexer.nim ├── lua.nim ├── main.lua ├── main.raz ├── namedcolors.nim ├── nanovg ├── GL │ ├── glext.h │ ├── glxext.h │ └── wglext.h ├── fontstash.h ├── load_glex.c ├── load_glex.h ├── nanovg.c ├── nanovg.h ├── nanovg_gl.h ├── nanovg_gl_utils.h ├── stb_image.h └── stb_truetype.h ├── nimLUA.nim ├── nvg.nim ├── parser.nim ├── razcal.nim ├── razcal.nim.cfg ├── razcontext.nim ├── semcheck.nim ├── types.nim └── utils.nim /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | 19 | nanovg/* linguist-vendored 20 | docs/* linguist-vendored -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.raz 2 | *.lua 3 | *.exe 4 | *.dll 5 | *.so 6 | nimcache 7 | older -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 andri lim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # razcal 2 | 3 | a cross platfrom desktop app framework written in Nim 4 | 5 | --- 6 | 7 | ### A bit history 8 | 9 | If you come here, you should already know something about [electron](https://electron.atom.io/), 10 | a big desktop app framework written in C++. 11 | Perhaps you also know [CEF](https://bitbucket.org/chromiumembedded/cef), electron minus node.js. 12 | 13 | Well, I used them both in the past, and some of my project still depend on earlier version of CEF, 14 | but they grow up immensely, adding much features I don't need at all. 15 | Building the binary myself already a nightmare, removing unneeded features is worse. 16 | 17 | Then I stumbled upon [Layx](https://github.com/layxlang/layx), a layout language written in javascript. 18 | Then I thought, hey, why not we have something like electron/CEF, but lightweight and hackable. 19 | And of course, don't use xml-like whatsoever for the layout, 20 | we already have too much xml-like language to describe GUI. 21 | 22 | That is how razcal idea was born, written in [Nim](https://nim-lang.org/), 23 | using Layx inspired layout language, scripted by [MoonScript](https://MoonScript.org/) 24 | on top of [Lua](https://www.lua.org/) vm. 25 | Currently using [kiwi](https://github.com/yglukhov/kiwi) as it's constraint solver algorithm. 26 | Thanks to Nim powerful metaprogramming feature, binding to Lua is easy, 27 | razcal use [nimLUA](https://github.com/jangko/nimLUA) to automate binding process 28 | interleaved with hand coded glue code. 29 | 30 | You might be tempted to say this is an overly ambitious project. 31 | Using new emerging language like Nim and MoonScript, 32 | plus a home brew layout language, what kind of hybrid mutant razcal will be? 33 | 34 | I will be nice, you can choose what component to be included in your final executable, 35 | you can add more functionality either using Nim, Lua, or MoonScript as a module. 36 | It already and always be easy to build. 37 | 38 | ### Interesting facts 39 | 40 | * The main language to develop razcal is Nim, it has indentation significant syntax. 41 | * MoonScript provides a clean syntax using significant whitespace that avoids all the keyword noise typically seen in a Lua script. 42 | * razcal Layout Language, being inspired by Layx, also has significant whitespace syntax. 43 | 44 | And I'm thinking to use Yaml as configuration script, which is also use indentation based syntax. 45 | Python?, hmm....., the standard Python is too big, perhaps MicroPython is more suitable. 46 | 47 | ### Construction phase 48 | 49 | The [first phase](https://github.com/jangko/razcal/wiki) already done. 50 | Now we can move to the next construction phase, a lot of work to do: 51 | 52 | * Parser 53 | * parse more styling syntax 54 | * text & fonts 55 | * colors & backgrounds 56 | * transformations 57 | * tables 58 | * input widget 59 | * parse conditional constraint 60 | * overflow/scrolling content 61 | * content size constraint 62 | * Semantic pass 63 | * activating more style 64 | * applying functional constraint 65 | * applying conditional constraint 66 | * exposing events to lua 67 | * API 68 | * widget geometry 69 | * anim/hierarchy 70 | * style 71 | * events 72 | * Test 73 | * conditional compilation system 74 | * robust test suite for Nim 75 | * for Lua side 76 | * automated build system 77 | * Docs 78 | * documentation 79 | * tutorial 80 | -------------------------------------------------------------------------------- /ast.nim: -------------------------------------------------------------------------------- 1 | import sets, idents, strutils, kiwi, tables, sets, types, nvg 2 | 3 | type 4 | EasingFN* = proc(origin, destination, t: float): float {.nimcall.} 5 | Interpolator* = proc (origin, destination, current: VarSet, t: float64) {.nimcall.} 6 | PivotFN* = proc(view: View): float64 {.nimcall.} 7 | 8 | Scope* = ref object 9 | depthLevel*: int 10 | symbols*: HashSet[Symbol] 11 | parent*: Scope 12 | 13 | VarSet* = ref object 14 | top*, left*, right*, bottom*: kiwi.Variable 15 | width*, height*: kiwi.Variable 16 | centerX*, centerY*: kiwi.Variable 17 | 18 | PropSet* = ref object 19 | visible*: bool 20 | rotate*: float64 21 | bgColor*, borderColor*: NVGColor 22 | pivotX*, pivotY*: Node # transform origin, calculated on the fly 23 | 24 | View* = ref object 25 | origin*: VarSet 26 | current*: VarSet 27 | views*: Table[Ident, View] # map string to children view 28 | children*: seq[View] # children view 29 | parent*: View # nil if no parent/root 30 | name*: Ident # view's name 31 | idx*: int # index into children position/-1 if invalid 32 | symNode*: Node 33 | node*: Node # view node 34 | dependencies*: HashSet[View] 35 | content*: string 36 | curProp*: PropSet 37 | oriProp*: PropSet 38 | actor*: Actor 39 | 40 | Actor* = ref object 41 | view*: View 42 | classList*: Node 43 | interpolator*: Interpolator 44 | startAni*: float64 45 | duration*: float64 46 | current*: VarSet 47 | destination*: VarSet 48 | easing*: EasingFN 49 | curProp*: PropSet 50 | destProp*: PropSet 51 | 52 | Animation* = ref object of IDobj 53 | duration*: float64 54 | actors*: seq[Actor] 55 | solver*: kiwi.Solver 56 | 57 | ClassContext* = ref object 58 | paramTable*: Table[Ident, Node] # map param name to SymbolNode 59 | n*: Node # Node.nkClass 60 | 61 | SymKind* = enum 62 | skUnknown, skView, skClass, skAlias, skStyle, skParam 63 | skAnimation 64 | 65 | SymFlags* = enum 66 | sfUsed 67 | 68 | Symbol* = ref SymbolObj 69 | SymbolObj* {.acyclic.} = object of IDobj 70 | case kind*: SymKind 71 | of skView: 72 | view*: View 73 | of skClass: 74 | class*: ClassContext 75 | of skParam: 76 | value*: Node # the default value of a param or nil 77 | of skAlias: 78 | alias*: Node 79 | of skAnimation: 80 | anim*: Animation 81 | else: nil 82 | flags*: set[SymFlags] 83 | name*: Ident 84 | pos*: int # param position 85 | lineInfo*: RazLineInfo 86 | 87 | NodeKind* = enum 88 | nkEmpty 89 | nkInfix # opr a b 90 | nkPrefix # opr n 91 | nkPostfix # opr n 92 | 93 | # basic terminal node 94 | nkInt, nkUInt, nkFloat, nkString, nkCharLit, nkIdent 95 | 96 | nkSymbol # act as pointer to other node 97 | 98 | nkCall # f(args) 99 | nkDotCall # x.y 100 | nkAsgn # n '=' expr 101 | nkBracketExpr # n[expr] 102 | 103 | # x.sons[1..n] 104 | nkStmtList, nkClassParams, nkViewClassList 105 | nkEventList, nkPropList, nkFlexList, nkAnimList 106 | nkAliasList 107 | 108 | # constraint's related node 109 | nkChoice # a list of exprs, excluding '|' operator 110 | nkChoiceList # a list of choices, separated by comma ',' 111 | nkConstraint # kiwi.Constraint 112 | nkFlexExpr # kiwi.Expression 113 | nkFlexTerm # kiwi.Term 114 | nkFlexVar # kiwi.Variable 115 | 116 | # class instantiation used by view 117 | nkViewClass, nkArgs 118 | 119 | # a view, a class, a style section 120 | nkView, nkClass, nkStyle 121 | 122 | # an event and a property node inside view 123 | nkEvent, nkProp 124 | 125 | # a list of choices, including {'=','>=','<='} [in]equality 126 | nkFlex # a single constraint 127 | 128 | # view classes startAni endAni interpolator 129 | nkAnim 130 | 131 | # name expr 132 | nkAlias 133 | 134 | Node* = ref NodeObj 135 | NodeObj* {.acyclic.} = object 136 | case kind*: NodeKind 137 | of nkInt: 138 | intVal*: BiggestInt 139 | of nkUInt: 140 | uintVal*: BiggestUInt 141 | of nkFloat: 142 | floatVal*: BiggestFloat 143 | of nkString: 144 | strVal*: string 145 | of nkCharLit: 146 | charLit*: string 147 | of nkIdent: 148 | ident*: Ident 149 | of nkSymbol: 150 | sym*: Symbol 151 | of nkConstraint: 152 | constraint*: kiwi.Constraint 153 | of nkFlexExpr: 154 | expression*: kiwi.Expression 155 | of nkFlexTerm: 156 | term*: kiwi.Term 157 | of nkFlexVar: 158 | variable*: kiwi.Variable 159 | else: 160 | sons*: seq[Node] 161 | lineInfo*: RazLineInfo 162 | 163 | const 164 | NodeWithVal* = {nkInt, nkUInt, nkFloat, nkString, nkCharLit, 165 | nkIdent, nkSymbol, nkConstraint, nkFlexExpr, nkFlexTerm, nkFlexVar} 166 | 167 | NodeWithSons* = {low(NodeKind)..high(NodeKind)} - NodewithVal 168 | 169 | proc newNode*(kind: NodeKind): Node = 170 | new(result) 171 | result.kind = kind 172 | result.lineInfo.line = -1 173 | result.lineInfo.col = -1 174 | result.lineInfo.fileIndex = -1 175 | 176 | proc newIntNode*(intVal: BiggestInt): Node = 177 | result = newNode(nkInt) 178 | result.intVal = intVal 179 | 180 | proc newUIntNode*(uintVal: BiggestUInt): Node = 181 | result = newNode(nkUInt) 182 | result.uintVal = uintVal 183 | 184 | proc newFloatNode*(floatVal: BiggestFloat): Node = 185 | result = newNode(nkFloat) 186 | result.floatVal = floatVal 187 | 188 | proc newStringNode*(strVal: string): Node = 189 | result = newNode(nkString) 190 | result.strVal = strVal 191 | 192 | proc newCharLitNode*(charLit: string): Node = 193 | result = newNode(nkCharLit) 194 | result.charLit = charLit 195 | 196 | proc newIdentNode*(ident: Ident): Node = 197 | result = newNode(nkIdent) 198 | result.ident = ident 199 | 200 | proc newTree*(kind: NodeKind; children: varargs[Node]): Node = 201 | result = newNode(kind) 202 | result.sons = @children 203 | 204 | proc newNodeI*(kind: NodeKind, lineInfo: RazLineInfo): Node = 205 | result = newNode(kind) 206 | result.lineInfo = lineInfo 207 | 208 | proc newSymbolNode*(sym: Symbol): Node = 209 | result = newNode(nkSymbol) 210 | result.sym = sym 211 | result.lineInfo = sym.lineInfo 212 | 213 | proc addSon*(father, son: Node) = 214 | assert son != nil 215 | if isNil(father.sons): father.sons = @[] 216 | add(father.sons, son) 217 | 218 | proc val*(n: Node): string = 219 | case n.kind 220 | of nkInt: result = $n.intVal 221 | of nkUInt: result = $n.uintVal 222 | of nkFloat: result = $n.floatVal 223 | of nkString: result = n.strVal 224 | of nkCharLit: 225 | result = "0x" 226 | for c in n.charLit: 227 | result.add(toHex(ord(c), 2)) 228 | of nkIdent: result = $n.ident 229 | of nkSymbol: result = $n.sym.name 230 | of nkFlexVar: result = n.variable.name & "(" & $n.variable.value & ")" 231 | of nkFlexExpr: result = $n.expression 232 | of nkFlexTerm: result = $n.term 233 | of nkConstraint: result = $n.constraint 234 | else: result = "" 235 | 236 | proc treeRepr*(n: Node, indent = 0): string = 237 | let spaces = repeat(' ', indent) 238 | if n.isNil: return spaces & "nil\n" 239 | let val = n.val 240 | if val.len == 0: 241 | result = "$1$2\n" % [spaces, $n.kind] 242 | else: 243 | result = "$1$2: $3\n" % [spaces, $n.kind, n.val] 244 | if n.kind notin NodeWithVal and not n.sons.isNil: 245 | for s in n.sons: 246 | result.add treeRepr(s, indent + 2) 247 | 248 | proc copyTree*(n: Node): Node = 249 | case n.kind 250 | of nkInt: result = newIntNode(n.intVal) 251 | of nkUInt: result = newUIntNode(n.uintVal) 252 | of nkFloat: result = newFloatNode(n.floatVal) 253 | of nkString: result = newStringNode(n.strVal) 254 | of nkCharLit: result = newCharLitNode(n.charLit) 255 | of nkIdent: result = newIdentNode(n.ident) 256 | of nkSymbol: result = newSymbolNode(n.sym) 257 | of nkConstraint: 258 | result = newNode(nkConstraint) 259 | result.constraint = n.constraint 260 | of nkFlexExpr: 261 | result = newNode(nkFlexExpr) 262 | result.expression = n.expression 263 | of nkFlexTerm: 264 | result = newNode(nkFlexTerm) 265 | result.term = n. term 266 | of nkFlexVar: 267 | result = newNode(nkFlexVar) 268 | result.variable = n.variable 269 | else: 270 | result = newNode(n.kind) 271 | if not n.sons.isNil: 272 | result.sons = newSeq[Node](n.sons.len) 273 | for i in 0.. =", "<=", 63 | 64 | "program", "style", "alias", 65 | "flex", "event", "prop", 66 | 67 | "this", "root", "parent", "child", "prev", "next", 68 | "left", "right", "top", "bottom", 69 | "width", "height", "x", "y", "centerX", "centerY", 70 | 71 | "content", "title", "zindex", "overflow", 72 | "bgColor", "borderColor", "rotate", 73 | "visible", "multiline", "link", "img", "icon", 74 | 75 | "rgb", "rgbf", "rgba", "rgbaf", "hsl", "hsla", 76 | 77 | "click", "contextMenu", "dblClick", "mouseDown", 78 | "mouseEnter", "mouseLeave", "mouseMove", "mouseOver", 79 | "mouseOut", "mouseUp", "wheel", 80 | 81 | "keyDown", "keyPress", "keyUp", 82 | 83 | "abort", "beforeUnload", "error", "hashChange", 84 | "load", "resize", "scroll", "unload", 85 | 86 | "blur", "change", "focus", "focusIn", "focusOut", 87 | "input", "select", 88 | 89 | "drag", "dragEnd", "dragEnter", "dragLeave", 90 | "dragOver", "dragStart", "onDrop", 91 | 92 | "true", "false", "count" 93 | ] 94 | 95 | flexOpr* = {wEquals..wLessOrEqual} 96 | flexRel* = {wThis..wNext} 97 | flexProp* = {wLeft..wCenterY} 98 | flexBinaryTermOp* = {wPlus..wDiv} 99 | flexUnaryTermOp* = {wMinus} 100 | 101 | validEvents* = {wClick..wOnDrop} 102 | validProps* = {wContent..wIcon} 103 | -------------------------------------------------------------------------------- /kiwi.nim: -------------------------------------------------------------------------------- 1 | import kiwi.variable 2 | import kiwi.constraint 3 | import kiwi.term 4 | import kiwi.expression 5 | import kiwi.symbolics 6 | import kiwi.solver 7 | import kiwi.strength 8 | 9 | export solver, variable, constraint, symbolics, expression, term, strength 10 | -------------------------------------------------------------------------------- /kiwi/constraint.nim: -------------------------------------------------------------------------------- 1 | import tables 2 | 3 | import expression, relational_operator, strength, variable, util, term 4 | 5 | type Constraint* = ref object 6 | expression*: Expression 7 | strength*: float 8 | op*: RelationalOperator 9 | 10 | proc reduce(e: Expression): Expression = 11 | var vars = initTable[Variable, float]() 12 | for term in e.terms: 13 | let v = term.variable 14 | var value = vars.getOrDefault(v) 15 | value += term.coefficient 16 | vars[v] = value 17 | 18 | var reducedTerms = newSeq[Term]() 19 | for variable, coef in vars: 20 | reducedTerms.add(newTerm(variable, coef)) 21 | 22 | result = newExpression(reducedTerms, e.constant) 23 | 24 | proc newConstraint*(e: Expression, op: RelationalOperator, strength: float): Constraint = 25 | result.new() 26 | result.expression = reduce(e) 27 | result.op = op 28 | result.strength = clipStrength(strength) 29 | 30 | proc newConstraint*(e: Expression, op: RelationalOperator): Constraint = 31 | newConstraint(e, op, REQUIRED) 32 | 33 | proc newConstraint*(other: Constraint, strength: float): Constraint = 34 | newConstraint(other.expression, other.op, strength) 35 | 36 | proc `$`*(c: Constraint): string = 37 | "expression: (" & $c.expression & ") strength: " & $c.strength & " operator: " & $c.op 38 | -------------------------------------------------------------------------------- /kiwi/expression.nim: -------------------------------------------------------------------------------- 1 | import term 2 | 3 | type Expression* = ref object 4 | terms*: seq[Term] 5 | constant*: float 6 | 7 | proc newExpression*(constant: float = 0): Expression = 8 | result.new() 9 | result.terms = newSeq[Term]() 10 | result.constant = constant 11 | 12 | proc newExpression*(term: Term, constant: float = 0): Expression = 13 | result.new() 14 | result.terms = @[term] 15 | result.constant = constant 16 | 17 | proc newExpression*(terms: seq[Term], constant: float = 0): Expression = 18 | result.new() 19 | result.terms = terms 20 | result.constant = constant 21 | 22 | proc value*(e: Expression): float = 23 | result = e.constant 24 | for t in e.terms: 25 | result += t.value 26 | 27 | proc isConstant*(e: Expression): bool = e.terms.len == 0 28 | 29 | proc `$`*(e: Expression): string = 30 | result = "isConstant: " & $e.isConstant & " constant: " & $e.constant 31 | if not e.isConstant: 32 | result &= " terms: [" 33 | for term in e.terms: 34 | result &= "(" 35 | result &= $term 36 | result &= ")" 37 | result &= "]" 38 | -------------------------------------------------------------------------------- /kiwi/relational_operator.nim: -------------------------------------------------------------------------------- 1 | 2 | type RelationalOperator* = enum 3 | OP_LE 4 | OP_GE 5 | OP_EQ 6 | -------------------------------------------------------------------------------- /kiwi/row.nim: -------------------------------------------------------------------------------- 1 | import tables 2 | 3 | import symbol, util 4 | 5 | type Row* = ref object 6 | constant*: float 7 | cells*: Table[Symbol, float] 8 | 9 | proc newRow*(constant: float = 0): Row = 10 | result.new() 11 | result.cells = initTable[Symbol, float]() 12 | result.constant = constant 13 | 14 | proc newRow*(other: Row): Row = 15 | result.new() 16 | result.cells = other.cells 17 | result.constant = other.constant 18 | 19 | proc add*(r: Row, value: float): float = 20 | ## Add a constant value to the row constant. 21 | r.constant += value 22 | result = r.constant 23 | 24 | proc insert*(r: Row, symbol: Symbol, coefficient: float = 1.0) = 25 | ## Insert a symbol into the row with a given coefficient. 26 | ## 27 | ## If the symbol already exists in the row, the coefficient will be 28 | ## added to the existing coefficient. If the resulting coefficient 29 | ## is zero, the symbol will be removed from the row 30 | 31 | let coefficient = coefficient + r.cells.getOrDefault(symbol) 32 | if nearZero(coefficient): 33 | r.cells.del(symbol) 34 | else: 35 | r.cells[symbol] = coefficient 36 | 37 | proc insert*(r, other: Row, coefficient: float = 1.0) = 38 | ## Insert a row into this row with a given coefficient. 39 | ## The constant and the cells of the other row will be multiplied by 40 | ## the coefficient and added to this row. Any cell with a resulting 41 | ## coefficient of zero will be removed from the row. 42 | 43 | r.constant += other.constant * coefficient 44 | 45 | for s, v in other.cells: 46 | let coeff = v * coefficient 47 | 48 | # r.insert(s, coeff) this line looks different than the c++ 49 | 50 | # changes start here 51 | let temp = r.cells.getOrDefault(s) + coeff; 52 | if nearZero(temp): 53 | r.cells.del(s) 54 | else: 55 | r.cells[s] = temp 56 | 57 | proc remove*(r: Row, symbol: Symbol) = 58 | ## Remove the given symbol from the row. 59 | r.cells.del(symbol) 60 | 61 | proc reverseSign*(r: Row) = 62 | ## Reverse the sign of the constant and all cells in the row. 63 | r.constant = -r.constant 64 | for s, v in r.cells: 65 | r.cells[s] = -v 66 | 67 | proc solveFor*(r: Row, symbol: Symbol) = 68 | ## Solve the row for the given symbol. 69 | ## 70 | ## This method assumes the row is of the form a * x + b * y + c = 0 71 | ## and (assuming solve for x) will modify the row to represent the 72 | ## right hand side of x = -b/a * y - c / a. The target symbol will 73 | ## be removed from the row, and the constant and other cells will 74 | ## be multiplied by the negative inverse of the target coefficient. 75 | ## The given symbol *must* exist in the row. 76 | 77 | let coeff = -1.0 / r.cells[symbol] 78 | r.cells.del(symbol) 79 | r.constant *= coeff 80 | 81 | for s, v in r.cells: 82 | r.cells[s] = v * coeff 83 | 84 | proc solveFor*(r: Row, lhs, rhs: Symbol) = 85 | ## Solve the row for the given symbols. 86 | ## 87 | ## This method assumes the row is of the form x = b * y + c and will 88 | ## solve the row such that y = x / b - c / b. The rhs symbol will be 89 | ## removed from the row, the lhs added, and the result divided by the 90 | ## negative inverse of the rhs coefficient. 91 | ## The lhs symbol *must not* exist in the row, and the rhs symbol 92 | ## must* exist in the row. 93 | 94 | r.insert(lhs, -1.0) 95 | r.solveFor(rhs) 96 | 97 | proc coefficientFor*(r: Row, symbol: Symbol): float = 98 | ## Get the coefficient for the given symbol. 99 | ## 100 | ## If the symbol does not exist in the row, zero will be returned. 101 | r.cells.getOrDefault(symbol) 102 | 103 | proc substitute*(r: Row, symbol: Symbol, row: Row) = 104 | ## Substitute a symbol with the data from another row. 105 | ## 106 | ## Given a row of the form a * x + b and a substitution of the 107 | ## form x = 3 * y + c the row will be updated to reflect the 108 | ## expression 3 * a * y + a * c + b. 109 | ## If the symbol does not exist in the row, this is a no-op. 110 | if symbol in r.cells: 111 | let coefficient = r.cells[symbol] 112 | r.cells.del(symbol) 113 | r.insert(row, coefficient) 114 | -------------------------------------------------------------------------------- /kiwi/solver.nim: -------------------------------------------------------------------------------- 1 | import tables 2 | import symbol, constraint, variable, row, util, strength, expression, term, relational_operator 3 | 4 | type 5 | Solver* = ref object 6 | cns: Table[Constraint, Tag] 7 | rows: Table[Symbol, Row] 8 | vars: Table[Variable, Symbol] 9 | edits: Table[Variable, EditInfo] 10 | infeasibleRows: seq[Symbol] 11 | objective: Row 12 | artificial: Row 13 | 14 | Tag = ref object 15 | marker: Symbol 16 | other: Symbol 17 | 18 | EditInfo = ref object 19 | tag: Tag 20 | constraint: Constraint 21 | constant: float 22 | 23 | DuplicateConstraintException = object of Exception 24 | UnsatisfiableConstraintException* = object of Exception 25 | UnknownConstraintException = object of Exception 26 | InternalSolverError = object of Exception 27 | DuplicateEditVariableException = object of Exception 28 | RequiredFailureException = object of Exception 29 | UnknownEditVariableException = object of Exception 30 | 31 | proc newTag(): Tag = 32 | result.new() 33 | result.marker = newSymbol() 34 | result.other = newSymbol() 35 | 36 | proc newEditInfo(constraint: Constraint, tag: Tag, constant: float): EditInfo = 37 | result.new() 38 | result.tag = tag 39 | result.constraint = constraint 40 | result.constant = constant 41 | 42 | proc newSolver*(): Solver = 43 | result.new() 44 | result.cns = initTable[Constraint, Tag]() 45 | result.rows = initTable[Symbol, Row]() 46 | result.vars = initTable[Variable, Symbol]() 47 | result.edits = initTable[Variable, EditInfo]() 48 | result.infeasibleRows = @[] 49 | result.objective = newRow() 50 | 51 | proc createRow(s: Solver, constraint: Constraint, tag: Tag): Row 52 | proc chooseSubject(row: Row, tag: Tag): Symbol 53 | proc addWithArtificialVariable(s: Solver, row: Row): bool 54 | proc substitute(s: Solver, symbol: Symbol, row: Row) 55 | proc optimize(s: Solver, objective: Row) 56 | 57 | proc allDummies(row: Row): bool = 58 | ## Test whether a row is composed of all dummy variables. 59 | for k, v in row.cells: 60 | if k.kind != DUMMY: 61 | return false 62 | return true 63 | 64 | proc addConstraint*(s: Solver, constraint: Constraint) = 65 | if constraint in s.cns: 66 | raise newException(DuplicateConstraintException, "") 67 | 68 | let tag = newTag() 69 | let row = s.createRow(constraint, tag) 70 | var subject = chooseSubject(row, tag) 71 | 72 | if subject.kind == INVALID and allDummies(row): 73 | if not nearZero(row.constant): 74 | raise newException(UnsatisfiableConstraintException, "") 75 | else: 76 | subject = tag.marker; 77 | 78 | if subject.kind == INVALID: 79 | if not s.addWithArtificialVariable(row): 80 | raise newException(UnsatisfiableConstraintException, "") 81 | else: 82 | row.solveFor(subject) 83 | s.substitute(subject, row) 84 | s.rows[subject] = row 85 | 86 | s.cns[constraint] = tag 87 | 88 | s.optimize(s.objective) 89 | 90 | proc removeMarkerEffects(s: Solver, marker: Symbol, strength: float) = 91 | let row = s.rows.getOrDefault(marker) 92 | if row.isNil: 93 | s.objective.insert(marker, -strength); 94 | else: 95 | s.objective.insert(row, -strength); 96 | 97 | proc removeConstraintEffects(s: Solver, constraint: Constraint, tag: Tag) = 98 | if tag.marker.kind == ERROR: 99 | s.removeMarkerEffects(tag.marker, constraint.strength) 100 | elif tag.other.kind == ERROR: 101 | s.removeMarkerEffects(tag.other, constraint.strength) 102 | 103 | proc getMarkerLeavingRow(solver: Solver, marker: Symbol): Row = 104 | var r1 = Inf 105 | var r2 = Inf 106 | 107 | var first, second, third: Row 108 | for s, candidateRow in solver.rows: 109 | let c = candidateRow.coefficientFor(marker) 110 | if c == 0.0: 111 | continue; 112 | 113 | if s.kind == EXTERNAL: 114 | third = candidateRow 115 | elif c < 0: 116 | let r = - candidateRow.constant / c 117 | if r < r1: 118 | r1 = r 119 | first = candidateRow 120 | else: 121 | let r = candidateRow.constant / c; 122 | if r < r2: 123 | r2 = r 124 | second = candidateRow 125 | 126 | if first != nil: return first 127 | if second != nil: return second 128 | return third; 129 | 130 | proc removeConstraint*(s: Solver, constraint: Constraint) = 131 | let tag = s.cns.getOrDefault(constraint) 132 | if tag.isNil: 133 | raise newException(UnknownConstraintException, "") 134 | 135 | s.cns.del(constraint) 136 | s.removeConstraintEffects(constraint, tag) 137 | 138 | var row = s.rows.getOrDefault(tag.marker) 139 | if not row.isNil: 140 | s.rows.del(tag.marker) 141 | else: 142 | row = s.getMarkerLeavingRow(tag.marker) 143 | if row.isNil: 144 | raise newException(InternalSolverError, "internal solver error") 145 | 146 | #This looks wrong! changes made below 147 | #Symbol leaving = tag.marker; 148 | #rows.remove(tag.marker); 149 | 150 | var leaving: Symbol 151 | for sym, v in s.rows: 152 | if v == row: 153 | leaving = sym 154 | 155 | if leaving.isNil: 156 | raise newException(InternalSolverError, "internal solver error") 157 | 158 | s.rows.del(leaving) 159 | row.solveFor(leaving, tag.marker) 160 | s.substitute(tag.marker, row) 161 | 162 | s.optimize(s.objective) 163 | 164 | proc hasConstraint*(s: Solver, constraint: Constraint): bool = 165 | constraint in s.cns 166 | 167 | proc addEditVariable*(s: Solver, variable: Variable, strength: float) = 168 | if variable in s.edits: 169 | raise newException(DuplicateEditVariableException, "") 170 | 171 | let strength = clipStrength(strength) 172 | 173 | if strength == REQUIRED: 174 | raise newException(RequiredFailureException, "") 175 | 176 | let constraint = newConstraint(newExpression(newTerm(variable)), OP_EQ, strength) 177 | 178 | try: 179 | s.addConstraint(constraint) 180 | except: 181 | echo getCurrentException().getStackTrace() 182 | 183 | let info = newEditInfo(constraint, s.cns.getOrDefault(constraint), 0.0); 184 | s.edits[variable] = info 185 | 186 | proc removeEditVariable*(s: Solver, variable: Variable) = 187 | let edit = s.edits.getOrDefault(variable) 188 | if edit.isNil: 189 | raise newException(UnknownEditVariableException, "") 190 | 191 | try: 192 | s.removeConstraint(edit.constraint) 193 | except UnknownConstraintException: 194 | echo getCurrentException().getStackTrace() 195 | 196 | s.edits.del(variable) 197 | 198 | proc removeAllEditVariables*(s: Solver) = 199 | for k, edit in s.edits: 200 | try: 201 | s.removeConstraint(edit.constraint) 202 | except UnknownConstraintException: 203 | echo getCurrentException().getStackTrace() 204 | s.edits.clear() 205 | 206 | proc hasEditVariable*(s: Solver, variable: Variable): bool = 207 | variable in s.edits 208 | 209 | proc getDualEnteringSymbol(solver: Solver, row: Row): Symbol = 210 | var ratio = Inf 211 | for s, currentCell in row.cells: 212 | if s.kind != DUMMY: 213 | if currentCell > 0: 214 | let coefficient = solver.objective.coefficientFor(s) 215 | let r = coefficient / currentCell 216 | if r < ratio: 217 | ratio = r 218 | result = s 219 | if result.isNil: 220 | result = newSymbol() 221 | 222 | proc dualOptimize(s: Solver) = 223 | while s.infeasibleRows.len > 0: 224 | let leaving = s.infeasibleRows.pop() 225 | let row = s.rows.getOrDefault(leaving) 226 | if row != nil and row.constant < 0: 227 | let entering = s.getDualEnteringSymbol(row) 228 | if entering.kind == INVALID: 229 | raise newException(InternalSolverError, "internal solver error") 230 | 231 | s.rows.del(leaving) 232 | row.solveFor(leaving, entering) 233 | s.substitute(entering, row); 234 | s.rows[entering] = row 235 | 236 | proc suggestValue*(s: Solver, variable: Variable, value: float) = 237 | let info = s.edits.getOrDefault(variable) 238 | if info.isNil: 239 | raise newException(UnknownEditVariableException, "") 240 | 241 | let delta = value - info.constant 242 | info.constant = value 243 | 244 | var row = s.rows.getOrDefault(info.tag.marker) 245 | if row != nil: 246 | if row.add(-delta) < 0: 247 | s.infeasibleRows.add(info.tag.marker) 248 | s.dualOptimize() 249 | return 250 | 251 | row = s.rows.getOrDefault(info.tag.other); 252 | if row != nil: 253 | if row.add(delta) < 0: 254 | s.infeasibleRows.add(info.tag.other); 255 | s.dualOptimize() 256 | return 257 | 258 | for sym, currentRow in s.rows: 259 | let coefficient = currentRow.coefficientFor(info.tag.marker) 260 | if coefficient != 0 and currentRow.add(delta * coefficient) < 0 and sym.kind != EXTERNAL: 261 | s.infeasibleRows.add(sym) 262 | 263 | s.dualOptimize() 264 | 265 | proc updateVariables*(s: Solver) = 266 | ## Update the values of the external solver variables. 267 | for variable, sym in s.vars: 268 | let row = s.rows.getOrDefault(sym) 269 | if row.isNil: 270 | variable.value = 0 271 | else: 272 | variable.value = row.constant 273 | 274 | proc getVarSymbol(s: Solver, variable: Variable): Symbol = 275 | ## Get the symbol for the given variable. 276 | ## 277 | ## If a symbol does not exist for the variable, one will be created. 278 | if variable in s.vars: 279 | result = s.vars.getOrDefault(variable) 280 | else: 281 | result = newSymbol(EXTERNAL) 282 | s.vars[variable] = result 283 | 284 | proc createRow(s: Solver, constraint: Constraint, tag: Tag): Row = 285 | ## Create a new Row object for the given constraint. 286 | ## 287 | ## The terms in the constraint will be converted to cells in the row. 288 | ## Any term in the constraint with a coefficient of zero is ignored. 289 | ## This method uses the `getVarSymbol` method to get the symbol for 290 | ## the variables added to the row. If the symbol for a given cell 291 | ## variable is basic, the cell variable will be substituted with the 292 | ## basic row. 293 | ## 294 | ## The necessary slack and error variables will be added to the row. 295 | ## If the constant for the row is negative, the sign for the row 296 | ## will be inverted so the constant becomes positive. 297 | ## 298 | ## The tag will be updated with the marker and error symbols to use 299 | ## for tracking the movement of the constraint in the tableau. 300 | let expression = constraint.expression 301 | let row = newRow(expression.constant) 302 | 303 | for term in expression.terms: 304 | if not nearZero(term.coefficient): 305 | let symbol = s.getVarSymbol(term.variable) 306 | 307 | let otherRow = s.rows.getOrDefault(symbol) 308 | 309 | if otherRow.isNil: 310 | row.insert(symbol, term.coefficient); 311 | else: 312 | row.insert(otherRow, term.coefficient); 313 | 314 | case constraint.op 315 | of OP_LE, OP_GE: 316 | let coeff = if constraint.op == OP_LE: 1.0 else: -1.0 317 | let slack = newSymbol(SLACK); 318 | tag.marker = slack 319 | row.insert(slack, coeff) 320 | if constraint.strength < REQUIRED: 321 | let error = newSymbol(ERROR) 322 | tag.other = error; 323 | row.insert(error, -coeff); 324 | s.objective.insert(error, constraint.strength) 325 | of OP_EQ: 326 | if constraint.strength < REQUIRED: 327 | let errplus = newSymbol(ERROR) 328 | let errminus = newSymbol(ERROR) 329 | tag.marker = errplus 330 | tag.other = errminus 331 | row.insert(errplus, -1.0) # v = eplus - eminus 332 | row.insert(errminus, 1.0) # v - eplus + eminus = 0 333 | s.objective.insert(errplus, constraint.strength) 334 | s.objective.insert(errminus, constraint.strength) 335 | else: 336 | let dummy = newSymbol(DUMMY) 337 | tag.marker = dummy 338 | row.insert(dummy) 339 | 340 | # Ensure the row as a positive constant. 341 | if row.constant < 0: 342 | row.reverseSign() 343 | 344 | result = row 345 | 346 | proc chooseSubject(row: Row, tag: Tag): Symbol = 347 | ## Choose the subject for solving for the row 348 | ##

349 | ## This method will choose the best subject for using as the solve 350 | ## target for the row. An invalid symbol will be returned if there 351 | ## is no valid target. 352 | ## The symbols are chosen according to the following precedence: 353 | ## 1) The first symbol representing an external variable. 354 | ## 2) A negative slack or error tag variable. 355 | ## If a subject cannot be found, an invalid symbol will be returned. 356 | 357 | for s in row.cells.keys: 358 | if s.kind == EXTERNAL: 359 | return s 360 | 361 | if tag.marker.kind == SLACK or tag.marker.kind == ERROR: 362 | if row.coefficientFor(tag.marker) < 0: 363 | return tag.marker 364 | 365 | if tag.other != nil and (tag.other.kind == SLACK or tag.other.kind == ERROR): 366 | if row.coefficientFor(tag.other) < 0: 367 | return tag.other 368 | 369 | result = newSymbol() 370 | 371 | proc anyPivotableSymbol(s: Solver, row: Row): Symbol = 372 | ## Get the first Slack or Error symbol in the row. 373 | ## 374 | ## If no such symbol is present, and Invalid symbol will be returned. 375 | for k, v in row.cells: 376 | if k.kind == SLACK or k.kind == ERROR: 377 | result = k 378 | 379 | if result.isNil: 380 | result = newSymbol() 381 | 382 | proc addWithArtificialVariable(s: Solver, row: Row): bool = 383 | ## Add the row to the tableau using an artificial variable. 384 | ## 385 | ## This will return false if the constraint cannot be satisfied. 386 | 387 | #TODO check this 388 | 389 | # Create and add the artificial variable to the tableau 390 | 391 | let art = newSymbol(SLACK) 392 | s.rows[art] = newRow(row) 393 | 394 | s.artificial = newRow(row) 395 | 396 | # Optimize the artificial objective. This is successful 397 | # only if the artificial objective is optimized to zero. 398 | s.optimize(s.artificial) 399 | let success = nearZero(s.artificial.constant) 400 | s.artificial = nil 401 | 402 | # If the artificial variable is basic, pivot the row so that 403 | # it becomes basic. If the row is constant, exit early. 404 | 405 | let rowptr = s.rows.getOrDefault(art) 406 | 407 | if rowptr != nil: 408 | #/**this looks wrong!!!*/ 409 | #rows.remove(rowptr); 410 | 411 | var deleteQueue = newSeq[Symbol]() 412 | for sym, v in s.rows: 413 | if v == rowptr: 414 | deleteQueue.add(sym) 415 | for sym in deleteQueue: 416 | s.rows.del(sym) 417 | 418 | if rowptr.cells.len == 0: 419 | return success 420 | 421 | let entering = s.anyPivotableSymbol(rowptr) 422 | if entering.kind == INVALID: 423 | return false # unsatisfiable (will this ever happen?) 424 | 425 | rowptr.solveFor(art, entering) 426 | s.substitute(entering, rowptr) 427 | s.rows[entering] = rowptr 428 | 429 | # Remove the artificial variable from the tableau. 430 | for v in s.rows.values: 431 | v.remove(art) 432 | 433 | s.objective.remove(art) 434 | 435 | return success 436 | 437 | proc substitute(s: Solver, symbol: Symbol, row: Row) = 438 | ## Substitute the parametric symbol with the given row. 439 | ## 440 | ## This method will substitute all instances of the parametric symbol 441 | ## in the tableau and the objective function with the given row. 442 | for sym, r in s.rows: 443 | r.substitute(symbol, row) 444 | if sym.kind != EXTERNAL and r.constant < 0: 445 | s.infeasibleRows.add(sym) 446 | 447 | s.objective.substitute(symbol, row) 448 | 449 | if s.artificial != nil: 450 | s.artificial.substitute(symbol, row) 451 | 452 | proc getEnteringSymbol(objective: Row): Symbol = 453 | ## Compute the entering variable for a pivot operation. 454 | ## 455 | ## This method will return first symbol in the objective function which 456 | ## is non-dummy and has a coefficient less than zero. If no symbol meets 457 | ## the criteria, it means the objective function is at a minimum, and an 458 | ## invalid symbol is returned. 459 | 460 | for k, v in objective.cells: 461 | if k.kind != DUMMY and v < 0: 462 | return k 463 | return newSymbol() 464 | 465 | proc getLeavingRow(s: Solver, entering: Symbol): Row = 466 | ## Compute the row which holds the exit symbol for a pivot. 467 | ## 468 | ## This documentation is copied from the C++ version and is outdated 469 | ## 470 | ## 471 | ## This method will return an iterator to the row in the row map 472 | ## which holds the exit symbol. If no appropriate exit symbol is 473 | ## found, the end() iterator will be returned. This indicates that 474 | ## the objective function is unbounded. 475 | var ratio = Inf 476 | for key, candidateRow in s.rows: 477 | if key.kind != EXTERNAL: 478 | let temp = candidateRow.coefficientFor(entering) 479 | if temp < 0: 480 | let temp_ratio = (-candidateRow.constant / temp) 481 | if temp_ratio < ratio: 482 | ratio = temp_ratio 483 | result = candidateRow 484 | 485 | proc optimize(s: Solver, objective: Row) = 486 | ## Optimize the system for the given objective function. 487 | ## 488 | ## This method performs iterations of Phase 2 of the simplex method 489 | ## until the objective function reaches a minimum. 490 | while true: 491 | let entering = getEnteringSymbol(objective); 492 | if entering.kind == INVALID: 493 | return 494 | 495 | let entry = s.getLeavingRow(entering) 496 | if entry.isNil: 497 | raise newException(InternalSolverError, "The objective is unbounded.") 498 | 499 | var entryKey: Symbol 500 | for key, v in s.rows: 501 | if v == entry: 502 | entryKey = key 503 | 504 | s.rows.del(entryKey) 505 | entry.solveFor(entryKey, entering); 506 | s.substitute(entering, entry) 507 | s.rows[entering] = entry 508 | -------------------------------------------------------------------------------- /kiwi/strength.nim: -------------------------------------------------------------------------------- 1 | proc createStrength*(a: float; b: float, c: float, w: float = 1.0): float = 2 | result += max(0.0, min(1000.0, a * w)) * 1000000.0; 3 | result += max(0.0, min(1000.0, b * w)) * 1000.0; 4 | result += max(0.0, min(1000.0, c * w)); 5 | 6 | const REQUIRED* = createStrength(1000.0, 1000.0, 1000.0) 7 | const STRONG* = createStrength(1.0, 0.0, 0.0) 8 | const MEDIUM* = createStrength(0.0, 1.0, 0.0) 9 | const WEAK* = createStrength(0.0, 0.0, 1.0) 10 | 11 | proc clipStrength*(value: float): float = 12 | max(0.0, min(REQUIRED, value)) 13 | -------------------------------------------------------------------------------- /kiwi/symbol.nim: -------------------------------------------------------------------------------- 1 | type 2 | SymbolKind* = enum 3 | INVALID, 4 | EXTERNAL, 5 | SLACK, 6 | ERROR, 7 | DUMMY 8 | 9 | Symbol* = ref object 10 | mKind: SymbolKind 11 | 12 | proc newSymbol*(kind: SymbolKind = INVALID): Symbol = 13 | result.new() 14 | result.mKind = kind 15 | 16 | proc kind*(s: Symbol): SymbolKind = s.mKind 17 | -------------------------------------------------------------------------------- /kiwi/symbolics.nim: -------------------------------------------------------------------------------- 1 | import term, variable, expression, constraint, relational_operator 2 | 3 | # Variable multiply, divide, and unary invert 4 | template `*`*(variable: Variable, coefficient: float): Term = newTerm(variable, coefficient) 5 | template `/`*(variable: Variable, denominator: float): Term = variable * (1 / denominator) 6 | template `-`*(variable: Variable): Term = variable * (-1) 7 | 8 | # Term multiply, divide, and unary invert 9 | proc `*`*(term: Term, coefficient: float): Term = term.variable * (term.coefficient * coefficient) 10 | template `/`*(term: Term, denominator: float): Term = term * (1 / denominator) 11 | template `-`*(term: Term): Term = term * (-1) 12 | 13 | # Expression multiply, divide, and unary invert 14 | proc `*`*(expression: Expression, coefficient: float): Expression = 15 | var terms = newSeqOfCap[Term](expression.terms.len) 16 | for term in expression.terms: 17 | terms.add(term * coefficient) 18 | 19 | # TODO Do we need to make a copy of the term objects in the array? 20 | newExpression(terms, expression.constant * coefficient) 21 | 22 | type NonlinearExpressionException* = object of Exception 23 | 24 | proc `*`*(expression1, expression2: Expression): Expression = 25 | if expression1.isConstant: 26 | return expression2 * expression1.constant 27 | elif expression2.isConstant: 28 | return expression1 * expression2.constant 29 | else: 30 | raise newException(NonlinearExpressionException, "") 31 | 32 | template `/`*(expression: Expression, denominator: float): Expression = expression * (1 / denominator) 33 | 34 | proc `/`*(expression1, expression2: Expression): Expression = 35 | if expression2.isConstant: 36 | return expression1 / expression2.constant 37 | else: 38 | raise newException(NonlinearExpressionException, "") 39 | 40 | template `-`*(expression: Expression): Expression = expression * (-1) 41 | 42 | # Double multiply 43 | proc `*`*(lhs: float, rhs: Expression): Expression = rhs * lhs 44 | proc `*`*(lhs: float, rhs: Term | Variable): Term = rhs * lhs 45 | 46 | # Expression add and subtract 47 | proc `+`*(first, second: Expression): Expression = 48 | #TODO do we need to copy term objects? 49 | newExpression(first.terms & second.terms, first.constant + second.constant) 50 | 51 | proc `+`*(first: Expression , second: Term): Expression = 52 | #TODO do we need to copy term objects? 53 | newExpression(first.terms & second, first.constant) 54 | 55 | template `+`*(expression: Expression, variable: Variable): Expression = expression + newTerm(variable) 56 | 57 | proc `+`*(expression: Expression, constant: float): Expression = 58 | newExpression(expression.terms, expression.constant + constant) 59 | 60 | template `-`*(lhs: Expression, rhs: Expression | Term | Variable | float): Expression = lhs + (-rhs) 61 | 62 | # Term add and subtract 63 | template `+`*(term: Term, expression: Expression): Expression = expression + term 64 | proc `+`*(first, second: Term): Expression = newExpression(@[first, second]) 65 | template `+`*(term: Term, variable: Variable): Expression = term + newTerm(variable) 66 | template `+`*(term: Term, constant: float): Expression = newExpression(term, constant) 67 | 68 | template `-`*(lhs: Term, rhs: Expression | Term | Variable | float): Expression = lhs + (-rhs) 69 | 70 | # Variable add and subtract 71 | template `+`*(variable: Variable, expression: Expression): Expression = expression + variable 72 | template `+`*(variable: Variable, term: Term): Expression = term + variable 73 | template `+`*(first, second: Variable): Expression = newTerm(first) + second 74 | template `+`*(variable: Variable, constant: float): Expression = newTerm(variable) + constant 75 | 76 | template `-`*(lhs: Variable, rhs: Expression | Term | Variable): Expression = lhs + (-rhs) 77 | template `-`*(lhs: Variable, rhs: float): Expression = lhs + (-rhs) 78 | 79 | # Double add and subtract 80 | 81 | proc `+`*(lhs: float, rhs: Expression | Term | Variable): Expression = rhs + lhs 82 | proc `-`*(lhs: float, rhs: Expression | Term | Variable): Expression = -rhs + lhs 83 | 84 | # Expression relations 85 | template `==`*(first, second: Expression): Constraint = newConstraint(first - second, OP_EQ) 86 | template `==`*(expression: Expression, term: Term): Constraint = expression == newExpression(term) 87 | template `==`*(expression: Expression, variable: Variable): Constraint = expression == newTerm(variable) 88 | template `==`*(expression: Expression, constant: float): Constraint = expression == newExpression(constant) 89 | 90 | template `<=`*(first, second: Expression): Constraint = newConstraint(first - second, OP_LE) 91 | template `<=`*(expression: Expression, term: Term): Constraint = expression <= newExpression(term) 92 | template `<=`*(expression: Expression, variable: Variable): Constraint = expression <= newTerm(variable) 93 | template `<=`*(expression: Expression, constant: float): Constraint = expression <= newExpression(constant) 94 | 95 | template `>=`*(first, second: Expression): Constraint = newConstraint(first - second, OP_GE) 96 | template `>=`*(expression: Expression, term: Term): Constraint = expression >= newExpression(term) 97 | template `>=`*(expression: Expression, variable: Variable): Constraint = expression >= newTerm(variable) 98 | template `>=`*(expression: Expression, constant: float): Constraint = expression >= newExpression(constant) 99 | 100 | # Term relations 101 | template `==`*(lhs: Term, rhs: Expression): Constraint = rhs == lhs 102 | template `==`*(lhs: Term, rhs: Term | Variable | float): Constraint = newExpression(lhs) == rhs 103 | 104 | template `<=`*(lhs: Term, rhs: Expression | Term | Variable | float): Constraint = newExpression(lhs) <= rhs 105 | template `>=`*(lhs: Term, rhs: Expression | Term | Variable | float): Constraint = newExpression(lhs) >= rhs 106 | 107 | # Variable relations 108 | template `==`*(variable: Variable, expression: Expression): Constraint = expression == variable 109 | template `==`*(variable: Variable, term: Term): Constraint = term == variable 110 | template `==`*(first, second: Variable): Constraint = newTerm(first) == second 111 | proc `==`*(variable: Variable, constant: float): Constraint = newTerm(variable) == constant 112 | 113 | template `<=`*(lhs: Variable, rhs: Expression | Term | Variable | float): Constraint = newTerm(lhs) <= rhs 114 | 115 | template `>=`*(variable: Variable, expression: Expression): Constraint = newTerm(variable) >= expression 116 | template `>=`*(variable: Variable, term: Term): Constraint = term >= variable 117 | template `>=`*(first, second: Variable): Constraint = newTerm(first) >= second 118 | proc `>=`*(variable: Variable, constant: float): Constraint = newTerm(variable) >= constant 119 | 120 | # Double relations 121 | proc `==`*(lhs: float, rhs: Expression | Term | Variable): Constraint = rhs == lhs 122 | 123 | proc `<=`*(constant: float, expression: Expression): Constraint = newExpression(constant) <= expression 124 | proc `<=`*(constant: float, term: Term): Constraint = constant <= newExpression(term) 125 | proc `<=`*(constant: float, variable: Variable): Constraint = constant <= newTerm(variable) 126 | 127 | proc `>=`*(constant: float, term: Term): Constraint = newExpression(constant) >= term 128 | proc `>=`*(constant: float, variable: Variable): Constraint = constant >= newTerm(variable) 129 | 130 | # Constraint strength modifier 131 | proc modifyStrength*(constraint: Constraint, strength: float): Constraint = 132 | newConstraint(constraint, strength) 133 | 134 | proc modifyStrength*(strength: float, constraint: Constraint): Constraint = 135 | modifyStrength(strength, constraint) 136 | 137 | -------------------------------------------------------------------------------- /kiwi/term.nim: -------------------------------------------------------------------------------- 1 | import variable 2 | 3 | type Term* = ref object 4 | variable*: Variable 5 | coefficient*: float 6 | 7 | proc newTerm*(variable: Variable, coefficient: float = 1): Term = 8 | result.new() 9 | result.variable = variable 10 | result.coefficient = coefficient 11 | 12 | proc value*(t: Term): float = 13 | t.coefficient * t.variable.value 14 | 15 | proc `$`*(t: Term): string = 16 | "variable: (" & $t.variable & ") coefficient: " & $t.coefficient 17 | -------------------------------------------------------------------------------- /kiwi/util.nim: -------------------------------------------------------------------------------- 1 | import hashes 2 | 3 | const EPS = 1.0e-8; 4 | 5 | proc nearZero*(value: float): bool = 6 | if value < 0: 7 | -value < EPS 8 | else: 9 | value < EPS 10 | 11 | when defined(js): 12 | var idCounter = 0 13 | proc hash*(o: ref object): Hash = 14 | var id = 0 15 | var cnt = idCounter 16 | {.emit: """ 17 | if (`o`.__kiwi_objid === undefined) { 18 | `o`.__kiwi_objid = ++`cnt`; 19 | } 20 | `id` = `o`.__kiwi_objid; 21 | """.} 22 | idCounter = cnt 23 | hash(id) 24 | else: 25 | proc hash*(o: ref object): Hash {.inline.} = hash(cast[int](o)) 26 | -------------------------------------------------------------------------------- /kiwi/variable.nim: -------------------------------------------------------------------------------- 1 | type Variable* = ref object 2 | mName: string 3 | value*: float 4 | 5 | proc newVariable*(name: string, value: float = 0): Variable = 6 | result.new() 7 | result.mName = name 8 | result.value = value 9 | 10 | proc newVariable*(value: float = 0): Variable = 11 | result.new() 12 | result.mName = ":anonymous" 13 | result.value = value 14 | 15 | proc name*(v: Variable): string = v.mName 16 | 17 | proc `$`*(v: Variable): string = 18 | "name: " & v.name & " value: " & $v.value 19 | -------------------------------------------------------------------------------- /layout.nim: -------------------------------------------------------------------------------- 1 | import kiwi, tables, idents, ast, sets 2 | 3 | proc newVarSet*(name: Ident): VarSet = 4 | new(result) 5 | result.top = newVariable(name.s & ".top") 6 | result.left = newVariable(name.s & ".left") 7 | result.right = newVariable(name.s & ".right") 8 | result.bottom = newVariable(name.s & ".bottom") 9 | result.width = newVariable(name.s & ".width") 10 | result.height = newVariable(name.s & ".height") 11 | result.centerX = newVariable(name.s & ".centerX") 12 | result.centerY = newVariable(name.s & ".centerY") 13 | 14 | proc newPropSet*(): PropSet = 15 | new(result) 16 | result.visible = true 17 | result.rotate = 0.0 18 | #result.bgColor 19 | #result.borderColor 20 | #result.pivotX 21 | #result.pivotY 22 | 23 | proc newPropSet*(o: PropSet): PropSet = 24 | new(result) 25 | result.visible = o.visible 26 | result.rotate = o.rotate 27 | result.bgColor = o.bgColor 28 | result.borderColor = o.borderColor 29 | result.pivotX = o.pivotX 30 | result.pivotY = o.pivotY 31 | 32 | proc newView*(name: Ident): View = 33 | new(result) 34 | result.origin = newVarSet(name) 35 | result.current = result.origin 36 | result.parent = nil 37 | result.views = initTable[Ident, View]() 38 | result.name = name 39 | result.children = @[] 40 | result.idx = -1 41 | result.dependencies = initSet[View]() 42 | result.content = "" 43 | result.oriProp = newPropSet() 44 | result.curProp = result.oriProp 45 | 46 | proc newView*(parent: View, name: Ident): View = 47 | assert(parent != nil) 48 | result = newView(name) 49 | result.idx = parent.children.len 50 | parent.views[name] = result 51 | parent.children.add result 52 | result.parent = parent 53 | 54 | proc setConstraint*(self: VarSet, solver: Solver) = 55 | solver.addConstraint(self.right == self.left + self.width) 56 | solver.addConstraint(self.bottom == self.top + self.height) 57 | solver.addConstraint(self.right >= self.left) 58 | solver.addConstraint(self.bottom >= self.top) 59 | solver.addConstraint(self.centerX == self.left + (self.right - self.left) / 2) 60 | solver.addConstraint(self.centerY == self.top + (self.bottom - self.top) / 2) 61 | 62 | proc setBasicConstraint*(solver: Solver, view: View) = 63 | view.origin.setConstraint(solver) 64 | 65 | proc setOrigin*(view: View) = 66 | view.current = view.origin 67 | view.curProp = view.oriProp 68 | for child in view.children: 69 | child.setOrigin() 70 | 71 | proc setOrigin*(view: View, origin: VarSet, prop: PropSet) = 72 | view.origin = origin 73 | view.current = origin 74 | view.oriProp = prop 75 | view.curProp = prop 76 | 77 | proc getChildren*(view: View): seq[View] = 78 | view.children 79 | 80 | proc getName*(view: View): string = 81 | view.name.s 82 | 83 | proc getTop*(view: View): float64 = 84 | view.current.top.value 85 | 86 | proc getLeft*(view: View): float64 = 87 | view.current.left.value 88 | 89 | proc getRight*(view: View): float64 = 90 | view.current.right.value 91 | 92 | proc getBottom*(view: View): float64 = 93 | view.current.bottom.value 94 | 95 | proc getWidth*(view: View): float64 = 96 | view.current.width.value 97 | 98 | proc getHeight*(view: View): float64 = 99 | view.current.height.value 100 | 101 | proc getCenterX*(view: View): float64 = 102 | view.current.centerX.value 103 | 104 | proc getCenterY*(view: View): float64 = 105 | view.current.centerY.value 106 | 107 | proc getParent*(view: View): View = 108 | view.parent 109 | 110 | proc getPrev*(view: View): View = 111 | result = nil 112 | if not view.parent.isNil: 113 | let i = view.idx - 1 114 | if i >= 0 and i < view.parent.children.len: 115 | result = view.parent.children[i] 116 | 117 | proc getNext*(view: View): View = 118 | result = nil 119 | if not view.parent.isNil: 120 | let i = view.idx + 1 121 | if i < view.parent.children.len: 122 | result = view.parent.children[i] 123 | 124 | proc getPrevIdx*(view: View, idx: int): View = 125 | result = nil 126 | if not view.parent.isNil: 127 | let i = view.idx - idx 128 | if i >= 0 and i < view.parent.children.len: 129 | result = view.parent.children[i] 130 | 131 | proc getNextIdx*(view: View, idx: int): View = 132 | result = nil 133 | if not view.parent.isNil: 134 | let i = view.idx + idx 135 | if i < view.parent.children.len: 136 | result = view.parent.children[i] 137 | 138 | proc findChild*(view: View, id: Ident): View = 139 | result = view.views.getOrDefault(id) 140 | 141 | proc newAnimation*(duration: float64): Animation = 142 | new(result) 143 | result.duration = duration 144 | result.actors = @[] 145 | result.solver = newSolver() 146 | 147 | # procs this needed for pivot mechanism 148 | proc viewGetTop*(view: View): float64 = 149 | view.current.top.value 150 | 151 | proc viewGetLeft*(view: View): float64 = 152 | view.current.left.value 153 | 154 | proc viewGetRight*(view: View): float64 = 155 | view.current.right.value 156 | 157 | proc viewGetBottom*(view: View): float64 = 158 | view.current.bottom.value 159 | 160 | proc viewGetWidth*(view: View): float64 = 161 | view.current.width.value 162 | 163 | proc viewGetHeight*(view: View): float64 = 164 | view.current.height.value 165 | 166 | proc viewGetCenterX*(view: View): float64 = 167 | view.current.centerX.value 168 | 169 | proc viewGetCenterY*(view: View): float64 = 170 | view.current.centerY.value 171 | -------------------------------------------------------------------------------- /lexer.nim: -------------------------------------------------------------------------------- 1 | import lexbase, strutils, streams, idents, keywords, razcontext, types 2 | 3 | type 4 | Lexer* = object of BaseLexer 5 | context*: RazContext 6 | nextState: LexerState 7 | tokenStartPos: int 8 | fileIndex*: int32 9 | c: char 10 | 11 | # this must be keep in sync with TokenKindToStr array 12 | TokenKind* = enum 13 | tkInvalid, tkEof, tkComment, tkNestedComment 14 | tkIdent, tkNumber, tkFloat, tkString, tkCharLit 15 | tkSemiColon, tkAccent, tkComma 16 | tkParLe, tkParRi, tkCurlyLe, tkCurlyRi, tkBracketLe, tkBracketRi 17 | 18 | # tkDot..tkProp must be keep in sync with 19 | # keywords.SpecialWords 20 | tkDot, tkDotDot, tkColon, tkColonColon 21 | tkBang, tkChoice, tkPercent, tkAt 22 | tkEquals, tkGreaterOrEqual, tkLessOrEqual, tkOpr 23 | 24 | tkProgram, tkStyle, tkAlias, tkFlex, tkEvent, tkProp 25 | 26 | TokenVal* = object {.union.} 27 | iNumber*: uint64 28 | fNumber*: float64 29 | ident*: Ident 30 | 31 | Token* {.acyclic.} = object 32 | kind*: TokenKind 33 | val*: TokenVal 34 | indent*: int 35 | literal*: string 36 | line*, col*: int 37 | 38 | LexerState = proc(lex: var Lexer, tok: var Token): bool {.locks: 0, gcSafe.} 39 | 40 | const 41 | LineEnd = {'\l', '\c', EndOfFile} 42 | OctalDigits = {'0'..'7'} 43 | BinaryDigits = {'0'..'1'} 44 | OpChars = {'+', '-', '*', '/', '\\', '<', '>', '!', '?', '^', '.', 45 | '|', '=', '%', '&', '$', '@', '~', ':', '\x80'..'\xFF'} 46 | 47 | TokenKindToStr: array[TokenKind, string] = [ 48 | "tkInvalid", "[EOF]", "tkComment", "tkNestedComment", 49 | "tkIdent", "tkNumber", "tkFloat", "tkString", "tkCharLit", 50 | ";", "`", ",", "(", ")", "{", "}", "[", "]", 51 | ".", "..", ":", "::", "!", "|", "%", "@", "=", ">=", "<=", "tkOpr", 52 | "program", "style", "alias", "flex", "event", "prop"] 53 | 54 | proc toString*(kind: TokenKind): string = 55 | result = TokenKindToStr[kind] 56 | 57 | const 58 | Tabulator* = '\x09' 59 | ESC* = '\x1B' 60 | CR* = '\x0D' 61 | FF* = '\x0C' 62 | LF* = '\x0A' 63 | BEL* = '\x07' 64 | BACKSPACE* = '\x08' 65 | VT* = '\x0B' 66 | 67 | {.push gcSafe, locks: 0.} 68 | proc stateOuterScope(lex: var Lexer, tok: var Token): bool 69 | proc stateLineComment(lex: var Lexer, tok: var Token): bool 70 | proc stateNestedComment(lex: var Lexer, tok: var Token): bool 71 | proc stateIdentifier(lex: var Lexer, tok: var Token): bool 72 | proc stateNumber(lex: var Lexer, tok: var Token): bool 73 | proc stateString(lex: var Lexer, tok: var Token): bool 74 | proc stateCharLit(lex: var Lexer, tok: var Token): bool 75 | proc stateOperator(lex: var Lexer, tok: var Token): bool 76 | {.pop.} 77 | 78 | proc openLexer*(stream: Stream, context: RazContext, fileIndex: int32): Lexer = 79 | result.fileIndex = fileIndex 80 | result.open(stream) 81 | result.nextState = stateOuterScope 82 | result.c = result.buf[result.bufpos] 83 | result.context = context 84 | 85 | proc advance(lex: var Lexer, step = 1) = 86 | lex.bufpos.inc(step) 87 | lex.c = lex.buf[lex.bufpos] 88 | 89 | proc nextChar(lex: Lexer): char = 90 | result = lex.buf[lex.bufpos + 1] 91 | 92 | proc tokenLen(lex: Lexer): int = 93 | result = lex.bufpos - lex.tokenStartPos 94 | 95 | proc tokenLit(lex: Lexer): string = 96 | result = newString(lex.tokenLen) 97 | copyMem(result.cstring, lex.buf[lex.tokenStartPos].unsafeAddr, result.len) 98 | 99 | proc getToken*(lex: var Lexer, tok: var Token) = 100 | while not lex.nextState(lex, tok): discard 101 | 102 | proc error(lex: Lexer, kind: MsgKind, args: varargs[string, `$`]) = 103 | var err = new(SourceError) 104 | err.msg = lex.context.msgKindToString(kind, args) 105 | err.line = lex.lineNumber 106 | err.column = lex.getColNumber(lex.bufpos) 107 | err.lineContent = lex.getCurrentLine(false) 108 | err.fileIndex = lex.fileIndex 109 | raise err 110 | 111 | proc lexCR(lex: var Lexer) = 112 | lex.bufpos = lex.handleCR(lex.bufpos) 113 | lex.c = lex.buf[lex.bufpos] 114 | 115 | proc lexLF(lex: var Lexer) = 116 | lex.bufpos = lex.handleLF(lex.bufpos) 117 | lex.c = lex.buf[lex.bufpos] 118 | 119 | template singleToken(tokKind: TokenKind) = 120 | tok.col = lex.getColNumber(lex.bufpos) 121 | tok.kind = tokKind 122 | tok.literal.add lex.c 123 | lex.advance 124 | return true 125 | 126 | #[template doubleToken(kindSingle: TokenKind, secondChar: char, kindDouble: TokenKind) = 127 | tok.col = lex.getColNumber(lex.bufpos) 128 | tok.literal.add lex.c 129 | if lex.nextChar == secondChar: 130 | lex.advance 131 | tok.kind = kindDouble 132 | tok.literal.add lex.c 133 | else: 134 | tok.kind = kindSingle 135 | lex.advance 136 | return true 137 | ]# 138 | 139 | template tokenNextState(tokKind: TokenKind, nextStateProc: typed) = 140 | tok.col = lex.getColNumber(lex.bufpos) 141 | tok.kind = tokKind 142 | lex.nextState = nextStateProc 143 | return false 144 | 145 | proc stateOuterScope(lex: var Lexer, tok: var Token): bool = 146 | let lineStart = lex.getColNumber(lex.bufpos) == 0 147 | 148 | while lex.c == ' ': 149 | lex.advance 150 | 151 | tok.line = lex.lineNumber 152 | if lineStart: 153 | tok.indent = lex.getColNumber(lex.bufpos) 154 | 155 | case lex.c 156 | of '#': 157 | tok.col = lex.getColNumber(lex.bufpos) 158 | if lex.nextChar == '[': 159 | tok.kind = tkNestedComment 160 | lex.nextState = stateNestedComment 161 | else: 162 | tok.kind = tkComment 163 | lex.nextState = stateLineComment 164 | return false 165 | of '\t': 166 | lex.error(errTabsAreNotAllowed) 167 | of '\c': 168 | lex.lexCR 169 | return false 170 | of '\l': 171 | lex.lexLF 172 | return false 173 | of IdentStartChars: tokenNextState(tkIdent, stateIdentifier) 174 | of Digits: tokenNextState(tkNumber, stateNumber) 175 | of '"': tokenNextState(tkString, stateString) 176 | of '\'': tokenNextState(tkCharLit, stateCharLit) 177 | of EndOfFile: 178 | tok.indent = 0 179 | singleToken(tkEof) 180 | of ';': singleToken(tkSemiColon) 181 | of '`': singleToken(tkAccent) 182 | of '(': singleToken(tkParLe) 183 | of ')': singleToken(tkParRi) 184 | of '{': singleToken(tkCurlyLe) 185 | of '}': singleToken(tkCurlyRi) 186 | of '[': singleToken(tkBracketLe) 187 | of ']': singleToken(tkBracketRi) 188 | of ',': singleToken(tkComma) 189 | else: 190 | if lex.c in OpChars: tokenNextstate(tkOpr, stateOperator) 191 | else: lex.error(errInvalidToken, lex.c) 192 | 193 | result = true 194 | 195 | proc stateLineComment(lex: var Lexer, tok: var Token): bool = 196 | while lex.c notin LineEnd: 197 | lex.advance 198 | lex.nextState = stateOuterScope 199 | result = true 200 | 201 | proc stateNestedComment(lex: var Lexer, tok: var Token): bool = 202 | lex.advance # skip '[' 203 | var level = 1 204 | while true: 205 | case lex.c 206 | of '\c': lex.lexCR 207 | of '\l': lex.lexLF 208 | of '#': 209 | if lex.nextChar == '[': 210 | lex.advance 211 | inc(level) 212 | lex.advance 213 | of ']': 214 | if lex.nextChar == '#': 215 | lex.advance 216 | dec(level) 217 | if level == 0: 218 | lex.advance 219 | lex.nextState = stateOuterScope 220 | return true 221 | lex.advance 222 | of EndOfFile: 223 | lex.error(errUnexpectedEOLinMultiLineComment) 224 | else: 225 | lex.advance 226 | 227 | proc stateIdentifier(lex: var Lexer, tok: var Token): bool = 228 | lex.tokenStartPos = lex.bufpos 229 | while lex.c in IdentChars: 230 | lex.advance 231 | 232 | tok.literal = lex.tokenLit 233 | tok.val.ident = lex.context.getIdent(tok.literal) 234 | if tok.val.ident.id > ord(wProgram) and tok.val.ident.id < ord(wThis): 235 | let id = tok.val.ident.id - ord(wProgram) 236 | tok.kind = TokenKind(id + ord(tkProgram)) 237 | 238 | lex.nextState = stateOuterScope 239 | result = true 240 | 241 | proc charToInt(c: char): int = 242 | case c 243 | of {'0'..'9'}: result = ord(c) - ord('0') 244 | of {'a'..'f'}: result = ord(c) - ord('a') + 10 245 | of {'A'..'F'}: result = ord(c) - ord('A') + 10 246 | else: result = 0 247 | 248 | proc parseBoundedInt(lex: var Lexer, val: string, base: int, maxVal: uint64, start = 0): uint64 = 249 | result = 0 250 | for i in start.. ov: 255 | lex.error(errNumberOverflow) 256 | else: result += c 257 | 258 | proc stateNumber(lex: var Lexer, tok: var Token): bool = 259 | type 260 | NumberType = enum 261 | UnknownNumber 262 | OrdinaryNumber 263 | HexNumber 264 | OctalNumber 265 | BinaryNumber 266 | FloatNumber 267 | 268 | template matchChars(lex: var Lexer, validRange, wideRange: set[char]) = 269 | while true: 270 | while lex.c in wideRange: 271 | if lex.c notin validRange: 272 | lex.error(errInvalidNumberRange, lex.c) 273 | tok.literal.add lex.c 274 | lex.advance 275 | 276 | if lex.c == '_': 277 | lex.advance 278 | if lex.c notin wideRange: 279 | lex.error(errInvalidToken, "_") 280 | 281 | if lex.c notin wideRange: 282 | break 283 | 284 | var numberType = UnknownNumber 285 | if lex.c == '0': 286 | let nc = lex.nextChar 287 | tok.literal.add lex.c 288 | lex.advance 289 | case nc 290 | of 'x', 'X': 291 | tok.literal.add lex.c 292 | lex.advance 293 | lex.matchChars(HexDigits, Letters + Digits) 294 | numberType = HexNumber 295 | of 'c', 'o', 'C', 'O': 296 | tok.literal.add lex.c 297 | lex.advance 298 | lex.matchChars(OctalDigits, Digits) 299 | numberType = OctalNumber 300 | of 'b', 'B': 301 | tok.literal.add lex.c 302 | lex.advance 303 | lex.matchChars(BinaryDigits, Digits) 304 | numberType = BinaryNumber 305 | else: 306 | numberType = OrdinaryNumber 307 | else: 308 | numberType = OrdinaryNumber 309 | 310 | if numberType == OrdinaryNumber: 311 | lex.matchChars(Digits, Digits) 312 | if lex.c == '.': 313 | tok.literal.add lex.c 314 | lex.advance 315 | tok.kind = tkFloat 316 | numberType = FloatNumber 317 | 318 | if tok.kind == tkFloat: 319 | lex.matchChars(Digits, Digits) 320 | 321 | if lex.c in {'e', 'E'}: 322 | tok.literal.add lex.c 323 | lex.advance 324 | 325 | if lex.c in {'+', '-'}: 326 | tok.literal.add lex.c 327 | lex.advance 328 | 329 | while lex.c in Digits: 330 | tok.literal.add lex.c 331 | lex.advance 332 | 333 | when defined(cpu64): 334 | const maxVal = 0xFFFFFFFF_FFFFFFFF'u64 335 | else: 336 | const maxVal = 0xFFFFFFFF'u32 337 | 338 | case numberType 339 | of OrdinaryNumber: tok.val.iNumber = lex.parseBoundedInt(tok.literal, 10, maxVal) 340 | of HexNumber: tok.val.iNumber = lex.parseBoundedInt(tok.literal, 16, maxVal, 2) 341 | of OctalNumber: tok.val.iNumber = lex.parseBoundedInt(tok.literal, 8, maxVal, 2) 342 | of BinaryNumber: tok.val.iNumber = lex.parseBoundedInt(tok.literal, 2, maxVal, 2) 343 | of FloatNumber: tok.val.fNumber = parseFloat(tok.literal) 344 | else: lex.error(errUnknownNumberType) 345 | 346 | lex.nextState = stateOuterScope 347 | result = true 348 | 349 | proc handleHexChar(lex: var Lexer, xi: var int) = 350 | case lex.c 351 | of '0'..'9': 352 | xi = (xi shl 4) or (ord(lex.c) - ord('0')) 353 | lex.advance 354 | of 'a'..'f': 355 | xi = (xi shl 4) or (ord(lex.c) - ord('a') + 10) 356 | lex.advance 357 | of 'A'..'F': 358 | xi = (xi shl 4) or (ord(lex.c) - ord('A') + 10) 359 | lex.advance 360 | else: discard 361 | 362 | proc handleDecChars(lex: var Lexer, xi: var int) = 363 | while lex.c in {'0'..'9'}: 364 | xi = (xi * 10) + (ord(lex.c) - ord('0')) 365 | lex.advance 366 | 367 | template ones(n): untyped = ((1 shl n)-1) # for utf-8 conversion 368 | 369 | proc getEscapedChar(lex: var Lexer, tok: var Token) = 370 | lex.advance 371 | case lex.c 372 | of 'r', 'c', 'R', 'C': 373 | tok.literal.add CR 374 | lex.advance 375 | of 'l', 'L', 'n', 'N': 376 | tok.literal.add LF 377 | lex.advance 378 | of 'f', 'F': 379 | tok.literal.add FF 380 | lex.advance 381 | of 't', 'T': 382 | tok.literal.add Tabulator 383 | lex.advance 384 | of 'v', 'V': 385 | tok.literal.add VT 386 | lex.advance 387 | of '\\', '"', '\'': 388 | tok.literal.add lex.c 389 | lex.advance 390 | of 'a', 'A': 391 | tok.literal.add BEL 392 | lex.advance 393 | of 'b', 'B': 394 | tok.literal.add BACKSPACE 395 | lex.advance 396 | of 'e', 'E': 397 | tok.literal.add ESC 398 | lex.advance 399 | of Digits: 400 | var xi = 0 401 | lex.handleDecChars(xi) 402 | if (xi <= 255): add(tok.literal, chr(xi)) 403 | else: lex.error(errInvalidCharacterConstant, lex.c) 404 | of 'x', 'X', 'u', 'U': 405 | let tp = lex.c 406 | lex.advance 407 | var xi = 0 408 | lex.handleHexChar(xi) 409 | lex.handleHexChar(xi) 410 | if tp in {'u', 'U'}: 411 | lex.handleHexChar(xi) 412 | lex.handleHexChar(xi) 413 | # inlined toUTF-8 to avoid unicode and strutils dependencies. 414 | if xi <=% 127: 415 | add(tok.literal, xi.char) 416 | elif xi <=% 0x07FF: 417 | add(tok.literal, ((xi shr 6) or 0b110_00000).char) 418 | add(tok.literal, ((xi and ones(6)) or 0b10_0000_00).char) 419 | elif xi <=% 0xFFFF: 420 | add(tok.literal, (xi shr 12 or 0b1110_0000).char) 421 | add(tok.literal, (xi shr 6 and ones(6) or 0b10_0000_00).char) 422 | add(tok.literal, (xi and ones(6) or 0b10_0000_00).char) 423 | else: # value is 0xFFFF 424 | add(tok.literal, "\xef\xbf\xbf") 425 | else: 426 | add(tok.literal, chr(xi)) 427 | else: 428 | lex.error(errWrongEscapeChar, lex.c) 429 | 430 | proc stateString(lex: var Lexer, tok: var Token): bool = 431 | lex.advance 432 | while true: 433 | case lex.c 434 | of LineEnd: 435 | lex.error(errClosingQuoteExpected) 436 | of '\\': 437 | lex.getEscapedChar(tok) 438 | of '"': 439 | lex.advance 440 | break 441 | else: 442 | tok.literal.add lex.c 443 | lex.advance 444 | 445 | lex.nextState = stateOuterScope 446 | result = true 447 | 448 | proc stateCharLit(lex: var Lexer, tok: var Token): bool = 449 | lex.advance 450 | case lex.c 451 | of '\0'..pred(' '), '\'': 452 | lex.error(errInvalidCharacterConstant, toHex(ord(lex.c), 2)) 453 | of '\\': 454 | lex.getEscapedChar(tok) 455 | else: 456 | tok.literal.add lex.c 457 | lex.advance 458 | 459 | if lex.c != '\'': 460 | lex.error(errMissingFinalQuote) 461 | 462 | lex.advance 463 | lex.nextState = stateOuterScope 464 | result = true 465 | 466 | proc stateOperator(lex: var Lexer, tok: var Token): bool = 467 | while lex.c in OpChars: 468 | tok.literal.add lex.c 469 | lex.advance 470 | 471 | tok.val.ident = lex.context.getIdent(tok.literal) 472 | if tok.val.ident.id >= ord(wDot) and tok.val.ident.id < ord(wProgram): 473 | let id = tok.val.ident.id - ord(wDot) 474 | tok.kind = TokenKind(id + ord(tkDot)) 475 | 476 | lex.nextState = stateOuterScope 477 | result = true 478 | 479 | proc initToken*(): Token = 480 | result.kind = tkInvalid 481 | result.indent = -1 482 | result.literal = "" 483 | result.line = 0 484 | result.col = 0 485 | 486 | proc reset*(tok: var Token) = 487 | tok.kind = tkInvalid 488 | tok.indent = -1 489 | tok.literal.setLen(0) 490 | tok.line = 0 491 | tok.col = 0 492 | -------------------------------------------------------------------------------- /lua.nim: -------------------------------------------------------------------------------- 1 | # 2 | #* $Id: lua.h,v 1.283 2012/04/20 13:18:26 roberto Exp $ 3 | #* Lua - A Scripting Language 4 | #* Lua.org, PUC-Rio, Brazil (http://www.lua.org) 5 | #* See Copyright Notice at the end of this file 6 | # 7 | const 8 | LUA_VERSION_MAJOR* = "5" 9 | LUA_VERSION_MINOR* = "3" 10 | LUA_VERSION_NUM* = 531 11 | LUA_VERSION_RELEASE* = "1" 12 | LUA_VERSION* = "Lua " & LUA_VERSION_MAJOR & "." & LUA_VERSION_MINOR 13 | #LUA_RELEASE = LUA_VERSION & "." & LUA_VERSION_RELEASE 14 | #LUA_COPYRIGHT = LUA_RELEASE & " Copyright (C) 1994-2012 Lua.org, PUC-Rio" 15 | #LUA_AUTHORS = "R. Ierusalimschy, L. H. de Figueiredo, W. Celes" 16 | 17 | #{.deadCodeElim: on.} 18 | #when defined(useLuaJIT): 19 | # {.warning: "Lua JIT does not support Lua 5.3 at this time."} 20 | 21 | when not defined(useLuaJIT): 22 | when defined(MACOSX): 23 | const 24 | LIB_NAME* = "liblua5.3.dylib" 25 | elif defined(UNIX): 26 | const 27 | LIB_NAME* = "liblua(5.3.so|.so.5.3)" 28 | else: 29 | when defined(cpu64): 30 | const 31 | LIB_NAME* = "lua53x64.dll" 32 | else: 33 | const 34 | LIB_NAME* = "lua53.dll" 35 | else: 36 | when defined(MACOSX): 37 | const 38 | LIB_NAME* = "libluajit.dylib" 39 | elif defined(UNIX): 40 | const 41 | LIB_NAME* = "libluajit.so" 42 | else: 43 | const 44 | LIB_NAME* = "luajit.dll" 45 | 46 | const 47 | # mark for precompiled code ('Lua') 48 | LUA_SIGNATURE* = "\x1BLua" 49 | 50 | # option for multiple returns in 'lua_pcall' and 'lua_call' 51 | LUA_MULTRET* = (-1) 52 | 53 | # 54 | #* pseudo-indices 55 | # 56 | #@@ LUAI_MAXSTACK limits the size of the Lua stack. 57 | #* CHANGE it if you need a different limit. This limit is arbitrary; 58 | #* its only purpose is to stop Lua to consume unlimited stack 59 | #* space (and to reserve some numbers for pseudo-indices). 60 | # 61 | when sizeof(int) >= 4: #LUAI_BITSINT >= 32: 62 | const 63 | LUAI_MAXSTACK* = 1000000 64 | else: 65 | const 66 | LUAI_MAXSTACK* = 15000 67 | 68 | # reserve some space for error handling 69 | const 70 | LUAI_FIRSTPSEUDOIDX* = (-LUAI_MAXSTACK - 1000) 71 | LUA_REGISTRYINDEX* = LUAI_FIRSTPSEUDOIDX 72 | 73 | proc upvalueindex*(i: int): int {.inline.} = LUA_REGISTRYINDEX - i 74 | 75 | # thread status 76 | type TThreadStatus* {.size:sizeof(cint).}= enum 77 | Thread_OK = 0, Thread_Yield, Thread_ErrRun, Thread_ErrSyntax, 78 | Thread_ErrMem, Thread_ErrGCMM, Thread_ErrErr 79 | 80 | const 81 | LUA_OK* = 0 82 | LUA_YIELD* = 1 83 | LUA_ERRRUN* = 2 84 | LUA_ERRSYNTAX* = 3 85 | LUA_ERRMEM* = 4 86 | LUA_ERRGCMM* = 5 87 | LUA_ERRERR* = 6 88 | 89 | type 90 | PState* = distinct pointer 91 | lua_State* = PState 92 | TCFunction* = proc (L: PState): cint{.cdecl.} 93 | 94 | #* functions that read/write blocks when loading/dumping Lua chunks 95 | TReader* = proc (L: PState; ud: pointer; sz: var csize): cstring {.cdecl.} 96 | TWriter* = proc (L: PState; p: pointer; sz: csize; ud: pointer): cint {.cdecl.} 97 | 98 | #* prototype for memory-allocation functions 99 | TAlloc* = proc (ud, p: pointer; osize, nsize: csize): pointer 100 | 101 | #* basic types 102 | const 103 | LUA_TNONE* = (-1) 104 | LUA_TNIL* = 0 105 | LUA_TBOOLEAN* = 1 106 | LUA_TLIGHTUSERDATA* = 2 107 | LUA_TNUMBER* = 3 108 | LUA_TSTRING* = 4 109 | LUA_TTABLE* = 5 110 | LUA_TFUNCTION* = 6 111 | LUA_TUSERDATA* = 7 112 | LUA_TTHREAD* = 8 113 | LUA_NUMTAGS* = 9 114 | 115 | type 116 | LUA_TYPE* = enum 117 | LNONE = -1, LNIL, LBOOLEAN, LLIGHTUSERDATA, LNUMBER, 118 | LSTRING, LTABLE, LFUNCTION, LUSERDATA, LTHREAD, LNUMTAGS 119 | 120 | # minimum Lua stack available to a C function 121 | const 122 | LUA_MINSTACK* = 20 123 | 124 | # predefined values in the registry 125 | const 126 | LUA_RIDX_MAINTHREAD* = 1 127 | LUA_RIDX_GLOBALS* = 2 128 | LUA_RIDX_LAST* = LUA_RIDX_GLOBALS 129 | 130 | type 131 | lua_Number* = float64 # type of numbers in Lua 132 | lua_Integer* = int64 # ptrdiff_t \ type for integer functions 133 | 134 | {.push callconv: cdecl, dynlib: LIB_NAME .} # importc: "lua_$1" was not allowed? 135 | {.pragma: ilua, importc: "lua_$1".} # lua.h 136 | {.pragma: iluaLIB, importc: "lua$1".} # lualib.h 137 | {.pragma: iluaL, importc: "luaL_$1".} # lauxlib.h 138 | 139 | proc newstate*(f: TAlloc; ud: pointer): PState {.ilua.} 140 | proc close*(L: PState) {.ilua.} 141 | proc newthread*(L: PState): PState {.ilua.} 142 | proc atpanic*(L: PState; panicf: TCFunction): TCFunction {.ilua.} 143 | proc version*(L: PState): ptr lua_Number {.ilua.} 144 | 145 | # 146 | #* basic stack manipulation 147 | # 148 | proc absindex*(L: PState; idx: cint): cint {.ilua.} 149 | proc gettop*(L: PState): cint {.ilua.} 150 | proc settop*(L: PState; idx: cint) {.ilua.} 151 | proc pushvalue*(L: PState; idx: cint) {.ilua.} 152 | proc rotate*(L: PState; idx, n: cint) {.ilua.} 153 | 154 | proc copy*(L: PState; fromidx: cint; toidx: cint) {.ilua.} 155 | proc checkstack*(L: PState; sz: cint): cint {.ilua.} 156 | proc xmove*(src: PState; dst: PState; n: cint) {.ilua.} 157 | 158 | proc pop*(L: PState; n: cint) {.inline.} = L.settop(-n - 1) 159 | proc insert*(L: PState, idx: cint) {.inline.} = L.rotate(idx, 1) 160 | proc remove*(L: PState, idx: cint) {.inline.} = L.rotate(idx, -1); L.pop(1) 161 | proc replace*(L: PState, idx: cint) {.inline.} = L.copy(-1, idx); L.pop(1) 162 | 163 | # 164 | #* access functions (stack -> C) 165 | # 166 | proc isnumber*(L: PState; idx: cint): cint {.ilua.} 167 | proc isstring*(L: PState; idx: cint): cint {.ilua.} 168 | proc iscfunction*(L: PState; idx: cint): cint {.ilua.} 169 | proc isuserdata*(L: PState; idx: cint): cint {.ilua.} 170 | proc isinteger*(L: PState; idx: cint): cint {.ilua.} 171 | proc luatype*(L: PState; idx: cint): cint {.importc: "lua_type".} 172 | proc typename*(L: PState; tp: cint): cstring {.ilua.} 173 | proc tonumberx*(L: PState; idx: cint; isnum: ptr cint): lua_Number {.ilua.} 174 | proc tointegerx*(L: PState; idx: cint; isnum: ptr cint): lua_Integer {.ilua.} 175 | proc toboolean*(L: PState; idx: cint): cint {.ilua.} 176 | proc tolstring*(L: PState; idx: cint; len: ptr csize): cstring {.ilua.} 177 | proc rawlen*(L: PState; idx: cint): csize {.ilua.} 178 | proc tocfunction*(L: PState; idx: cint): TCFunction {.ilua.} 179 | proc touserdata*(L: PState; idx: cint): pointer {.ilua.} 180 | proc tothread*(L: PState; idx: cint): PState {.ilua.} 181 | proc topointer*(L: PState; idx: cint): pointer {.ilua.} 182 | 183 | # 184 | #* Comparison and arithmetic functions 185 | # 186 | const 187 | LUA_OPADD* = 0 # ORDER TM 188 | LUA_OPSUB* = 1 189 | LUA_OPMUL* = 2 190 | LUA_OPDIV* = 3 191 | LUA_OPMOD* = 4 192 | LUA_OPPOW* = 5 193 | LUA_OPUNM* = 6 194 | proc arith*(L: PState; op: cint) {.ilua.} 195 | 196 | const 197 | LUA_OPEQ* = 0 198 | LUA_OPLT* = 1 199 | LUA_OPLE* = 2 200 | proc rawequal*(L: PState; idx1: cint; idx2: cint): cint {.ilua.} 201 | proc compare*(L: PState; idx1: cint; idx2: cint; op: cint): cint {.ilua.} 202 | 203 | # 204 | #* push functions (C -> stack) 205 | # 206 | proc pushnil*(L: PState) {.ilua.} 207 | proc pushnumber*(L: PState; n: lua_Number) {.ilua.} 208 | proc pushinteger*(L: PState; n: lua_Integer) {.ilua.} 209 | proc pushlstring*(L: PState; s: cstring; len: csize): cstring {.ilua.} 210 | proc pushstring*(L: PState; s: cstring): cstring {.ilua.} 211 | proc pushvfstring*(L: PState; fmt: cstring): cstring {.varargs,ilua.} 212 | proc pushfstring*(L: PState; fmt: cstring): cstring {.varargs,ilua.} 213 | proc pushcclosure*(L: PState; fn: TCFunction; n: cint) {.ilua.} 214 | proc pushboolean*(L: PState; b: cint) {.ilua.} 215 | proc pushlightuserdata*(L: PState; p: pointer) {.ilua.} 216 | proc pushthread*(L: PState): cint {.ilua.} 217 | 218 | # 219 | #* get functions (Lua -> stack) 220 | # 221 | proc getglobal*(L: PState; variable: cstring) {.ilua.} 222 | proc gettable*(L: PState; idx: cint) {.ilua.} 223 | proc getfield*(L: PState; idx: cint; k: cstring) {.ilua.} 224 | proc rawget*(L: PState; idx: cint) {.ilua.} 225 | proc rawgeti*(L: PState; idx: cint; n: cint) {.ilua.} 226 | proc rawgetp*(L: PState; idx: cint; p: pointer) {.ilua.} 227 | proc createtable*(L: PState; narr: cint; nrec: cint) {.ilua.} 228 | proc newuserdata*(L: PState; sz: csize): pointer {.ilua.} 229 | proc getmetatable*(L: PState; idx: cint): cint {.ilua.} 230 | proc getuservalue*(L: PState; idx: cint) {.ilua.} 231 | 232 | # 233 | #* set functions (stack -> Lua) 234 | # 235 | proc setglobal*(L: PState; variable: cstring) {.ilua.} 236 | proc settable*(L: PState; idx: cint) {.ilua.} 237 | proc setfield*(L: PState; idx: cint; k: cstring) {.ilua.} 238 | proc rawset*(L: PState; idx: cint) {.ilua.} 239 | proc rawseti*(L: PState; idx: cint; n: lua_Integer) {.ilua.} 240 | proc rawsetp*(L: PState; idx: cint; p: pointer) {.ilua.} 241 | proc setmetatable*(L: PState; objindex: cint): cint {.ilua.} 242 | proc setuservalue*(L: PState; idx: cint) {.ilua.} 243 | 244 | # 245 | #* 'load' and 'call' functions (load and run Lua code) 246 | # 247 | proc callk*(L: PState; nargs, nresults, ctx: cint; k: TCFunction) {.ilua.} 248 | proc call*(L: PState; n, r: cint) {.inline.} = L.callk(n, r, 0, nil) 249 | 250 | #proc getctx*(L: PState; ctx: ptr cint): cint {.ilua.} 251 | proc pcallk*(L: PState; nargs, nresults, errfunc, ctx: cint; k: TCFunction): cint {.ilua.} 252 | proc pcall*(L: PState; nargs, nresults, errFunc: cint): cint {.inline.} = 253 | L.pcallK(nargs, nresults, errFunc, 0, nil) 254 | 255 | proc load*(L: PState; reader: TReader; dt: pointer; chunkname, mode: cstring): cint {.ilua.} 256 | proc dump*(L: PState; writer: TWriter; data: pointer): cint {.ilua.} 257 | 258 | # 259 | #* coroutine functions 260 | # 261 | proc yieldk*(L: PState; nresults: cint; ctx: cint; k: TCFunction): cint {.ilua.} 262 | proc luayield*(L: PState, n: cint): cint {.inline.} = L.yieldk(n, 0, nil) 263 | proc resume*(L: PState; fromL: PState; narg: cint): cint {.ilua.} 264 | proc status*(L: PState): cint {.ilua.} 265 | 266 | # 267 | #* garbage-collection function and options 268 | # 269 | const 270 | LUA_GCSTOP* = 0 271 | LUA_GCRESTART* = 1 272 | LUA_GCCOLLECT* = 2 273 | LUA_GCCOUNT* = 3 274 | LUA_GCCOUNTB* = 4 275 | LUA_GCSTEP* = 5 276 | LUA_GCSETPAUSE* = 6 277 | LUA_GCSETSTEPMUL* = 7 278 | LUA_GCSETMAJORINC* = 8 279 | LUA_GCISRUNNING* = 9 280 | LUA_GCGEN* = 10 281 | LUA_GCINC* = 11 282 | proc gc*(L: PState; what: cint; data: cint): cint {.ilua.} 283 | 284 | # 285 | #* miscellaneous functions 286 | # 287 | proc error*(L: PState): cint {.ilua.} 288 | proc next*(L: PState; idx: cint): cint {.ilua.} 289 | proc concat*(L: PState; n: cint) {.ilua.} 290 | proc len*(L: PState; idx: cint) {.ilua.} 291 | proc getallocf*(L: PState; ud: var pointer): TAlloc {.ilua.} 292 | proc setallocf*(L: PState; f: TAlloc; ud: pointer) {.ilua.} 293 | 294 | # 295 | #* =============================================================== 296 | #* some useful macros 297 | #* =============================================================== 298 | # 299 | proc tonumber*(L: PState; i: cint): lua_Number {.inline.} = L.tonumberx(i, nil) 300 | proc tointeger*(L: PState; i: cint): lua_Integer {.inline.} = L.tointegerx(i, nil) 301 | proc newtable*(L: PState) {.inline.} = L.createtable(0,0) 302 | proc pushcfunction*(L: PState; fn: TCfunction) {.inline.} = L.pushCclosure(fn, 0) 303 | proc register*(L: PState, n: string, f :TCFunction) {.inline.} = 304 | L.pushcfunction(f); L.setglobal(n) 305 | 306 | proc isfunction* (L: PState; n: cint): bool {.inline.} = 307 | L.luatype(n) == LUA_TFUNCTION 308 | 309 | proc istable* (L: PState; n: cint): bool {.inline.} = 310 | L.luatype(n) == LUA_TTABLE 311 | 312 | proc islightuserdata*(L: PState; n: cint): bool {.inline.} = 313 | L.luatype(n) == LUA_TLIGHTUSERDATA 314 | 315 | proc isnil*(L: PState; n: cint): bool {.inline.} = 316 | L.luatype(n) == LUA_TNIL 317 | 318 | proc isboolean*(L: PState; n: cint): bool {.inline.} = 319 | L.luatype(n) == LUA_TBOOLEAN 320 | 321 | proc isthread* (L: PState; n: cint): bool {.inline.} = 322 | L.luatype(n) == LUA_TTHREAD 323 | 324 | proc isnone* (L: PState; n: cint): bool {.inline.} = 325 | L.luatype(n) == LUA_TNONE 326 | 327 | proc isnoneornil*(L: PState; n: cint): bool {.inline.} = 328 | L.luatype(n) <= 0 329 | 330 | proc pushliteral*(L: PState, s: string): cstring {.inline, discardable.} = 331 | L.pushlstring(s, s.len) 332 | 333 | proc pushglobaltable*(L: PState) {.inline.} = 334 | L.rawgeti(LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS) 335 | 336 | proc tostring*(L: PState; index: cint): string = 337 | var len: cint = 0 338 | var s = L.tolstring(index, addr(len)) 339 | result = newString(len) 340 | copyMem(result.cstring, s, len) 341 | 342 | proc tobool*(L: PState; index: cint): bool = 343 | result = if L.toboolean(index) == 1: true else: false 344 | 345 | proc gettype*(L: PState, index: int): LUA_TYPE = 346 | result = LUA_TYPE(L.luatype(index.cint)) 347 | 348 | # 349 | #* {====================================================================== 350 | #* Debug API 351 | #* ======================================================================= 352 | # 353 | # 354 | #* Event codes 355 | # 356 | const 357 | LUA_HOOKCALL* = 0 358 | LUA_HOOKRET* = 1 359 | LUA_HOOKLINE* = 2 360 | LUA_HOOKCOUNT* = 3 361 | LUA_HOOKTAILCALL* = 4 362 | # 363 | #* Event masks 364 | # 365 | const 366 | LUA_MASKCALL* = (1 shl LUA_HOOKCALL) 367 | LUA_MASKRET* = (1 shl LUA_HOOKRET) 368 | LUA_MASKLINE* = (1 shl LUA_HOOKLINE) 369 | LUA_MASKCOUNT* = (1 shl LUA_HOOKCOUNT) 370 | # activation record 371 | 372 | 373 | #@@ LUA_IDSIZE gives the maximum size for the description of the source 374 | #@* of a function in debug information. 375 | #* CHANGE it if you want a different size. 376 | # 377 | const 378 | LUA_IDSIZE* = 60 379 | 380 | # Functions to be called by the debugger in specific events 381 | type 382 | PDebug* = ptr lua.TDebug 383 | TDebug* {.pure, final.} = object 384 | event*: cint 385 | name*: cstring # (n) 386 | namewhat*: cstring # (n) 'global', 'local', 'field', 'method' 387 | what*: cstring # (S) 'Lua', 'C', 'main', 'tail' 388 | source*: cstring # (S) 389 | currentline*: cint # (l) 390 | linedefined*: cint # (S) 391 | lastlinedefined*: cint # (S) 392 | nups*: cuchar # (u) number of upvalues 393 | nparams*: cuchar # (u) number of parameters 394 | isvararg*: char # (u) 395 | istailcall*: char # (t) 396 | short_src*: array[LUA_IDSIZE, char] # (S) \ # private part 397 | i_ci: pointer#ptr CallInfo # active function 398 | 399 | 400 | type 401 | lua_Hook* = proc (L: PState; ar: PDebug) {.cdecl.} 402 | proc getstack*(L: PState; level: cint; ar: PDebug): cint {.ilua.} 403 | proc getinfo*(L: PState; what: cstring; ar: PDebug): cint {.ilua.} 404 | proc getlocal*(L: PState; ar: PDebug; n: cint): cstring {.ilua.} 405 | proc setlocal*(L: PState; ar: PDebug; n: cint): cstring {.ilua.} 406 | proc getupvalue*(L: PState; funcindex: cint; n: cint): cstring {.ilua.} 407 | proc setupvalue*(L: PState; funcindex: cint; n: cint): cstring {.ilua.} 408 | proc upvalueid*(L: PState; fidx: cint; n: cint): pointer {.ilua.} 409 | proc upvaluejoin*(L: PState; fidx1: cint; n1: cint; fidx2: cint; n2: cint) {.ilua.} 410 | proc sethook*(L: PState; fn: lua_Hook; mask: cint; count: cint): cint {.ilua.} 411 | proc gethook*(L: PState): lua_Hook {.ilua.} 412 | proc gethookmask*(L: PState): cint {.ilua.} 413 | proc gethookcount*(L: PState): cint {.ilua.} 414 | 415 | # }====================================================================== 416 | #***************************************************************************** 417 | # Copyright (C) 1994-2012 Lua.org, PUC-Rio. 418 | # 419 | # Permission is hereby granted, free of charge, to any person obtaining 420 | # a copy of this software and associated documentation files (the 421 | # "Software"), to deal in the Software without restriction, including 422 | # without limitation the rights to use, copy, modify, merge, publish, 423 | # distribute, sublicense, and/or sell copies of the Software, and to 424 | # permit persons to whom the Software is furnished to do so, subject to 425 | # the following conditions: 426 | # 427 | # The above copyright notice and this permission notice shall be 428 | # included in all copies or substantial portions of the Software. 429 | # 430 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 431 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 432 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 433 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 434 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 435 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 436 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 437 | #**************************************************************************** 438 | 439 | 440 | 441 | 442 | # 443 | #* $Id: lualib.h,v 1.43 2011/12/08 12:11:37 roberto Exp $ 444 | #* Lua standard libraries 445 | #* See Copyright Notice in lua.h 446 | # 447 | 448 | proc open_base*(L: PState): cint {.iluaLIB.} 449 | const 450 | LUA_COLIBNAME* = "coroutine" 451 | proc open_coroutine*(L: PState): cint {.iluaLIB.} 452 | const 453 | LUA_TABLIBNAME* = "table" 454 | proc open_table*(L: PState): cint {.iluaLIB.} 455 | const 456 | LUA_IOLIBNAME* = "io" 457 | proc open_io*(L: PState): cint {.iluaLIB.} 458 | const 459 | LUA_OSLIBNAME* = "os" 460 | proc open_os*(L: PState): cint {.iluaLIB.} 461 | const 462 | LUA_STRLIBNAME* = "string" 463 | proc open_string*(L: PState): cint {.iluaLIB.} 464 | const 465 | LUA_UTF8LIBNAME* = "utf8" 466 | proc open_utf8*(L: PState): cint {.iluaLIB.} 467 | const 468 | LUA_BITLIBNAME* = "bit32" 469 | proc open_bit32*(L: PState): cint {.iluaLIB.} 470 | const 471 | LUA_MATHLIBNAME* = "math" 472 | proc open_math*(L: PState): cint {.iluaLIB.} 473 | const 474 | LUA_DBLIBNAME* = "debug" 475 | proc open_debug*(L: PState): cint {.iluaLIB.} 476 | const 477 | LUA_LOADLIBNAME* = "package" 478 | proc open_package*(L: PState): cint {.iluaLIB.} 479 | # open all previous libraries 480 | proc openlibs*(L: PState) {.iluaL.} 481 | 482 | when not defined(lua_assert): 483 | template lua_assert*(x: typed): typed = 484 | (cast[nil](0)) 485 | 486 | 487 | # 488 | #* $Id: lauxlib.h,v 1.120 2011/11/29 15:55:08 roberto Exp $ 489 | #* Auxiliary functions for building Lua libraries 490 | #* See Copyright Notice in lua.h 491 | # 492 | 493 | # extra error code for `luaL_load' 494 | const 495 | LUA_ERRFILE* = Thread_ErrErr.cint + 1'i32 #(LUA_ERRERR + 1) 496 | 497 | type 498 | luaL_Reg* {.pure, final.} = object 499 | name*: cstring 500 | fn*: TCFunction 501 | 502 | const 503 | LUAL_NUMSIZES = (sizeof(lua_Integer)*16 + sizeof(lua_Number)) 504 | 505 | ### IMPORT FROM "luaL_$1" 506 | proc checkversion*(L: PState; ver: lua_Number; sz: csize) {.importc: "luaL_checkversion_".} 507 | proc checkversion*(L: PState) {.inline.} = L.checkversion(LUA_VERSION_NUM, LUAL_NUMSIZES) 508 | 509 | proc getmetafield*(L: PState; obj: cint; e: cstring): cint {.iluaL.} 510 | proc callmeta*(L: PState; obj: cint; e: cstring): cint {.iluaL.} 511 | #proc tolstring*(L: PState; idx: cint; len: ptr csize): cstring {.importc: "luaL_tolstring".} 512 | # ^ duplicate? 513 | proc argerror*(L: PState; numarg: cint; extramsg: cstring): cint {.iluaL.} 514 | proc checklstring*(L: PState; arg: cint; len: ptr csize): cstring {.iluaL.} 515 | proc optlstring*(L: PState; arg: cint; def: cstring; len: ptr csize): cstring {.iluaL.} 516 | proc checknumber*(L: PState; arg: cint): lua_Number {.iluaL.} 517 | proc optnumber*(L: PState; arg: cint; def: lua_Number): lua_Number {.iluaL.} 518 | proc checkinteger*(L: PState; arg: cint): lua_Integer {.iluaL.} 519 | proc optinteger*(L: PState; arg: cint; def: lua_Integer): lua_Integer {.iluaL.} 520 | proc checkstack*(L: PState; sz: cint; msg: cstring) {.iluaL.} 521 | proc checktype*(L: PState; arg: cint; t: cint) {.iluaL.} 522 | proc checkany*(L: PState; arg: cint) {.iluaL.} 523 | proc newmetatable*(L: PState; tname: cstring): cint {.iluaL.} 524 | proc setmetatable*(L: PState; tname: cstring) {.iluaL.} 525 | proc testudata*(L: PState; ud: cint; tname: cstring): pointer {.iluaL.} 526 | proc checkudata*(L: PState; ud: cint; tname: cstring): pointer {.iluaL.} 527 | proc where*(L: PState; lvl: cint) {.iluaL.} 528 | proc error*(L: PState; fmt: cstring): cint {.varargs, iluaL.} 529 | proc checkoption*(L: PState; arg: cint; def: cstring; lst: var cstring): cint {.iluaL.} 530 | proc fileresult*(L: PState; stat: cint; fname: cstring): cint {.iluaL.} 531 | proc execresult*(L: PState; stat: cint): cint {.iluaL.} 532 | 533 | # pre-defined references 534 | const 535 | LUA_NOREF* = (- 2) 536 | LUA_REFNIL* = (- 1) 537 | proc luaref*(L: PState; t: cint): cint {.iluaL, importc:"luaL_ref".} 538 | proc unref*(L: PState; t: cint; iref: cint) {.iluaL.} 539 | proc loadfilex*(L: PState; filename: cstring; mode: cstring): cint {.iluaL.} 540 | proc loadfile*(L: PState; filename: cstring): cint = L.loadfilex(filename, nil) 541 | 542 | proc loadbufferx*(L: PState; buff: cstring; sz: csize; name, mode: cstring): cint {.iluaL.} 543 | proc loadstring*(L: PState; s: cstring): cint {.iluaL.} 544 | proc newstate*(): PState {.iluaL.} 545 | proc llen*(L: PState; idx: cint): cint {.iluaL, importc:"luaL_len".} 546 | proc gsub*(L: PState; s: cstring; p: cstring; r: cstring): cstring {.iluaL.} 547 | proc setfuncs*(L: PState; L2: ptr luaL_Reg; nup: cint) {.iluaL.} 548 | proc getsubtable*(L: PState; idx: cint; fname: cstring): cint {.iluaL.} 549 | proc traceback*(L: PState; L1: PState; msg: cstring; level: cint) {.iluaL.} 550 | proc requiref*(L: PState; modname: cstring; openf: TCFunction; glb: cint) {.iluaL.} 551 | # 552 | #* =============================================================== 553 | #* some useful macros 554 | #* =============================================================== 555 | # 556 | 557 | proc newlibtable*(L: PState, arr: openArray[luaL_Reg]){.inline.} = 558 | createtable(L, 0, (arr.len - 1).cint) 559 | 560 | proc newlib*(L: PState, arr: var openArray[luaL_Reg]) {.inline.} = 561 | newlibtable(L, arr) 562 | setfuncs(L, cast[ptr luaL_reg](addr(arr)), 0) 563 | 564 | proc argcheck*(L: PState, cond: bool, numarg: int, extramsg: string) {.inline.} = 565 | if not cond: discard L.argerror(numarg.cint, extramsg) 566 | 567 | proc checkstring*(L: PState, n: int): cstring {.inline.} = L.checklstring(n.cint, nil) 568 | proc optstring*(L: PState, n: int, d: string): cstring {.inline.} = L.optlstring(n.cint, d, nil) 569 | 570 | proc checkint*(L: PState, n: lua_Integer): lua_Integer {.inline.} = L.checkinteger(n.cint) 571 | proc optint*(L: PState, n, d: lua_Integer): lua_Integer {.inline.} = L.optinteger(n.cint, d) 572 | proc checklong*(L: PState, n: int, d: clong): clong {.inline.} = cast[clong](L.checkinteger(n.cint)) 573 | proc optlong*(L: PState, n: int, d: lua_Integer): clong = cast[clong](L.optinteger(n.cint, d)) 574 | 575 | proc Ltypename*(L: PState, i: cint): cstring {.inline.} = 576 | L.typename(L.luatype(i)) 577 | 578 | proc dofile*(L: PState, file: string): cint {.inline, discardable.} = 579 | result = L.loadfile(file) or L.pcall(0, LUA_MULTRET, 0) 580 | 581 | proc dostring*(L: PState, s: string): cint {.inline, discardable.} = 582 | result = L.loadstring(s) or L.pcall(0, LUA_MULTRET, 0) 583 | 584 | proc getmetatable*(L: PState, s: string) {.inline.} = 585 | L.getfield(LUA_REGISTRYINDEX, s) 586 | 587 | template opt*(L: PState, f: TCFunction, n, d: typed): typed = 588 | if L.isnoneornil(n): d else: L.f(n) 589 | 590 | proc loadbuffer*(L: PState, buff: string, name: string): cint = 591 | L.loadbufferx(buff, buff.len, name, nil) 592 | 593 | # 594 | #@@ TBufferSIZE is the buffer size used by the lauxlib buffer system. 595 | #* CHANGE it if it uses too much C-stack space. 596 | # 597 | const 598 | Lua_BufferSIZE* = 8192'i32 # BUFSIZ\ 599 | ## COULD NOT FIND BUFSIZE ?? on my machine this is 8192 600 | # 601 | #* {====================================================== 602 | #* Generic Buffer manipulation 603 | #* ======================================================= 604 | # 605 | type 606 | PBuffer* = ptr TBuffer 607 | TBuffer* {.pure, final.} = object 608 | b*: cstring # buffer address 609 | size*: csize # buffer size 610 | n*: csize # number of characters in buffer 611 | L*: PState 612 | initb*: array[Lua_BufferSIZE, char] # initial buffer 613 | 614 | proc buffinit*(L: PState; B: PBuffer) {.iluaL.} 615 | proc prepbuffsize*(B: PBuffer; sz: csize): cstring {.iluaL.} 616 | proc addlstring*(B: PBuffer; s: cstring; len: csize) {.iluaL.} 617 | proc addstring*(B: PBuffer; s: cstring) {.iluaL.} 618 | proc addvalue*(B: PBuffer) {.iluaL.} 619 | proc pushresult*(B: PBuffer) {.iluaL.} 620 | proc pushresultsize*(B: PBuffer; sz: csize) {.iluaL.} 621 | proc buffinitsize*(L: PState; B: PBuffer; sz: csize): cstring {.iluaL.} 622 | proc addchar*(B: PBuffer, c: char) = 623 | if B.n < B.size: discard B.prepbuffsize(1) 624 | B.b[B.n] = c 625 | inc B.n 626 | 627 | proc addsize*(B: PBuffer, s: int) {.inline.} = inc(B.n, s) 628 | proc prepbuffer*(B: PBuffer): cstring {.inline.} = prepbuffsize(B, Lua_BufferSIZE) 629 | 630 | # }====================================================== 631 | # 632 | #* {====================================================== 633 | #* File handles for IO library 634 | #* ======================================================= 635 | # 636 | # 637 | #* A file handle is a userdata with metatable 'LUA_FILEHANDLE' and 638 | #* initial structure 'luaL_Stream' (it may contain other fields 639 | #* after that initial structure). 640 | # 641 | const 642 | LUA_FILEHANDLE* = "FILE*" 643 | type 644 | luaL_Stream* {.pure, final.} = object 645 | f*: File # stream (NULL for incompletely created streams) 646 | closef*: TCFunction # to close stream (NULL for closed streams) 647 | 648 | # }====================================================== 649 | # compatibility with old module system 650 | when defined(LUA_COMPAT_MODULE): 651 | proc pushmodule*(L: PState; modname: cstring; sizehint: cint){.iluaL.} 652 | proc openlib*(L: PState; libname: cstring; ls: ptr luaL_Reg; nup: cint){.iluaL.} 653 | proc register*(L: PState, n: string, ls: var openArray[luaL_Reg]) {.inline.} = 654 | L.openlib(n, cast[ptr luaL_reg](addr(ls)), 0) 655 | 656 | when isMainModule: 657 | #import lua52 658 | import strutils 659 | 660 | echo "Starting Lua" 661 | var L = newState() 662 | 663 | proc myPanic(L: PState): cint {.cdecl.} = 664 | echo "panic" 665 | 666 | #discard L.atpanic(myPanic) 667 | 668 | var regs = [ 669 | luaL_Reg(name: "abc", fn: myPanic), 670 | luaL_Reg(name: nil, fn: nil) 671 | ] 672 | 673 | L.newlib(regs) 674 | L.setglobal("mylib") 675 | echo L.dostring("mylib.abc()") 676 | 677 | #echo "Loading libraries" 678 | #L.openLibs 679 | # 680 | #when defined (Lua_REPL): 681 | # import rdstdin 682 | # echo "To leave the REPL, hit ^D, type !!!, or call quit()" 683 | # 684 | # var line: string = "" 685 | # while readlineFromStdin ("> ", line): 686 | # 687 | # if line == "!!!": break 688 | # 689 | # let result = L.loadString(line).TThreadStatus 690 | # if result == Thread_OK: 691 | # let result = L.pcall(0, LUA_MULTRET, 0).TThreadStatus 692 | # case result 693 | # of Thread_OK: discard 694 | # else: echo result 695 | # else: 696 | # echo result 697 | # 698 | #else: 699 | # proc testFunc (L: PState): cint {.cdecl.} = 700 | # #foo 701 | # echo "Hello thar" 702 | # 703 | # echo "Setting testFunc" 704 | # L.pushCfunction testFunc 705 | # L.setGlobal "testFunc" 706 | # 707 | # const LuaScript = "testFunc()" 708 | # echo "Loading script: \"\"\"\L$1\L\"\"\"".format(LuaScript) 709 | # 710 | # let result = L.loadString(LuaScript).TThreadStatus 711 | # echo "return: ", result 712 | # 713 | # if result == Thread_OK: 714 | # echo "Running script" 715 | # let result = L.pcall (0, LUA_MULTRET, 0) 716 | 717 | echo "Closing Lua state" 718 | #L.close 719 | 720 | 721 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | --function View:onClick() 2 | -- print("my name is: ", self.name) 3 | --end 4 | 5 | local lay = getLayout() 6 | local root = lay:getRoot() 7 | 8 | local yy = root:findChild(lay:getIdent("year")) 9 | 10 | local _prev = yy:findChild(lay:getIdent("_prev")) 11 | local _next = yy:findChild(lay:getIdent("_next")) 12 | local _year = yy:findChild(lay:getIdent("year")) 13 | print("name: ", yy.name, _prev.name, _year.name, _next.name) 14 | print("- top:", yy.top, _prev.top, _year.top, _next.top) 15 | print("- bot:", yy.bottom, _prev.bottom, _year.bottom, _next.bottom) 16 | print("- lef:", yy.left, _prev.left, _year.left, _next.left) 17 | print("- rig:", yy.right, _prev.right, _year.right, _next.right) 18 | 19 | local mm = root:findChild(lay:getIdent("months")) 20 | print("name: ", mm.name) 21 | print("- top:", mm.top) 22 | print("- bot:", mm.bottom) 23 | print("- lef:", mm.left) 24 | print("- rig:", mm.right) 25 | 26 | local _1 = mm:findChild(lay:getIdent("_1")) 27 | local _2 = mm:findChild(lay:getIdent("_2")) 28 | local _3 = mm:findChild(lay:getIdent("_3")) 29 | local _4 = mm:findChild(lay:getIdent("_4")) 30 | --print("name: ", _1.name, _2.name, _3.name, _4.name) 31 | --print("- top:", _1.top, _2.top, _3.top, _4.top) 32 | --print("- bot:", _1.bottom, _2.bottom, _3.bottom, _4.bottom) 33 | --print("- lef:", _1.left, _2.left, _3.left, _4.left) 34 | --print("- rig:", _1.right, _2.right, _3.right, _4.right) 35 | 36 | local _5 = mm:findChild(lay:getIdent("_5")) 37 | local _6 = mm:findChild(lay:getIdent("_6")) 38 | local _7 = mm:findChild(lay:getIdent("_7")) 39 | local _8 = mm:findChild(lay:getIdent("_8")) 40 | --print("name: ", _5.name, _6.name, _7.name, _8.name) 41 | --print("- top:", _5.top, _6.top, _7.top, _8.top) 42 | --print("- bot:", _5.bottom, _6.bottom, _7.bottom, _8.bottom) 43 | --print("- lef:", _5.left, _6.left, _7.left, _8.left) 44 | --print("- rig:", _5.right, _6.right, _7.right, _8.right) 45 | -- 46 | local _9 = mm:findChild(lay:getIdent("_9")) 47 | local _10 = mm:findChild(lay:getIdent("_10")) 48 | local _11 = mm:findChild(lay:getIdent("_11")) 49 | local _12 = mm:findChild(lay:getIdent("_12")) 50 | --print("name: ", _9.name, _10.name, _11.name, _12.name) 51 | --print("- top:", _9.top, _10.top, _11.top, _12.top) 52 | --print("- bot:", _9.bottom, _10.bottom, _11.bottom, _12.bottom) 53 | --print("- lef:", _9.left, _10.left, _11.left, _12.left) 54 | --print("- rig:", _9.right, _10.right, _11.right, _12.right) 55 | 56 | 57 | function nvg:drawButton(x, y, w, h, col, text) 58 | local vg = self 59 | local cornerRadius = 4.0 60 | local bg = vg:linearGradient(x,y,x,y+h, nvg.RGBA(255,255,255,32), nvg.RGBA(0,0,0,32)) 61 | local tw = vg:textBounds(0,0, text) 62 | 63 | vg:beginPath() 64 | vg:roundedRect(x+1,y+1, w-2,h-2, cornerRadius-1) 65 | vg:fillColor(col) 66 | vg:fill() 67 | vg:fillPaint(bg) 68 | vg:fill() 69 | 70 | vg:beginPath() 71 | vg:roundedRect(x+0.5,y+0.5, w-1,h-1, cornerRadius-0.5) 72 | vg:strokeColor(nvg.RGBA(0,0,0,48)) 73 | vg:stroke() 74 | 75 | vg:fontSize(20.0) 76 | vg:fontFace("sans-bold") 77 | vg:textAlign(nvg.ALIGN_LEFT + nvg.ALIGN_MIDDLE) 78 | vg:fillColor(nvg.RGBA(0,0,0,160)) 79 | vg:text(x+w*0.5-tw*0.5,y+h*0.5-1,text) 80 | vg:fillColor(nvg.RGBA(255,255,255,160)) 81 | vg:text(x+w*0.5-tw*0.5,y+h*0.5,text) 82 | end 83 | 84 | function updateScene() 85 | local vg = getNVG() 86 | local red = nvg.RGBA(128,16,8,255) 87 | 88 | vg:beginFrame(640, 480, 1.0) 89 | vg:drawButton(_prev.left, _prev.top, _prev.width, _prev.height, red, _prev.name) 90 | vg:drawButton(_next.left, _next.top, _next.width, _next.height, red, _next.name) 91 | vg:drawButton(_year.left, _year.top, _year.width, _year.height, red, _year.name) 92 | 93 | vg:drawButton(_1.left, _1.top, _1.width, _1.height, red, _1.name) 94 | vg:drawButton(_2.left, _2.top, _2.width, _2.height, red, _2.name) 95 | vg:drawButton(_3.left, _3.top, _3.width, _3.height, red, _3.name) 96 | vg:drawButton(_4.left, _4.top, _4.width, _4.height, red, _4.name) 97 | vg:drawButton(_5.left, _5.top, _5.width, _5.height, red, _5.name) 98 | vg:drawButton(_6.left, _6.top, _6.width, _6.height, red, _6.name) 99 | vg:drawButton(_7.left, _7.top, _7.width, _7.height, red, _7.name) 100 | vg:drawButton(_8.left, _8.top, _8.width, _8.height, red, _8.name) 101 | vg:drawButton(_9.left, _9.top, _9.width, _9.height, red, _9.name) 102 | vg:drawButton(_10.left, _10.top, _10.width, _10.height, red, _10.name) 103 | vg:drawButton(_11.left, _11.top, _11.width, _11.height, red, _11.name) 104 | vg:drawButton(_12.left, _12.top, _12.width, _12.height, red, _12.name) 105 | 106 | vg:beginPath() 107 | vg:rect(mm.left, mm.top, mm.width, mm.height) 108 | vg:stroke(1.0, 0.0, 0.0, 1.0, 2.0) 109 | vg:endFrame() 110 | end -------------------------------------------------------------------------------- /main.raz: -------------------------------------------------------------------------------- 1 | box 2 | : 3 | content: "box"; visible: false 4 | bgColor: rgbf(0.1, 0.8, 0.3) 5 | 6 | @ 7 | left - parent.left = parent.right - right 8 | #top - parent.top = parent.bottom - bottom 9 | centerY = parent.centerY 10 | height = child[0].height 11 | 12 | ::boxColor(content, color) 13 | : 14 | bgColor: color 15 | content: content 16 | 17 | ::boxStyle(h = 30, rot = 0) 18 | : rotate: rot 19 | @ 20 | top - parent.top = parent.bottom - bottom 21 | left = prev.right + 5 | parent.left 22 | right = next.left - 5 | parent.right 23 | width = 30; height = h 24 | 25 | ::clr(col) 26 | : bgColor: col 27 | 28 | box.box1::boxStyle::boxColor("1", rgbf(0.9, 0.2, 0.3)) 29 | box.box2::boxStyle::boxColor("2", rgbf(0.8, 0.3, 0.3)) 30 | box.box3::boxStyle::boxColor("3", rgbf(0.7, 0.4, 0.3)) 31 | box.box4::boxStyle::boxColor("4", rgbf(0.6, 0.5, 0.3)) 32 | box.box5::boxStyle::boxColor("5", rgbf(0.5, 0.6, 0.3)) 33 | box.box6::boxStyle::boxColor("6", rgbf(0.4, 0.7, 0.3)) 34 | box.box7::boxStyle::boxColor("7", rgbf(0.3, 0.8, 0.3)) 35 | box.box8::boxStyle::boxColor("8", rgbf(0.2, 0.9, 0.3)) 36 | 37 | % anim1 0.5 38 | box.box1::boxStyle(90, 360) elasticEaseOut 39 | box.box2::boxStyle(80, 360) backEaseOut 40 | box.box3::boxStyle(70, 360) bounceEaseOut 41 | box.box4::boxStyle(60, 360) spring 42 | box.box5::boxStyle(50, 360) spring 43 | box.box6::boxStyle(40, 360) spring 44 | box.box7::boxStyle(30, 360) spring 45 | box.box8::boxStyle(20, 360) spring 46 | 47 | % anim2 0.3 48 | box.box1::boxStyle::clr(rgbf(0.1, 0.7, 0.3)) elasticEaseIn 49 | box.box2::boxStyle::clr(rgbf(0.1, 0.4, 0.6)) backEaseIn 50 | box.box3::boxStyle::clr(rgbf(0.1, 0.3, 0.9)) bounceEaseIn 51 | -------------------------------------------------------------------------------- /namedcolors.nim: -------------------------------------------------------------------------------- 1 | const NamedColors* = { 2 | "IndianRed": 0xCD5C5C'u32, 3 | "LightCoral": 0xF08080'u32, 4 | "Salmon": 0xFA8072'u32, 5 | "DarkSalmon": 0xE9967A'u32, 6 | "LightSalmon": 0xFFA07A'u32, 7 | "Crimson": 0xDC143C'u32, 8 | "Red": 0xFF0000'u32, 9 | "FireBrick": 0xB22222'u32, 10 | "DarkRed": 0x8B0000'u32, 11 | "Pink": 0xFFC0CB'u32, 12 | "LightPink": 0xFFB6C1'u32, 13 | "HotPink": 0xFF69B4'u32, 14 | "DeepPink": 0xFF1493'u32, 15 | "MediumVioletRed": 0xC71585'u32, 16 | "PaleVioletRed": 0xDB7093'u32, 17 | "LightSalmon": 0xFFA07A'u32, 18 | "Coral": 0xFF7F50'u32, 19 | "Tomato": 0xFF6347'u32, 20 | "OrangeRed": 0xFF4500'u32, 21 | "DarkOrange": 0xFF8C00'u32, 22 | "Orange": 0xFFA500'u32, 23 | "Gold": 0xFFD700'u32, 24 | "Yellow": 0xFFFF00'u32, 25 | "LightYellow": 0xFFFFE0'u32, 26 | "LemonChiffon": 0xFFFACD'u32, 27 | "LightGoldenrodYellow": 0xFAFAD2'u32, 28 | "PapayaWhip": 0xFFEFD5'u32, 29 | "Moccasin": 0xFFE4B5'u32, 30 | "PeachPuff": 0xFFDAB9'u32, 31 | "PaleGoldenrod": 0xEEE8AA'u32, 32 | "Khaki": 0xF0E68C'u32, 33 | "DarkKhaki": 0xBDB76B'u32, 34 | "Lavender": 0xE6E6FA'u32, 35 | "Thistle": 0xD8BFD8'u32, 36 | "Plum": 0xDDA0DD'u32, 37 | "Violet": 0xEE82EE'u32, 38 | "Orchid": 0xDA70D6'u32, 39 | "Fuchsia": 0xFF00FF'u32, 40 | "Magenta": 0xFF00FF'u32, 41 | "MediumOrchid": 0xBA55D3'u32, 42 | "MediumPurple": 0x9370DB'u32, 43 | "Amethyst": 0x9966CC'u32, 44 | "BlueViolet": 0x8A2BE2'u32, 45 | "DarkViolet": 0x9400D3'u32, 46 | "DarkOrchid": 0x9932CC'u32, 47 | "DarkMagenta": 0x8B008B'u32, 48 | "Purple": 0x800080'u32, 49 | "Indigo": 0x4B0082'u32, 50 | "SlateBlue": 0x6A5ACD'u32, 51 | "DarkSlateBlue": 0x483D8B'u32, 52 | "MediumSlateBlue": 0x7B68EE'u32, 53 | "GreenYellow": 0xADFF2F'u32, 54 | "Chartreuse": 0x7FFF00'u32, 55 | "LawnGreen": 0x7CFC00'u32, 56 | "Lime": 0x00FF00'u32, 57 | "LimeGreen": 0x32CD32'u32, 58 | "PaleGreen": 0x98FB98'u32, 59 | "LightGreen": 0x90EE90'u32, 60 | "MediumSpringGreen": 0x00FA9A'u32, 61 | "SpringGreen": 0x00FF7F'u32, 62 | "MediumSeaGreen": 0x3CB371'u32, 63 | "SeaGreen": 0x2E8B57'u32, 64 | "ForestGreen": 0x228B22'u32, 65 | "Green": 0x008000'u32, 66 | "DarkGreen": 0x006400'u32, 67 | "YellowGreen": 0x9ACD32'u32, 68 | "OliveDrab": 0x6B8E23'u32, 69 | "Olive": 0x808000'u32, 70 | "DarkOliveGreen": 0x556B2F'u32, 71 | "MediumAquamarine": 0x66CDAA'u32, 72 | "DarkSeaGreen": 0x8FBC8F'u32, 73 | "LightSeaGreen": 0x20B2AA'u32, 74 | "DarkCyan": 0x008B8B'u32, 75 | "Teal": 0x008080'u32, 76 | "Aqua": 0x00FFFF'u32, 77 | "Cyan": 0x00FFFF'u32, 78 | "LightCyan": 0xE0FFFF'u32, 79 | "PaleTurquoise": 0xAFEEEE'u32, 80 | "Aquamarine": 0x7FFFD4'u32, 81 | "Turquoise": 0x40E0D0'u32, 82 | "MediumTurquoise": 0x48D1CC'u32, 83 | "DarkTurquoise": 0x00CED1'u32, 84 | "CadetBlue": 0x5F9EA0'u32, 85 | "SteelBlue": 0x4682B4'u32, 86 | "LightSteelBlue": 0xB0C4DE'u32, 87 | "PowderBlue": 0xB0E0E6'u32, 88 | "LightBlue": 0xADD8E6'u32, 89 | "SkyBlue": 0x87CEEB'u32, 90 | "LightSkyBlue": 0x87CEFA'u32, 91 | "DeepSkyBlue": 0x00BFFF'u32, 92 | "DodgerBlue": 0x1E90FF'u32, 93 | "CornflowerBlue": 0x6495ED'u32, 94 | "MediumSlateBlue": 0x7B68EE'u32, 95 | "RoyalBlue": 0x4169E1'u32, 96 | "Blue": 0x0000FF'u32, 97 | "MediumBlue": 0x0000CD'u32, 98 | "DarkBlue": 0x00008B'u32, 99 | "Navy": 0x000080'u32, 100 | "MidnightBlue": 0x191970'u32, 101 | "Cornsilk": 0xFFF8DC'u32, 102 | "BlanchedAlmond": 0xFFEBCD'u32, 103 | "Bisque": 0xFFE4C4'u32, 104 | "NavajoWhite": 0xFFDEAD'u32, 105 | "Wheat": 0xF5DEB3'u32, 106 | "BurlyWood": 0xDEB887'u32, 107 | "Tan": 0xD2B48C'u32, 108 | "RosyBrown": 0xBC8F8F'u32, 109 | "SandyBrown": 0xF4A460'u32, 110 | "Goldenrod": 0xDAA520'u32, 111 | "DarkGoldenrod": 0xB8860B'u32, 112 | "Peru": 0xCD853F'u32, 113 | "Chocolate": 0xD2691E'u32, 114 | "SaddleBrown": 0x8B4513'u32, 115 | "Sienna": 0xA0522D'u32, 116 | "Brown": 0xA52A2A'u32, 117 | "Maroon": 0x800000'u32, 118 | "White": 0xFFFFFF'u32, 119 | "Snow": 0xFFFAFA'u32, 120 | "Honeydew": 0xF0FFF0'u32, 121 | "MintCream": 0xF5FFFA'u32, 122 | "Azure": 0xF0FFFF'u32, 123 | "AliceBlue": 0xF0F8FF'u32, 124 | "GhostWhite": 0xF8F8FF'u32, 125 | "WhiteSmoke": 0xF5F5F5'u32, 126 | "Seashell": 0xFFF5EE'u32, 127 | "Beige": 0xF5F5DC'u32, 128 | "OldLace": 0xFDF5E6'u32, 129 | "FloralWhite": 0xFFFAF0'u32, 130 | "Ivory": 0xFFFFF0'u32, 131 | "AntiqueWhite": 0xFAEBD7'u32, 132 | "Linen": 0xFAF0E6'u32, 133 | "LavenderBlush": 0xFFF0F5'u32, 134 | "MistyRose": 0xFFE4E1'u32, 135 | "Gainsboro": 0xDCDCDC'u32, 136 | "LightGrey": 0xD3D3D3'u32, 137 | "Silver": 0xC0C0C0'u32, 138 | "DarkGray": 0xA9A9A9'u32, 139 | "Gray": 0x808080'u32, 140 | "DimGray": 0x696969'u32, 141 | "LightSlateGray": 0x778899'u32, 142 | "SlateGray": 0x708090'u32, 143 | "DarkSlateGray": 0x2F4F4F'u32, 144 | "Black": 0x000000'u32 145 | } 146 | -------------------------------------------------------------------------------- /nanovg/load_glex.c: -------------------------------------------------------------------------------- 1 | #if defined(_WIN32) 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | #define WIN32_EXTRA_LEAN 5 | #include 6 | #include 7 | #include "GL\glext.h" 8 | #include 9 | 10 | PFNGLBLENDFUNCSEPARATEPROC glBlendFuncSeparate; 11 | PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog; 12 | PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog; 13 | PFNGLCREATEPROGRAMPROC glCreateProgram; 14 | PFNGLCREATESHADERPROC glCreateShader; 15 | PFNGLSHADERSOURCEPROC glShaderSource; 16 | PFNGLCOMPILESHADERPROC glCompileShader; 17 | PFNGLGETSHADERIVPROC glGetShaderiv; 18 | PFNGLATTACHSHADERPROC glAttachShader; 19 | PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation; 20 | PFNGLLINKPROGRAMPROC glLinkProgram; 21 | PFNGLGETPROGRAMIVPROC glGetProgramiv; 22 | PFNGLDELETEPROGRAMPROC glDeleteProgram; 23 | PFNGLDELETESHADERPROC glDeleteShader; 24 | PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation; 25 | PFNGLGENBUFFERSPROC glGenBuffers; 26 | PFNGLUNIFORM4FVPROC glUniform4fv; 27 | PFNGLSTENCILOPSEPARATEPROC glStencilOpSeparate; 28 | PFNGLUSEPROGRAMPROC glUseProgram; 29 | PFNGLACTIVETEXTUREPROC glActiveTexture; 30 | PFNGLBINDBUFFERPROC glBindBuffer; 31 | PFNGLBUFFERDATAPROC glBufferData; 32 | PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray; 33 | PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer; 34 | PFNGLUNIFORM1IPROC glUniform1i; 35 | PFNGLUNIFORM2FVPROC glUniform2fv; 36 | PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray; 37 | PFNGLDELETEBUFFERSPROC glDeleteBuffers; 38 | 39 | PFNGLGETUNIFORMBLOCKINDEXPROC glGetUniformBlockIndex; 40 | PFNGLGENVERTEXARRAYSPROC glGenVertexArrays; 41 | PFNGLUNIFORMBLOCKBINDINGPROC glUniformBlockBinding; 42 | PFNGLGENERATEMIPMAPPROC glGenerateMipmap; 43 | PFNGLBINDBUFFERRANGEPROC glBindBufferRange; 44 | PFNGLBINDVERTEXARRAYPROC glBindVertexArray; 45 | PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays; 46 | 47 | #define LOADPFNGL(GLVAR, PFNGL, FNNAME) \ 48 | GLVAR = (PFNGL) wglGetProcAddress(FNNAME); \ 49 | if(GLVAR == NULL) { \ 50 | printf(stderr, "`%s` cannot be loaded\n", FNNAME); \ 51 | numErr++; \ 52 | } 53 | 54 | #endif 55 | 56 | void load_glex() { 57 | int numErr = 0; 58 | 59 | #if defined(_WIN32) 60 | LOADPFNGL(glBlendFuncSeparate, PFNGLBLENDFUNCSEPARATEPROC, "glBlendFuncSeparate"); 61 | LOADPFNGL(glGetShaderInfoLog, PFNGLGETSHADERINFOLOGPROC, "glGetShaderInfoLog"); 62 | LOADPFNGL(glGetProgramInfoLog, PFNGLGETPROGRAMINFOLOGPROC, "glGetProgramInfoLog"); 63 | LOADPFNGL(glCreateProgram, PFNGLCREATEPROGRAMPROC, "glCreateProgram"); 64 | LOADPFNGL(glCreateShader, PFNGLCREATESHADERPROC, "glCreateShader"); 65 | LOADPFNGL(glShaderSource, PFNGLSHADERSOURCEPROC, "glShaderSource"); 66 | LOADPFNGL(glCompileShader, PFNGLCOMPILESHADERPROC, "glCompileShader"); 67 | LOADPFNGL(glGetShaderiv, PFNGLGETSHADERIVPROC, "glGetShaderiv"); 68 | LOADPFNGL(glAttachShader, PFNGLATTACHSHADERPROC, "glAttachShader"); 69 | LOADPFNGL(glBindAttribLocation, PFNGLBINDATTRIBLOCATIONPROC, "glBindAttribLocation"); 70 | LOADPFNGL(glLinkProgram, PFNGLLINKPROGRAMPROC, "glLinkProgram"); 71 | LOADPFNGL(glGetProgramiv, PFNGLGETPROGRAMIVPROC, "glGetProgramiv"); 72 | LOADPFNGL(glDeleteProgram, PFNGLDELETEPROGRAMPROC, "glDeleteProgram"); 73 | LOADPFNGL(glDeleteShader, PFNGLDELETESHADERPROC, "glDeleteShader"); 74 | LOADPFNGL(glGetUniformLocation, PFNGLGETUNIFORMLOCATIONPROC, "glGetUniformLocation"); 75 | LOADPFNGL(glGenBuffers, PFNGLGENBUFFERSPROC, "glGenBuffers"); 76 | LOADPFNGL(glUniform4fv, PFNGLUNIFORM4FVPROC, "glUniform4fv"); 77 | LOADPFNGL(glStencilOpSeparate, PFNGLSTENCILOPSEPARATEPROC, "glStencilOpSeparate"); 78 | LOADPFNGL(glUseProgram, PFNGLUSEPROGRAMPROC, "glUseProgram"); 79 | LOADPFNGL(glActiveTexture, PFNGLACTIVETEXTUREPROC, "glActiveTexture"); 80 | LOADPFNGL(glBindBuffer, PFNGLBINDBUFFERPROC, "glBindBuffer"); 81 | LOADPFNGL(glBufferData, PFNGLBUFFERDATAPROC, "glBufferData"); 82 | LOADPFNGL(glEnableVertexAttribArray, PFNGLENABLEVERTEXATTRIBARRAYPROC, "glEnableVertexAttribArray"); 83 | LOADPFNGL(glVertexAttribPointer, PFNGLVERTEXATTRIBPOINTERPROC, "glVertexAttribPointer"); 84 | LOADPFNGL(glUniform1i, PFNGLUNIFORM1IPROC, "glUniform1i"); 85 | LOADPFNGL(glUniform2fv, PFNGLUNIFORM2FVPROC, "glUniform2fv"); 86 | LOADPFNGL(glDisableVertexAttribArray, PFNGLDISABLEVERTEXATTRIBARRAYPROC, "glDisableVertexAttribArray"); 87 | LOADPFNGL(glDeleteBuffers, PFNGLDELETEBUFFERSPROC, "glDeleteBuffers"); 88 | 89 | LOADPFNGL(glGetUniformBlockIndex, PFNGLGETUNIFORMBLOCKINDEXPROC, "glGetUniformBlockIndex"); 90 | LOADPFNGL(glGenVertexArrays, PFNGLGENVERTEXARRAYSPROC, "glGenVertexArrays"); 91 | LOADPFNGL(glUniformBlockBinding, PFNGLUNIFORMBLOCKBINDINGPROC, "glUniformBlockBinding"); 92 | LOADPFNGL(glGenerateMipmap, PFNGLGENERATEMIPMAPPROC, "glGenerateMipmap"); 93 | LOADPFNGL(glBindBufferRange, PFNGLBINDBUFFERRANGEPROC, "glBindBufferRange"); 94 | LOADPFNGL(glBindVertexArray, PFNGLBINDVERTEXARRAYPROC, "glBindVertexArray"); 95 | LOADPFNGL(glDeleteVertexArrays, PFNGLDELETEVERTEXARRAYSPROC, "glDeleteVertexArrays"); 96 | #endif 97 | 98 | if(numErr > 0) exit(1); 99 | } 100 | -------------------------------------------------------------------------------- /nanovg/load_glex.h: -------------------------------------------------------------------------------- 1 | #ifndef load_glex_h 2 | #define load_glex_h 3 | 4 | #include "GL/glext.h" 5 | 6 | extern PFNGLBLENDFUNCSEPARATEPROC glBlendFuncSeparate; 7 | extern PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog; 8 | extern PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog; 9 | extern PFNGLCREATEPROGRAMPROC glCreateProgram; 10 | extern PFNGLCREATESHADERPROC glCreateShader; 11 | extern PFNGLSHADERSOURCEPROC glShaderSource; 12 | extern PFNGLCOMPILESHADERPROC glCompileShader; 13 | extern PFNGLGETSHADERIVPROC glGetShaderiv; 14 | extern PFNGLATTACHSHADERPROC glAttachShader; 15 | extern PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation; 16 | extern PFNGLLINKPROGRAMPROC glLinkProgram; 17 | extern PFNGLGETPROGRAMIVPROC glGetProgramiv; 18 | extern PFNGLDELETEPROGRAMPROC glDeleteProgram; 19 | extern PFNGLDELETESHADERPROC glDeleteShader; 20 | extern PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation; 21 | extern PFNGLGENBUFFERSPROC glGenBuffers; 22 | extern PFNGLUNIFORM4FVPROC glUniform4fv; 23 | extern PFNGLSTENCILOPSEPARATEPROC glStencilOpSeparate; 24 | extern PFNGLUSEPROGRAMPROC glUseProgram; 25 | extern PFNGLACTIVETEXTUREPROC glActiveTexture; 26 | extern PFNGLBINDBUFFERPROC glBindBuffer; 27 | extern PFNGLBUFFERDATAPROC glBufferData; 28 | extern PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray; 29 | extern PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer; 30 | extern PFNGLUNIFORM1IPROC glUniform1i; 31 | extern PFNGLUNIFORM2FVPROC glUniform2fv; 32 | extern PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray; 33 | extern PFNGLDELETEBUFFERSPROC glDeleteBuffers; 34 | 35 | extern PFNGLGETUNIFORMBLOCKINDEXPROC glGetUniformBlockIndex; 36 | extern PFNGLGENVERTEXARRAYSPROC glGenVertexArrays; 37 | extern PFNGLUNIFORMBLOCKBINDINGPROC glUniformBlockBinding; 38 | extern PFNGLGENERATEMIPMAPPROC glGenerateMipmap; 39 | extern PFNGLBINDBUFFERRANGEPROC glBindBufferRange; 40 | extern PFNGLBINDVERTEXARRAYPROC glBindVertexArray; 41 | extern PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays; 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /nanovg/nanovg.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2013 Mikko Mononen memon@inside.org 3 | // 4 | // This software is provided 'as-is', without any express or implied 5 | // warranty. In no event will the authors be held liable for any damages 6 | // arising from the use of this software. 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 1. The origin of this software must not be misrepresented; you must not 11 | // claim that you wrote the original software. If you use this software 12 | // in a product, an acknowledgment in the product documentation would be 13 | // appreciated but is not required. 14 | // 2. Altered source versions must be plainly marked as such, and must not be 15 | // misrepresented as being the original software. 16 | // 3. This notice may not be removed or altered from any source distribution. 17 | // 18 | 19 | #ifndef NANOVG_H 20 | #define NANOVG_H 21 | 22 | #ifdef __cplusplus 23 | extern "C" { 24 | #endif 25 | 26 | #define NVG_PI 3.14159265358979323846264338327f 27 | 28 | #ifdef _MSC_VER 29 | #pragma warning(push) 30 | #pragma warning(disable: 4201) // nonstandard extension used : nameless struct/union 31 | #endif 32 | 33 | typedef struct NVGcontext NVGcontext; 34 | 35 | struct NVGcolor { 36 | union { 37 | float rgba[4]; 38 | struct { 39 | float r,g,b,a; 40 | }; 41 | }; 42 | }; 43 | typedef struct NVGcolor NVGcolor; 44 | 45 | struct NVGpaint { 46 | float xform[6]; 47 | float extent[2]; 48 | float radius; 49 | float feather; 50 | NVGcolor innerColor; 51 | NVGcolor outerColor; 52 | int image; 53 | }; 54 | typedef struct NVGpaint NVGpaint; 55 | 56 | enum NVGwinding { 57 | NVG_CCW = 1, // Winding for solid shapes 58 | NVG_CW = 2, // Winding for holes 59 | }; 60 | 61 | enum NVGsolidity { 62 | NVG_SOLID = 1, // CCW 63 | NVG_HOLE = 2, // CW 64 | }; 65 | 66 | enum NVGlineCap { 67 | NVG_BUTT, 68 | NVG_ROUND, 69 | NVG_SQUARE, 70 | NVG_BEVEL, 71 | NVG_MITER, 72 | }; 73 | 74 | enum NVGalign { 75 | // Horizontal align 76 | NVG_ALIGN_LEFT = 1<<0, // Default, align text horizontally to left. 77 | NVG_ALIGN_CENTER = 1<<1, // Align text horizontally to center. 78 | NVG_ALIGN_RIGHT = 1<<2, // Align text horizontally to right. 79 | // Vertical align 80 | NVG_ALIGN_TOP = 1<<3, // Align text vertically to top. 81 | NVG_ALIGN_MIDDLE = 1<<4, // Align text vertically to middle. 82 | NVG_ALIGN_BOTTOM = 1<<5, // Align text vertically to bottom. 83 | NVG_ALIGN_BASELINE = 1<<6, // Default, align text vertically to baseline. 84 | }; 85 | 86 | enum NVGblendFactor { 87 | NVG_ZERO = 1<<0, 88 | NVG_ONE = 1<<1, 89 | NVG_SRC_COLOR = 1<<2, 90 | NVG_ONE_MINUS_SRC_COLOR = 1<<3, 91 | NVG_DST_COLOR = 1<<4, 92 | NVG_ONE_MINUS_DST_COLOR = 1<<5, 93 | NVG_SRC_ALPHA = 1<<6, 94 | NVG_ONE_MINUS_SRC_ALPHA = 1<<7, 95 | NVG_DST_ALPHA = 1<<8, 96 | NVG_ONE_MINUS_DST_ALPHA = 1<<9, 97 | NVG_SRC_ALPHA_SATURATE = 1<<10, 98 | }; 99 | 100 | enum NVGcompositeOperation { 101 | NVG_SOURCE_OVER, 102 | NVG_SOURCE_IN, 103 | NVG_SOURCE_OUT, 104 | NVG_ATOP, 105 | NVG_DESTINATION_OVER, 106 | NVG_DESTINATION_IN, 107 | NVG_DESTINATION_OUT, 108 | NVG_DESTINATION_ATOP, 109 | NVG_LIGHTER, 110 | NVG_COPY, 111 | NVG_XOR, 112 | }; 113 | 114 | struct NVGcompositeOperationState { 115 | int srcRGB; 116 | int dstRGB; 117 | int srcAlpha; 118 | int dstAlpha; 119 | }; 120 | typedef struct NVGcompositeOperationState NVGcompositeOperationState; 121 | 122 | struct NVGglyphPosition { 123 | const char* str; // Position of the glyph in the input string. 124 | float x; // The x-coordinate of the logical glyph position. 125 | float minx, maxx; // The bounds of the glyph shape. 126 | }; 127 | typedef struct NVGglyphPosition NVGglyphPosition; 128 | 129 | struct NVGtextRow { 130 | const char* start; // Pointer to the input text where the row starts. 131 | const char* end; // Pointer to the input text where the row ends (one past the last character). 132 | const char* next; // Pointer to the beginning of the next row. 133 | float width; // Logical width of the row. 134 | float minx, maxx; // Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending. 135 | }; 136 | typedef struct NVGtextRow NVGtextRow; 137 | 138 | enum NVGimageFlags { 139 | NVG_IMAGE_GENERATE_MIPMAPS = 1<<0, // Generate mipmaps during creation of the image. 140 | NVG_IMAGE_REPEATX = 1<<1, // Repeat image in X direction. 141 | NVG_IMAGE_REPEATY = 1<<2, // Repeat image in Y direction. 142 | NVG_IMAGE_FLIPY = 1<<3, // Flips (inverses) image in Y direction when rendered. 143 | NVG_IMAGE_PREMULTIPLIED = 1<<4, // Image data has premultiplied alpha. 144 | NVG_IMAGE_NEAREST = 1<<5, // Image interpolation is Nearest instead Linear 145 | }; 146 | 147 | // Begin drawing a new frame 148 | // Calls to nanovg drawing API should be wrapped in nvgBeginFrame() & nvgEndFrame() 149 | // nvgBeginFrame() defines the size of the window to render to in relation currently 150 | // set viewport (i.e. glViewport on GL backends). Device pixel ration allows to 151 | // control the rendering on Hi-DPI devices. 152 | // For example, GLFW returns two dimension for an opened window: window size and 153 | // frame buffer size. In that case you would set windowWidth/Height to the window size 154 | // devicePixelRatio to: frameBufferWidth / windowWidth. 155 | void nvgBeginFrame(NVGcontext* ctx, int windowWidth, int windowHeight, float devicePixelRatio); 156 | 157 | // Cancels drawing the current frame. 158 | void nvgCancelFrame(NVGcontext* ctx); 159 | 160 | // Ends drawing flushing remaining render state. 161 | void nvgEndFrame(NVGcontext* ctx); 162 | 163 | // 164 | // Composite operation 165 | // 166 | // The composite operations in NanoVG are modeled after HTML Canvas API, and 167 | // the blend func is based on OpenGL (see corresponding manuals for more info). 168 | // The colors in the blending state have premultiplied alpha. 169 | 170 | // Sets the composite operation. The op parameter should be one of NVGcompositeOperation. 171 | void nvgGlobalCompositeOperation(NVGcontext* ctx, int op); 172 | 173 | // Sets the composite operation with custom pixel arithmetic. The parameters should be one of NVGblendFactor. 174 | void nvgGlobalCompositeBlendFunc(NVGcontext* ctx, int sfactor, int dfactor); 175 | 176 | // Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately. The parameters should be one of NVGblendFactor. 177 | void nvgGlobalCompositeBlendFuncSeparate(NVGcontext* ctx, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha); 178 | 179 | // 180 | // Color utils 181 | // 182 | // Colors in NanoVG are stored as unsigned ints in ABGR format. 183 | 184 | // Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f). 185 | NVGcolor nvgRGB(unsigned char r, unsigned char g, unsigned char b); 186 | 187 | // Returns a color value from red, green, blue values. Alpha will be set to 1.0f. 188 | NVGcolor nvgRGBf(float r, float g, float b); 189 | 190 | 191 | // Returns a color value from red, green, blue and alpha values. 192 | NVGcolor nvgRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a); 193 | 194 | // Returns a color value from red, green, blue and alpha values. 195 | NVGcolor nvgRGBAf(float r, float g, float b, float a); 196 | 197 | 198 | // Linearly interpolates from color c0 to c1, and returns resulting color value. 199 | NVGcolor nvgLerpRGBA(NVGcolor c0, NVGcolor c1, float u); 200 | 201 | // Sets transparency of a color value. 202 | NVGcolor nvgTransRGBA(NVGcolor c0, unsigned char a); 203 | 204 | // Sets transparency of a color value. 205 | NVGcolor nvgTransRGBAf(NVGcolor c0, float a); 206 | 207 | // Returns color value specified by hue, saturation and lightness. 208 | // HSL values are all in range [0..1], alpha will be set to 255. 209 | NVGcolor nvgHSL(float h, float s, float l); 210 | 211 | // Returns color value specified by hue, saturation and lightness and alpha. 212 | // HSL values are all in range [0..1], alpha in range [0..255] 213 | NVGcolor nvgHSLA(float h, float s, float l, unsigned char a); 214 | 215 | // 216 | // State Handling 217 | // 218 | // NanoVG contains state which represents how paths will be rendered. 219 | // The state contains transform, fill and stroke styles, text and font styles, 220 | // and scissor clipping. 221 | 222 | // Pushes and saves the current render state into a state stack. 223 | // A matching nvgRestore() must be used to restore the state. 224 | void nvgSave(NVGcontext* ctx); 225 | 226 | // Pops and restores current render state. 227 | void nvgRestore(NVGcontext* ctx); 228 | 229 | // Resets current render state to default values. Does not affect the render state stack. 230 | void nvgReset(NVGcontext* ctx); 231 | 232 | // 233 | // Render styles 234 | // 235 | // Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern. 236 | // Solid color is simply defined as a color value, different kinds of paints can be created 237 | // using nvgLinearGradient(), nvgBoxGradient(), nvgRadialGradient() and nvgImagePattern(). 238 | // 239 | // Current render style can be saved and restored using nvgSave() and nvgRestore(). 240 | 241 | // Sets whether to draw antialias for nvgStroke() and nvgFill(). It's enabled by default. 242 | void nvgShapeAntiAlias(NVGcontext* ctx, int enabled); 243 | 244 | // Sets current stroke style to a solid color. 245 | void nvgStrokeColor(NVGcontext* ctx, NVGcolor color); 246 | 247 | // Sets current stroke style to a paint, which can be a one of the gradients or a pattern. 248 | void nvgStrokePaint(NVGcontext* ctx, NVGpaint paint); 249 | 250 | // Sets current fill style to a solid color. 251 | void nvgFillColor(NVGcontext* ctx, NVGcolor color); 252 | 253 | // Sets current fill style to a paint, which can be a one of the gradients or a pattern. 254 | void nvgFillPaint(NVGcontext* ctx, NVGpaint paint); 255 | 256 | // Sets the miter limit of the stroke style. 257 | // Miter limit controls when a sharp corner is beveled. 258 | void nvgMiterLimit(NVGcontext* ctx, float limit); 259 | 260 | // Sets the stroke width of the stroke style. 261 | void nvgStrokeWidth(NVGcontext* ctx, float size); 262 | 263 | // Sets how the end of the line (cap) is drawn, 264 | // Can be one of: NVG_BUTT (default), NVG_ROUND, NVG_SQUARE. 265 | void nvgLineCap(NVGcontext* ctx, int cap); 266 | 267 | // Sets how sharp path corners are drawn. 268 | // Can be one of NVG_MITER (default), NVG_ROUND, NVG_BEVEL. 269 | void nvgLineJoin(NVGcontext* ctx, int join); 270 | 271 | // Sets the transparency applied to all rendered shapes. 272 | // Already transparent paths will get proportionally more transparent as well. 273 | void nvgGlobalAlpha(NVGcontext* ctx, float alpha); 274 | 275 | // 276 | // Transforms 277 | // 278 | // The paths, gradients, patterns and scissor region are transformed by an transformation 279 | // matrix at the time when they are passed to the API. 280 | // The current transformation matrix is a affine matrix: 281 | // [sx kx tx] 282 | // [ky sy ty] 283 | // [ 0 0 1] 284 | // Where: sx,sy define scaling, kx,ky skewing, and tx,ty translation. 285 | // The last row is assumed to be 0,0,1 and is not stored. 286 | // 287 | // Apart from nvgResetTransform(), each transformation function first creates 288 | // specific transformation matrix and pre-multiplies the current transformation by it. 289 | // 290 | // Current coordinate system (transformation) can be saved and restored using nvgSave() and nvgRestore(). 291 | 292 | // Resets current transform to a identity matrix. 293 | void nvgResetTransform(NVGcontext* ctx); 294 | 295 | // Premultiplies current coordinate system by specified matrix. 296 | // The parameters are interpreted as matrix as follows: 297 | // [a c e] 298 | // [b d f] 299 | // [0 0 1] 300 | void nvgTransform(NVGcontext* ctx, float a, float b, float c, float d, float e, float f); 301 | 302 | // Translates current coordinate system. 303 | void nvgTranslate(NVGcontext* ctx, float x, float y); 304 | 305 | // Rotates current coordinate system. Angle is specified in radians. 306 | void nvgRotate(NVGcontext* ctx, float angle); 307 | 308 | // Skews the current coordinate system along X axis. Angle is specified in radians. 309 | void nvgSkewX(NVGcontext* ctx, float angle); 310 | 311 | // Skews the current coordinate system along Y axis. Angle is specified in radians. 312 | void nvgSkewY(NVGcontext* ctx, float angle); 313 | 314 | // Scales the current coordinate system. 315 | void nvgScale(NVGcontext* ctx, float x, float y); 316 | 317 | // Stores the top part (a-f) of the current transformation matrix in to the specified buffer. 318 | // [a c e] 319 | // [b d f] 320 | // [0 0 1] 321 | // There should be space for 6 floats in the return buffer for the values a-f. 322 | void nvgCurrentTransform(NVGcontext* ctx, float* xform); 323 | 324 | 325 | // The following functions can be used to make calculations on 2x3 transformation matrices. 326 | // A 2x3 matrix is represented as float[6]. 327 | 328 | // Sets the transform to identity matrix. 329 | void nvgTransformIdentity(float* dst); 330 | 331 | // Sets the transform to translation matrix matrix. 332 | void nvgTransformTranslate(float* dst, float tx, float ty); 333 | 334 | // Sets the transform to scale matrix. 335 | void nvgTransformScale(float* dst, float sx, float sy); 336 | 337 | // Sets the transform to rotate matrix. Angle is specified in radians. 338 | void nvgTransformRotate(float* dst, float a); 339 | 340 | // Sets the transform to skew-x matrix. Angle is specified in radians. 341 | void nvgTransformSkewX(float* dst, float a); 342 | 343 | // Sets the transform to skew-y matrix. Angle is specified in radians. 344 | void nvgTransformSkewY(float* dst, float a); 345 | 346 | // Sets the transform to the result of multiplication of two transforms, of A = A*B. 347 | void nvgTransformMultiply(float* dst, const float* src); 348 | 349 | // Sets the transform to the result of multiplication of two transforms, of A = B*A. 350 | void nvgTransformPremultiply(float* dst, const float* src); 351 | 352 | // Sets the destination to inverse of specified transform. 353 | // Returns 1 if the inverse could be calculated, else 0. 354 | int nvgTransformInverse(float* dst, const float* src); 355 | 356 | // Transform a point by given transform. 357 | void nvgTransformPoint(float* dstx, float* dsty, const float* xform, float srcx, float srcy); 358 | 359 | // Converts degrees to radians and vice versa. 360 | float nvgDegToRad(float deg); 361 | float nvgRadToDeg(float rad); 362 | 363 | // 364 | // Images 365 | // 366 | // NanoVG allows you to load jpg, png, psd, tga, pic and gif files to be used for rendering. 367 | // In addition you can upload your own image. The image loading is provided by stb_image. 368 | // The parameter imageFlags is combination of flags defined in NVGimageFlags. 369 | 370 | // Creates image by loading it from the disk from specified file name. 371 | // Returns handle to the image. 372 | int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags); 373 | 374 | // Creates image by loading it from the specified chunk of memory. 375 | // Returns handle to the image. 376 | int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata); 377 | 378 | // Creates image from specified image data. 379 | // Returns handle to the image. 380 | int nvgCreateImageRGBA(NVGcontext* ctx, int w, int h, int imageFlags, const unsigned char* data); 381 | 382 | // Updates image data specified by image handle. 383 | void nvgUpdateImage(NVGcontext* ctx, int image, const unsigned char* data); 384 | 385 | // Returns the dimensions of a created image. 386 | void nvgImageSize(NVGcontext* ctx, int image, int* w, int* h); 387 | 388 | // Deletes created image. 389 | void nvgDeleteImage(NVGcontext* ctx, int image); 390 | 391 | // 392 | // Paints 393 | // 394 | // NanoVG supports four types of paints: linear gradient, box gradient, radial gradient and image pattern. 395 | // These can be used as paints for strokes and fills. 396 | 397 | // Creates and returns a linear gradient. Parameters (sx,sy)-(ex,ey) specify the start and end coordinates 398 | // of the linear gradient, icol specifies the start color and ocol the end color. 399 | // The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). 400 | NVGpaint nvgLinearGradient(NVGcontext* ctx, float sx, float sy, float ex, float ey, 401 | NVGcolor icol, NVGcolor ocol); 402 | 403 | // Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering 404 | // drop shadows or highlights for boxes. Parameters (x,y) define the top-left corner of the rectangle, 405 | // (w,h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry 406 | // the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. 407 | // The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). 408 | NVGpaint nvgBoxGradient(NVGcontext* ctx, float x, float y, float w, float h, 409 | float r, float f, NVGcolor icol, NVGcolor ocol); 410 | 411 | // Creates and returns a radial gradient. Parameters (cx,cy) specify the center, inr and outr specify 412 | // the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. 413 | // The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). 414 | NVGpaint nvgRadialGradient(NVGcontext* ctx, float cx, float cy, float inr, float outr, 415 | NVGcolor icol, NVGcolor ocol); 416 | 417 | // Creates and returns an image patter. Parameters (ox,oy) specify the left-top location of the image pattern, 418 | // (ex,ey) the size of one image, angle rotation around the top-left corner, image is handle to the image to render. 419 | // The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). 420 | NVGpaint nvgImagePattern(NVGcontext* ctx, float ox, float oy, float ex, float ey, 421 | float angle, int image, float alpha); 422 | 423 | // 424 | // Scissoring 425 | // 426 | // Scissoring allows you to clip the rendering into a rectangle. This is useful for various 427 | // user interface cases like rendering a text edit or a timeline. 428 | 429 | // Sets the current scissor rectangle. 430 | // The scissor rectangle is transformed by the current transform. 431 | void nvgScissor(NVGcontext* ctx, float x, float y, float w, float h); 432 | 433 | // Intersects current scissor rectangle with the specified rectangle. 434 | // The scissor rectangle is transformed by the current transform. 435 | // Note: in case the rotation of previous scissor rect differs from 436 | // the current one, the intersection will be done between the specified 437 | // rectangle and the previous scissor rectangle transformed in the current 438 | // transform space. The resulting shape is always rectangle. 439 | void nvgIntersectScissor(NVGcontext* ctx, float x, float y, float w, float h); 440 | 441 | // Reset and disables scissoring. 442 | void nvgResetScissor(NVGcontext* ctx); 443 | 444 | // 445 | // Paths 446 | // 447 | // Drawing a new shape starts with nvgBeginPath(), it clears all the currently defined paths. 448 | // Then you define one or more paths and sub-paths which describe the shape. The are functions 449 | // to draw common shapes like rectangles and circles, and lower level step-by-step functions, 450 | // which allow to define a path curve by curve. 451 | // 452 | // NanoVG uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise 453 | // winding and holes should have counter clockwise order. To specify winding of a path you can 454 | // call nvgPathWinding(). This is useful especially for the common shapes, which are drawn CCW. 455 | // 456 | // Finally you can fill the path using current fill style by calling nvgFill(), and stroke it 457 | // with current stroke style by calling nvgStroke(). 458 | // 459 | // The curve segments and sub-paths are transformed by the current transform. 460 | 461 | // Clears the current path and sub-paths. 462 | void nvgBeginPath(NVGcontext* ctx); 463 | 464 | // Starts new sub-path with specified point as first point. 465 | void nvgMoveTo(NVGcontext* ctx, float x, float y); 466 | 467 | // Adds line segment from the last point in the path to the specified point. 468 | void nvgLineTo(NVGcontext* ctx, float x, float y); 469 | 470 | // Adds cubic bezier segment from last point in the path via two control points to the specified point. 471 | void nvgBezierTo(NVGcontext* ctx, float c1x, float c1y, float c2x, float c2y, float x, float y); 472 | 473 | // Adds quadratic bezier segment from last point in the path via a control point to the specified point. 474 | void nvgQuadTo(NVGcontext* ctx, float cx, float cy, float x, float y); 475 | 476 | // Adds an arc segment at the corner defined by the last path point, and two specified points. 477 | void nvgArcTo(NVGcontext* ctx, float x1, float y1, float x2, float y2, float radius); 478 | 479 | // Closes current sub-path with a line segment. 480 | void nvgClosePath(NVGcontext* ctx); 481 | 482 | // Sets the current sub-path winding, see NVGwinding and NVGsolidity. 483 | void nvgPathWinding(NVGcontext* ctx, int dir); 484 | 485 | // Creates new circle arc shaped sub-path. The arc center is at cx,cy, the arc radius is r, 486 | // and the arc is drawn from angle a0 to a1, and swept in direction dir (NVG_CCW, or NVG_CW). 487 | // Angles are specified in radians. 488 | void nvgArc(NVGcontext* ctx, float cx, float cy, float r, float a0, float a1, int dir); 489 | 490 | // Creates new rectangle shaped sub-path. 491 | void nvgRect(NVGcontext* ctx, float x, float y, float w, float h); 492 | 493 | // Creates new rounded rectangle shaped sub-path. 494 | void nvgRoundedRect(NVGcontext* ctx, float x, float y, float w, float h, float r); 495 | 496 | // Creates new rounded rectangle shaped sub-path with varying radii for each corner. 497 | void nvgRoundedRectVarying(NVGcontext* ctx, float x, float y, float w, float h, float radTopLeft, float radTopRight, float radBottomRight, float radBottomLeft); 498 | 499 | // Creates new ellipse shaped sub-path. 500 | void nvgEllipse(NVGcontext* ctx, float cx, float cy, float rx, float ry); 501 | 502 | // Creates new circle shaped sub-path. 503 | void nvgCircle(NVGcontext* ctx, float cx, float cy, float r); 504 | 505 | // Fills the current path with current fill style. 506 | void nvgFill(NVGcontext* ctx); 507 | 508 | // Fills the current path with current stroke style. 509 | void nvgStroke(NVGcontext* ctx); 510 | 511 | 512 | // 513 | // Text 514 | // 515 | // NanoVG allows you to load .ttf files and use the font to render text. 516 | // 517 | // The appearance of the text can be defined by setting the current text style 518 | // and by specifying the fill color. Common text and font settings such as 519 | // font size, letter spacing and text align are supported. Font blur allows you 520 | // to create simple text effects such as drop shadows. 521 | // 522 | // At render time the font face can be set based on the font handles or name. 523 | // 524 | // Font measure functions return values in local space, the calculations are 525 | // carried in the same resolution as the final rendering. This is done because 526 | // the text glyph positions are snapped to the nearest pixels sharp rendering. 527 | // 528 | // The local space means that values are not rotated or scale as per the current 529 | // transformation. For example if you set font size to 12, which would mean that 530 | // line height is 16, then regardless of the current scaling and rotation, the 531 | // returned line height is always 16. Some measures may vary because of the scaling 532 | // since aforementioned pixel snapping. 533 | // 534 | // While this may sound a little odd, the setup allows you to always render the 535 | // same way regardless of scaling. I.e. following works regardless of scaling: 536 | // 537 | // const char* txt = "Text me up."; 538 | // nvgTextBounds(vg, x,y, txt, NULL, bounds); 539 | // nvgBeginPath(vg); 540 | // nvgRoundedRect(vg, bounds[0],bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); 541 | // nvgFill(vg); 542 | // 543 | // Note: currently only solid color fill is supported for text. 544 | 545 | // Creates font by loading it from the disk from specified file name. 546 | // Returns handle to the font. 547 | int nvgCreateFont(NVGcontext* ctx, const char* name, const char* filename); 548 | 549 | // Creates font by loading it from the specified memory chunk. 550 | // Returns handle to the font. 551 | int nvgCreateFontMem(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData); 552 | 553 | // Finds a loaded font of specified name, and returns handle to it, or -1 if the font is not found. 554 | int nvgFindFont(NVGcontext* ctx, const char* name); 555 | 556 | // Adds a fallback font by handle. 557 | int nvgAddFallbackFontId(NVGcontext* ctx, int baseFont, int fallbackFont); 558 | 559 | // Adds a fallback font by name. 560 | int nvgAddFallbackFont(NVGcontext* ctx, const char* baseFont, const char* fallbackFont); 561 | 562 | // Sets the font size of current text style. 563 | void nvgFontSize(NVGcontext* ctx, float size); 564 | 565 | // Sets the blur of current text style. 566 | void nvgFontBlur(NVGcontext* ctx, float blur); 567 | 568 | // Sets the letter spacing of current text style. 569 | void nvgTextLetterSpacing(NVGcontext* ctx, float spacing); 570 | 571 | // Sets the proportional line height of current text style. The line height is specified as multiple of font size. 572 | void nvgTextLineHeight(NVGcontext* ctx, float lineHeight); 573 | 574 | // Sets the text align of current text style, see NVGalign for options. 575 | void nvgTextAlign(NVGcontext* ctx, int align); 576 | 577 | // Sets the font face based on specified id of current text style. 578 | void nvgFontFaceId(NVGcontext* ctx, int font); 579 | 580 | // Sets the font face based on specified name of current text style. 581 | void nvgFontFace(NVGcontext* ctx, const char* font); 582 | 583 | // Draws text string at specified location. If end is specified only the sub-string up to the end is drawn. 584 | float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* end); 585 | 586 | // Draws multi-line text string at specified location wrapped at the specified width. If end is specified only the sub-string up to the end is drawn. 587 | // White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 588 | // Words longer than the max width are slit at nearest character (i.e. no hyphenation). 589 | void nvgTextBox(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end); 590 | 591 | // Measures the specified text string. Parameter bounds should be a pointer to float[4], 592 | // if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] 593 | // Returns the horizontal advance of the measured text (i.e. where the next character should drawn). 594 | // Measured values are returned in local coordinate space. 595 | float nvgTextBounds(NVGcontext* ctx, float x, float y, const char* string, const char* end, float* bounds); 596 | 597 | // Measures the specified multi-text string. Parameter bounds should be a pointer to float[4], 598 | // if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] 599 | // Measured values are returned in local coordinate space. 600 | void nvgTextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end, float* bounds); 601 | 602 | // Calculates the glyph x positions of the specified text. If end is specified only the sub-string will be used. 603 | // Measured values are returned in local coordinate space. 604 | int nvgTextGlyphPositions(NVGcontext* ctx, float x, float y, const char* string, const char* end, NVGglyphPosition* positions, int maxPositions); 605 | 606 | // Returns the vertical metrics based on the current text style. 607 | // Measured values are returned in local coordinate space. 608 | void nvgTextMetrics(NVGcontext* ctx, float* ascender, float* descender, float* lineh); 609 | 610 | // Breaks the specified text into lines. If end is specified only the sub-string will be used. 611 | // White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 612 | // Words longer than the max width are slit at nearest character (i.e. no hyphenation). 613 | int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, float breakRowWidth, NVGtextRow* rows, int maxRows); 614 | 615 | // 616 | // Internal Render API 617 | // 618 | enum NVGtexture { 619 | NVG_TEXTURE_ALPHA = 0x01, 620 | NVG_TEXTURE_RGBA = 0x02, 621 | }; 622 | 623 | struct NVGscissor { 624 | float xform[6]; 625 | float extent[2]; 626 | }; 627 | typedef struct NVGscissor NVGscissor; 628 | 629 | struct NVGvertex { 630 | float x,y,u,v; 631 | }; 632 | typedef struct NVGvertex NVGvertex; 633 | 634 | struct NVGpath { 635 | int first; 636 | int count; 637 | unsigned char closed; 638 | int nbevel; 639 | NVGvertex* fill; 640 | int nfill; 641 | NVGvertex* stroke; 642 | int nstroke; 643 | int winding; 644 | int convex; 645 | }; 646 | typedef struct NVGpath NVGpath; 647 | 648 | struct NVGparams { 649 | void* userPtr; 650 | int edgeAntiAlias; 651 | int (*renderCreate)(void* uptr); 652 | int (*renderCreateTexture)(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data); 653 | int (*renderDeleteTexture)(void* uptr, int image); 654 | int (*renderUpdateTexture)(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data); 655 | int (*renderGetTextureSize)(void* uptr, int image, int* w, int* h); 656 | void (*renderViewport)(void* uptr, int width, int height, float devicePixelRatio); 657 | void (*renderCancel)(void* uptr); 658 | void (*renderFlush)(void* uptr); 659 | void (*renderFill)(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe, const float* bounds, const NVGpath* paths, int npaths); 660 | void (*renderStroke)(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe, float strokeWidth, const NVGpath* paths, int npaths); 661 | void (*renderTriangles)(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, const NVGvertex* verts, int nverts); 662 | void (*renderDelete)(void* uptr); 663 | }; 664 | typedef struct NVGparams NVGparams; 665 | 666 | // Constructor and destructor, called by the render back-end. 667 | NVGcontext* nvgCreateInternal(NVGparams* params); 668 | void nvgDeleteInternal(NVGcontext* ctx); 669 | 670 | NVGparams* nvgInternalParams(NVGcontext* ctx); 671 | 672 | // Debug function to dump cached path data. 673 | void nvgDebugDumpPathCache(NVGcontext* ctx); 674 | 675 | #ifdef _MSC_VER 676 | #pragma warning(pop) 677 | #endif 678 | 679 | #define NVG_NOTUSED(v) for (;;) { (void)(1 ? (void)0 : ( (void)(v) ) ); break; } 680 | 681 | #ifdef __cplusplus 682 | } 683 | #endif 684 | 685 | #endif // NANOVG_H 686 | -------------------------------------------------------------------------------- /nanovg/nanovg_gl_utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2009-2013 Mikko Mononen memon@inside.org 3 | // 4 | // This software is provided 'as-is', without any express or implied 5 | // warranty. In no event will the authors be held liable for any damages 6 | // arising from the use of this software. 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 1. The origin of this software must not be misrepresented; you must not 11 | // claim that you wrote the original software. If you use this software 12 | // in a product, an acknowledgment in the product documentation would be 13 | // appreciated but is not required. 14 | // 2. Altered source versions must be plainly marked as such, and must not be 15 | // misrepresented as being the original software. 16 | // 3. This notice may not be removed or altered from any source distribution. 17 | // 18 | #ifndef NANOVG_GL_UTILS_H 19 | #define NANOVG_GL_UTILS_H 20 | 21 | struct NVGLUframebuffer { 22 | NVGcontext* ctx; 23 | GLuint fbo; 24 | GLuint rbo; 25 | GLuint texture; 26 | int image; 27 | }; 28 | typedef struct NVGLUframebuffer NVGLUframebuffer; 29 | 30 | // Helper function to create GL frame buffer to render to. 31 | void nvgluBindFramebuffer(NVGLUframebuffer* fb); 32 | NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imageFlags); 33 | void nvgluDeleteFramebuffer(NVGLUframebuffer* fb); 34 | 35 | #endif // NANOVG_GL_UTILS_H 36 | 37 | #ifdef NANOVG_GL_IMPLEMENTATION 38 | 39 | #if defined(NANOVG_GL3) || defined(NANOVG_GLES2) || defined(NANOVG_GLES3) 40 | // FBO is core in OpenGL 3>. 41 | # define NANOVG_FBO_VALID 1 42 | #elif defined(NANOVG_GL2) 43 | // On OS X including glext defines FBO on GL2 too. 44 | # ifdef __APPLE__ 45 | # include 46 | # define NANOVG_FBO_VALID 1 47 | # endif 48 | #endif 49 | 50 | static GLint defaultFBO = -1; 51 | 52 | NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imageFlags) 53 | { 54 | #ifdef NANOVG_FBO_VALID 55 | GLint defaultFBO; 56 | GLint defaultRBO; 57 | NVGLUframebuffer* fb = NULL; 58 | 59 | glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO); 60 | glGetIntegerv(GL_RENDERBUFFER_BINDING, &defaultRBO); 61 | 62 | fb = (NVGLUframebuffer*)malloc(sizeof(NVGLUframebuffer)); 63 | if (fb == NULL) goto error; 64 | memset(fb, 0, sizeof(NVGLUframebuffer)); 65 | 66 | fb->image = nvgCreateImageRGBA(ctx, w, h, imageFlags | NVG_IMAGE_FLIPY | NVG_IMAGE_PREMULTIPLIED, NULL); 67 | 68 | #if defined NANOVG_GL2 69 | fb->texture = nvglImageHandleGL2(ctx, fb->image); 70 | #elif defined NANOVG_GL3 71 | fb->texture = nvglImageHandleGL3(ctx, fb->image); 72 | #elif defined NANOVG_GLES2 73 | fb->texture = nvglImageHandleGLES2(ctx, fb->image); 74 | #elif defined NANOVG_GLES3 75 | fb->texture = nvglImageHandleGLES3(ctx, fb->image); 76 | #endif 77 | 78 | fb->ctx = ctx; 79 | 80 | // frame buffer object 81 | glGenFramebuffers(1, &fb->fbo); 82 | glBindFramebuffer(GL_FRAMEBUFFER, fb->fbo); 83 | 84 | // render buffer object 85 | glGenRenderbuffers(1, &fb->rbo); 86 | glBindRenderbuffer(GL_RENDERBUFFER, fb->rbo); 87 | glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, w, h); 88 | 89 | // combine all 90 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->texture, 0); 91 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->rbo); 92 | 93 | if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) goto error; 94 | 95 | glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO); 96 | glBindRenderbuffer(GL_RENDERBUFFER, defaultRBO); 97 | return fb; 98 | error: 99 | glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO); 100 | glBindRenderbuffer(GL_RENDERBUFFER, defaultRBO); 101 | nvgluDeleteFramebuffer(fb); 102 | return NULL; 103 | #else 104 | NVG_NOTUSED(ctx); 105 | NVG_NOTUSED(w); 106 | NVG_NOTUSED(h); 107 | NVG_NOTUSED(imageFlags); 108 | return NULL; 109 | #endif 110 | } 111 | 112 | void nvgluBindFramebuffer(NVGLUframebuffer* fb) 113 | { 114 | #ifdef NANOVG_FBO_VALID 115 | if (defaultFBO == -1) glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO); 116 | glBindFramebuffer(GL_FRAMEBUFFER, fb != NULL ? fb->fbo : defaultFBO); 117 | #else 118 | NVG_NOTUSED(fb); 119 | #endif 120 | } 121 | 122 | void nvgluDeleteFramebuffer(NVGLUframebuffer* fb) 123 | { 124 | #ifdef NANOVG_FBO_VALID 125 | if (fb == NULL) return; 126 | if (fb->fbo != 0) 127 | glDeleteFramebuffers(1, &fb->fbo); 128 | if (fb->rbo != 0) 129 | glDeleteRenderbuffers(1, &fb->rbo); 130 | if (fb->image >= 0) 131 | nvgDeleteImage(fb->ctx, fb->image); 132 | fb->ctx = NULL; 133 | fb->fbo = 0; 134 | fb->rbo = 0; 135 | fb->texture = 0; 136 | fb->image = -1; 137 | free(fb); 138 | #else 139 | NVG_NOTUSED(fb); 140 | #endif 141 | } 142 | 143 | #endif // NANOVG_GL_IMPLEMENTATION 144 | -------------------------------------------------------------------------------- /parser.nim: -------------------------------------------------------------------------------- 1 | import lexer, lexbase, idents, ast, semcheck, keywords 2 | import streams, razcontext, types 3 | 4 | type 5 | Parser* = object 6 | currInd: int 7 | firstTok: bool 8 | hasProgress: bool 9 | lex*: Lexer 10 | tok*: Token 11 | emptyNode: Node 12 | 13 | proc getTok(p: var Parser) = 14 | p.tok.reset() 15 | p.lex.getToken(p.tok) 16 | while p.tok.kind in {tkComment, tkNestedComment}: 17 | p.lex.getToken(p.tok) 18 | p.hasProgress = true 19 | 20 | proc error(p: Parser, kind: MsgKind, args: varargs[string, `$`]) = 21 | var err = new(SourceError) 22 | err.msg = p.lex.context.msgKindToString(kind, args) 23 | err.line = p.tok.line 24 | err.column = p.tok.col 25 | err.lineContent = p.lex.getCurrentLine(false) 26 | err.fileIndex = p.lex.fileIndex 27 | raise err 28 | 29 | template withInd(p, body: untyped) = 30 | let oldInd = p.currInd 31 | p.currInd = p.tok.indent 32 | body 33 | p.currInd = oldInd 34 | 35 | template realInd(p): bool = p.tok.indent > p.currInd 36 | template sameInd(p): bool = p.tok.indent == p.currInd 37 | 38 | proc optPar(p: var Parser) = 39 | if p.tok.indent >= 0: 40 | if p.tok.indent < p.currInd: p.error(errInvalidIndentation) 41 | 42 | proc optInd(p: var Parser) = 43 | if p.tok.indent >= 0: 44 | if not realInd(p): p.error(errInvalidIndentation) 45 | 46 | proc eat(p: var Parser, kind: TokenKind) = 47 | if p.tok.kind == kind: 48 | p.getTok() 49 | else: 50 | p.error(errTokenExpected, kind.toString(), toString(p.tok.kind)) 51 | 52 | proc getLineInfo(p: Parser): RazLineInfo = 53 | result.line = int16(p.tok.line) 54 | result.col = int16(p.tok.col) 55 | result.fileIndex = p.lex.fileIndex 56 | 57 | proc newNodeP(p: Parser, kind: NodeKind, sons: varargs[Node]): Node = 58 | result = newTree(kind, sons) 59 | result.lineInfo = if sons.len == 0: p.getLineInfo() else: sons[0].lineInfo 60 | 61 | proc newIdentNodeP(p: Parser): Node = 62 | result = newIdentNode(p.tok.val.ident) 63 | result.lineInfo = p.getLineInfo 64 | 65 | proc newUIntNodeP(p: Parser): Node = 66 | result = newUIntNode(p.tok.val.iNumber) 67 | result.lineInfo = p.getLineInfo 68 | 69 | proc newFloatNodeP(p: Parser): Node = 70 | result = newFloatNode(p.tok.val.fNumber) 71 | result.lineInfo = p.getLineInfo 72 | 73 | proc newStringNodeP(p: Parser): Node = 74 | result = newStringNode(p.tok.literal) 75 | result.lineInfo = p.getLineInfo 76 | 77 | proc newCharLitNodeP(p: Parser): Node = 78 | result = newCharLitNode(p.tok.literal) 79 | result.lineInfo = p.getLineInfo 80 | 81 | proc openParser*(inputStream: Stream, context: RazContext, fileIndex: int32): Parser = 82 | result.tok = initToken() 83 | result.lex = openLexer(inputStream, context, fileIndex) 84 | result.getTok() # read the first token 85 | result.firstTok = true 86 | result.emptyNode = newNode(nkEmpty) 87 | 88 | proc close*(p: var Parser) = 89 | p.lex.close() 90 | 91 | proc parseExpr(p: var Parser, minPrec: int, prev = Node(nil)): Node 92 | proc parseArgs(p: var Parser): Node 93 | 94 | proc parseIdentChain(p: var Parser, prev: Node): Node = 95 | result = prev 96 | while p.tok.kind == tkDot and p.tok.indent < 0: 97 | p.getTok() 98 | if p.tok.kind != tkIdent: 99 | p.error(errIdentExpected) 100 | result = newNodeP(p, nkDotCall, result, newIdentNodeP(p)) 101 | p.getTok() 102 | 103 | proc parseName(p: var Parser): Node = 104 | result = newIdentNodeP(p) 105 | p.getTok() 106 | if p.tok.kind == tkDot and p.tok.indent < 0: 107 | result = parseIdentChain(p, result) 108 | 109 | proc parseStringNode(p: var Parser): Node = 110 | # sequence of strings are concatenated into one string 111 | result = newStringNodeP(p) 112 | 113 | p.getTok() 114 | while p.tok.kind == tkString: 115 | result.strVal.add p.tok.literal 116 | p.getTok() 117 | p.optPar() 118 | 119 | proc parseNameResolution(p: var Parser): Node = 120 | result = parseName(p) 121 | if p.tok.kind == tkBracketLe: 122 | p.getTok() 123 | let idxExpr = p.parseExpr(-1) 124 | if p.tok.kind != tkBracketRi: 125 | p.error(errClosingBracketExpected) 126 | p.getTok() 127 | result = newNodeP(p, nkBracketExpr, result, idxExpr) 128 | elif p.tok.kind == tkParLe: 129 | let args = parseArgs(p) 130 | result = newNodeP(p, nkCall, result, args) 131 | 132 | proc parseNameChain(p: var Parser): Node = 133 | result = parseNameResolution(p) 134 | if p.tok.kind == tkDot: 135 | p.getTok() 136 | if p.tok.kind != tkIdent: 137 | p.error(errIdentExpected) 138 | let rhs = parseNameResolution(p) 139 | result = newNodeP(p, nkDotCall, result, rhs) 140 | 141 | proc primary(p: var Parser): Node = 142 | case p.tok.kind: 143 | of tkParLe: 144 | p.getTok() 145 | result = p.parseExpr(-1) 146 | if p.tok.kind != tkParRi: 147 | p.error(errClosingParExpected) 148 | p.getTok() 149 | of tkEof: 150 | result = p.emptyNode 151 | of tkOpr: 152 | let a = newIdentNodeP(p) 153 | p.getTok() 154 | let b = p.primary() 155 | if b.kind == nkEmpty: p.error(errInvalidExpresion) 156 | result = newNodeP(p, nkPrefix, a, b) 157 | of tkNumber: 158 | result = newUIntNodeP(p) 159 | p.getTok() 160 | of tkFloat: 161 | result = newFloatNodeP(p) 162 | p.getTok() 163 | of tkIdent: 164 | result = parseNameChain(p) 165 | of tkString: 166 | result = parseStringNode(p) 167 | of tkCharLit: 168 | result = newCharLitNodeP(p) 169 | p.getTok() 170 | else: 171 | result = p.emptyNode 172 | 173 | proc getPrecedence(tok: Token): int = 174 | let L = tok.literal.len 175 | 176 | # arrow like? 177 | if L > 1 and tok.literal[L-1] == '>' and 178 | tok.literal[L-2] in {'-', '~', '='}: return 6 179 | 180 | template considerAsgn(value: untyped) = 181 | result = if tok.literal[L-1] == '=': 1 else: value 182 | 183 | case tok.literal[0] 184 | of '$', '^':considerAsgn(10) 185 | of '*', '%', '/', '\\': considerAsgn(9) 186 | of '~': result = 8 187 | of '+', '-', '|': considerAsgn(8) 188 | of '&': considerAsgn(7) 189 | of '=', '<', '>', '!': result = 5 190 | of '.': considerAsgn(6) 191 | of '?': result = 2 192 | else: considerAsgn(2) 193 | 194 | proc isLeftAssoc(tok: Token): bool = 195 | result = tok.literal[0] != '^' 196 | 197 | proc isBinary(tok: Token): bool = 198 | result = tok.kind in {tkOpr, tkDotDot} 199 | 200 | proc parseExpr(p: var Parser, minPrec: int, prev = Node(nil)): Node = 201 | # this is operator precedence parsing algorithm 202 | result = if prev.isNil: p.primary() else: prev 203 | var opPrec = getPrecedence(p.tok) 204 | while opPrec >= minPrec and p.tok.indent < 0 and isBinary(p.tok): 205 | let assoc = ord(isLeftAssoc(p.tok)) 206 | opPrec = getPrecedence(p.tok) 207 | let opNode = newIdentNodeP(p) 208 | p.getTok() 209 | let rhs = p.parseExpr(opPrec + assoc) 210 | if rhs.kind == nkEmpty: 211 | result = newNodeP(p, nkPostfix, opNode, result) 212 | else: 213 | result = newNodeP(p, nkInfix, opNode, result, rhs) 214 | 215 | proc parseArgs(p: var Parser): Node = 216 | result = newNodeP(p, nkArgs) 217 | if p.tok.kind == tkParLe and p.tok.indent < 0: 218 | p.getTok() 219 | p.optInd() 220 | 221 | while true: 222 | let exp = parseExpr(p, -1) 223 | if exp.kind != nkEmpty: addSon(result, exp) 224 | if p.tok.kind == tkParRi: break 225 | if p.tok.kind notin {tkComma, tkSemiColon}: break 226 | p.getTok() 227 | p.optPar() 228 | eat(p, tkParRi) 229 | 230 | proc parseViewClass(p: var Parser): Node = 231 | if p.tok.indent >= 0 and p.tok.indent <= p.currInd: return p.emptyNode 232 | if p.tok.kind != tkColonColon: 233 | return p.emptyNode 234 | 235 | p.getTok() 236 | if p.tok.kind != tkIdent: 237 | p.error(errIdentExpected) 238 | let name = newIdentNodeP(p) 239 | p.getTok() 240 | let args = parseArgs(p) 241 | result = newNodeP(p, nkViewClass, name, args) 242 | 243 | proc parseViewClassList(p: var Parser): Node = 244 | if p.tok.kind == tkColonColon and p.tok.indent < 0: 245 | result = newNodeP(p, nkViewClassList) 246 | else: 247 | return p.emptyNode 248 | 249 | while true: 250 | let viewClass = parseViewClass(p) 251 | if viewClass.kind == nkEmpty: break 252 | addSon(result, viewClass) 253 | 254 | proc parseChoice(p: var Parser): Node = 255 | var exp = p.parseExpr(-1) 256 | if p.tok.kind == tkChoice: 257 | result = newNodeP(p, nkChoice, exp) 258 | while p.tok.kind == tkChoice: 259 | p.getTok() 260 | exp = p.parseExpr(-1) 261 | if exp.kind == nkEmpty: 262 | p.error(errExprExpected) 263 | addSon(result, exp) 264 | else: 265 | result = exp 266 | 267 | proc parseChoiceList(p: var Parser): Node = 268 | var choice = p.parseChoice() 269 | if p.tok.kind == tkComma: 270 | result = newNodeP(p, nkChoiceList, choice) 271 | while p.tok.kind == tkComma: 272 | p.getTok() 273 | choice = p.parseChoice() 274 | if choice.kind == nkEmpty: 275 | p.error(errExprExpected) 276 | addSon(result, choice) 277 | else: 278 | result = choice 279 | 280 | proc parseFlex(p: var Parser): Node = 281 | const constOpr = [tkEquals, tkGreaterOrEqual, tkLessOrEqual] 282 | var choice = p.parseChoiceList() 283 | if choice.kind == nkEmpty: return choice 284 | if choice.kind == nkBracketExpr: 285 | p.error(errPropExpected) 286 | if p.tok.kind in constOpr: 287 | result = newNodeP(p, nkFlex, choice) 288 | while p.tok.kind in constOpr: 289 | let opr = newIdentNodeP(p) 290 | addSon(result, opr) 291 | p.getTok() 292 | choice = p.parseChoiceList() 293 | if choice.kind == nkEmpty: 294 | p.error(errExprExpected) 295 | if choice.kind == nkBracketExpr: 296 | p.error(errPropExpected) 297 | addSon(result, choice) 298 | else: 299 | p.error(errConstOprNeeded) 300 | 301 | proc parseFlexList(p: var Parser): Node = 302 | p.getTok() # skip tkFlex 303 | result = p.emptyNode 304 | withInd(p): 305 | while sameInd(p): 306 | while true: 307 | p.optPar() 308 | if p.currInd == -1 and p.tok.indent >= 0: 309 | p.error(errInvalidIndentation) 310 | let n = parseFlex(p) 311 | # if no more flex, we finish here 312 | if n.kind == nkEmpty: return result 313 | if result.kind == nkEmpty: result = newNodeP(p, nkFlexList) 314 | addSon(result, n) 315 | if p.tok.kind == tkSemiColon: p.getTok() 316 | else: break 317 | 318 | proc parseEvent(p: var Parser): Node = 319 | if p.tok.kind != tkIdent: 320 | p.error(errIdentExpected) 321 | let name = newIdentNodeP(p) 322 | 323 | p.getTok() 324 | eat(p, tkColon) 325 | 326 | if p.tok.kind != tkString: 327 | p.error(errTokenExpected, tkString.toString(), toString(p.tok.kind)) 328 | 329 | let rawCode = parseStringNode(p) 330 | result = newNodeP(p, nkEvent, name, rawCode) 331 | 332 | proc parseEventList(p: var Parser): Node = 333 | p.getTok() # skip tkEvent 334 | result = newNodeP(p, nkEventList) 335 | withInd(p): 336 | while sameInd(p): 337 | addSon(result, parseEvent(p)) 338 | 339 | proc parsePropValue(p: var Parser): Node = 340 | result = p.parseExpr(-1) 341 | 342 | proc parseProp(p: var Parser): Node = 343 | if p.tok.kind != tkIdent: 344 | p.error(errIdentExpected) 345 | let name = newIdentNodeP(p) 346 | 347 | p.getTok() 348 | eat(p, tkColon) 349 | 350 | let propValue = parsePropValue(p) 351 | result = newNodeP(p, nkProp, name, propValue) 352 | 353 | proc parsePropList(p: var Parser): Node = 354 | p.getTok() # skip tkProp 355 | result = newNodeP(p, nkPropList) 356 | withInd(p): 357 | while sameInd(p): 358 | while true: 359 | p.optPar() 360 | if p.currInd == -1 and p.tok.indent >= 0: 361 | p.error(errInvalidIndentation) 362 | addSon(result, parseProp(p)) 363 | if p.tok.kind == tkSemiColon: p.getTok() 364 | else: break 365 | 366 | proc parseViewBody(p: var Parser): Node = 367 | if p.tok.indent <= p.currInd: 368 | return p.emptyNode 369 | 370 | result = newNodeP(p, nkStmtList) 371 | withInd(p): 372 | while sameInd(p): 373 | case p.tok.kind 374 | of tkProp, tkColon: 375 | let list = parsePropList(p) 376 | addSon(result, list) 377 | of tkEvent, tkBang: 378 | let list = parseEventList(p) 379 | addSon(result, list) 380 | of tkFlex, tkAt: 381 | let list = parseFlexList(p) 382 | addSon(result, list) 383 | of tkEof: 384 | break 385 | else: p.error(errInvalidToken, toString(p.tok.kind)) 386 | 387 | proc parseView(p: var Parser): Node = 388 | let name = parseName(p) 389 | let classes = parseViewClassList(p) 390 | let body = parseViewBody(p) 391 | result = newNodeP(p, nkView, name, classes, body) 392 | 393 | proc parseClassParam(p: var Parser): Node = 394 | if p.tok.kind != tkIdent: 395 | p.error(errIdentExpected) 396 | 397 | let lhs = newIdentNodeP(p) 398 | p.getTok() 399 | if p.tok.kind in {tkSemiColon, tkComma, tkParRi}: 400 | return lhs 401 | 402 | if p.tok.indent >= 0: p.error(errInvalidIndentation) 403 | if p.tok.kind != tkEquals: 404 | p.error(errOnlyAsgnAllowed) 405 | let opr = newIdentNodeP(p) 406 | 407 | p.getTok() 408 | let rhs = parseExpr(p, -1) 409 | if rhs.kind == nkEmpty: p.error(errExprExpected) 410 | else: result = newNodeP(p, nkAsgn, opr, lhs, rhs) 411 | 412 | proc parseClassParams(p: var Parser): Node = 413 | p.getTok() 414 | p.optInd() 415 | 416 | if p.tok.kind == tkIdent: 417 | result = newNodeP(p, nkClassParams) 418 | while true: 419 | addSon(result, parseClassParam(p)) 420 | if p.tok.kind == tkParRi: break 421 | if p.tok.kind notin {tkComma, tkSemiColon}: break 422 | p.getTok() 423 | else: 424 | result = p.emptyNode 425 | 426 | p.optPar() 427 | eat(p, tkParRi) 428 | 429 | proc parseClass(p: var Parser): Node = 430 | p.getTok() 431 | if p.tok.kind != tkIdent: 432 | p.error(errIdentExpected) 433 | 434 | let name = newIdentNodeP(p) 435 | var params = p.emptyNode 436 | p.getTok() 437 | if p.tok.kind == tkParLe and p.tok.indent < 0: 438 | params = parseClassParams(p) 439 | 440 | let body = parseViewBody(p) 441 | result = newNodeP(p, nkClass, name, params, body) 442 | 443 | proc parseTime(p: var Parser): Node = 444 | case p.tok.kind 445 | of tkNumber: result = newUIntNodeP(p) 446 | of tkFloat: result = newFloatNodeP(p) 447 | else: 448 | p.error(errTokenExpected, "number", toString(p.tok.kind)) 449 | p.getTok() 450 | 451 | proc parseAnim(p: var Parser): Node = 452 | let name = parseName(p) 453 | var classes = p.emptyNode 454 | 455 | if p.tok.kind == tkBang: 456 | classes = newIdentNodeP(p) 457 | p.getTok() 458 | else: 459 | classes = parseViewClassList(p) 460 | 461 | var 462 | startAni = p.emptyNode 463 | endAni = p.emptyNode 464 | if p.tok.kind == tkAt: 465 | startAni = p.emptyNode 466 | p.getTok() 467 | endAni = parseTime(p) 468 | elif p.tok.kind in {tkNumber, tkFloat}: 469 | startAni = parseTime(p) 470 | if p.tok.kind in {tkNumber, tkFloat}: 471 | endAni = parseTime(p) 472 | 473 | var interpolator = p.emptyNode 474 | if p.tok.kind == tkIdent and p.tok.indent < 0: 475 | interpolator = newIdentNodeP(p) 476 | p.getTok() 477 | 478 | result = newNodeP(p, nkAnim, name, classes, startAni, endAni, interpolator) 479 | 480 | proc parseAnimBody(p: var Parser): Node = 481 | if p.tok.indent <= p.currInd: 482 | return p.emptyNode 483 | 484 | result = newNodeP(p, nkStmtList) 485 | withInd(p): 486 | while sameInd(p): 487 | case p.tok.kind 488 | of tkIdent: 489 | let anim = parseAnim(p) 490 | addSon(result, anim) 491 | of tkEof: 492 | break 493 | else: p.error(errInvalidToken, toString(p.tok.kind)) 494 | 495 | proc parseAnimList(p: var Parser): Node = 496 | p.getTok() 497 | if p.tok.kind != tkIdent: 498 | p.error(errIdentExpected) 499 | 500 | let name = newIdentNodeP(p) 501 | p.getTok() 502 | let duration = parseTime(p) 503 | let body = parseAnimBody(p) 504 | result = newNodeP(p, nkAnimList, name, duration, body) 505 | 506 | proc parseAlias(p: var Parser): Node = 507 | if p.tok.kind != tkIdent: 508 | p.error(errIdentExpected) 509 | let name = newIdentNodeP(p) 510 | 511 | p.getTok() 512 | eat(p, tkEquals) 513 | 514 | let aliasValue = p.parseExpr(-1) 515 | result = newNodeP(p, nkAlias, name, aliasValue) 516 | 517 | proc parseAliasList(p: var Parser): Node = 518 | p.getTok() # skip tkAlias 519 | result = newNodeP(p, nkAliasList) 520 | withInd(p): 521 | while sameInd(p): 522 | addSon(result, parseAlias(p)) 523 | 524 | proc parseTopLevel(p: var Parser): Node = 525 | case p.tok.kind 526 | of tkIdent: result = parseView(p) 527 | of tkColonColon: result = parseClass(p) 528 | of tkPercent: result = parseAnimList(p) 529 | of tkAlias: result = parseAliasList(p) 530 | else: 531 | p.error(errInvalidToken, toString(p.tok.kind)) 532 | 533 | proc parseAll*(p: var Parser): Node = 534 | result = newNodeP(p, nkStmtList) 535 | while p.tok.kind != tkEof: 536 | p.hasProgress = false 537 | let a = parseTopLevel(p) 538 | if a.kind != nkEmpty and p.hasProgress: 539 | addSon(result, a) 540 | else: 541 | p.error(errExprExpected) 542 | # consume a token here to prevent an endless loop: 543 | p.getTok() 544 | if p.tok.indent != 0: 545 | p.error(errInvalidIndentation) 546 | -------------------------------------------------------------------------------- /razcal.nim: -------------------------------------------------------------------------------- 1 | import os, glfw, nvg, nimLUA, opengl, parser, razcontext, semcheck 2 | import streams, ast, layout, idents, glfw/wrapper, interpolator 3 | import types 4 | 5 | proc load_glex() {.importc, cdecl.} 6 | proc textBounds*(ctx: NVGContext; x, y: cfloat; str: cstring): cfloat = 7 | result = ctx.textBounds(x, y, str, nil, nil) 8 | 9 | proc text*(ctx: NVGContext; x, y: cfloat; str: cstring): cfloat = 10 | result = ctx.text(x, y, str, nil) 11 | 12 | proc bindNVG(LX: PState, nvg: NVGcontext) = 13 | LX.bindObject(NVGContext -> "nvg"): 14 | beginFrame -> "beginFrame" 15 | cancelFrame -> "cancelFrame" 16 | endFrame -> "endFrame" 17 | globalCompositeOperation -> "globalCompositeOperation" 18 | globalCompositeBlendFunc -> "globalCompositeBlendFunc" 19 | globalCompositeBlendFuncSeparate -> "globalCompositeBlendFuncSeparate" 20 | save -> "save" 21 | restore -> "restore" 22 | reset -> "reset" 23 | shapeAntiAlias -> "shapeAntiAlias" 24 | strokeColor -> "strokeColor" 25 | strokePaint -> "strokePaint" 26 | fillColor -> "fillColor" 27 | fillPaint -> "fillPaint" 28 | miterLimit -> "miterLimit" 29 | strokeWidth -> "strokeWidth" 30 | lineCap -> "lineCap" 31 | lineJoin -> "lineJoin" 32 | globalAlpha -> "globalAlpha" 33 | resetTransform -> "resetTransform" 34 | transform -> "transform" 35 | translate -> "translate" 36 | rotate -> "rotate" 37 | skewX -> "skewX" 38 | skewY -> "skewY" 39 | scale -> "scale" 40 | currentTransform -> "currentTransform" 41 | createImage -> "createImage" 42 | createImageMem -> "createImageMem" 43 | createImageRGBA -> "createImageRGBA" 44 | updateImage -> "updateImage" 45 | imageSize -> "imageSize" 46 | deleteImage -> "deleteImage" 47 | linearGradient -> "linearGradient" 48 | boxGradient -> "boxGradient" 49 | radialGradient -> "radialGradient" 50 | imagePattern -> "imagePattern" 51 | scissor -> "scissor" 52 | intersectScissor -> "intersectScissor" 53 | resetScissor -> "resetScissor" 54 | beginPath -> "beginPath" 55 | moveTo -> "moveTo" 56 | lineTo -> "lineTo" 57 | bezierTo -> "bezierTo" 58 | quadTo -> "quadTo" 59 | arcTo -> "arcTo" 60 | closePath -> "closePath" 61 | pathWinding -> "pathWinding" 62 | arc -> "arc" 63 | rect -> "rect" 64 | roundedRect -> "roundedRect" 65 | roundedRectVarying -> "rectVarying" 66 | ellipse -> "ellipse" 67 | circle -> "circle" 68 | fill -> "fill" 69 | stroke -> "stroke" 70 | createFont -> "createFont" 71 | createFontMem -> "createFontMem" 72 | findFont -> "findFont" 73 | addFallbackFontId -> "addFallbackFontId" 74 | addFallbackFont -> "addFallbackFont" 75 | fontSize -> "fontSize" 76 | fontBlur -> "fontBlur" 77 | textLetterSpacing -> "textLetterSpacing" 78 | textLineHeight -> "textLineHeight" 79 | textAlign -> "textAlign" 80 | fontFaceId -> "fontFaceId" 81 | fontFace -> "fontFace" 82 | text -> "text" 83 | textBox -> "textBox" 84 | textBounds -> "textBounds" 85 | textBoxBounds -> "textBoxBounds" 86 | textGlyphPositions -> "textGlyphPositions" 87 | textMetrics -> "textMetrics" 88 | textBreakLines -> "textBreakLines" 89 | 90 | LX.bindConst("nvg"): 91 | NVG_ANTIALIAS -> "ANTIALIAS" 92 | NVG_STENCIL_STROKES -> "STENCIL_STROKES" 93 | NVG_DEBUG -> "DEBUG" 94 | NVG_ALIGN_LEFT -> "ALIGN_LEFT" 95 | NVG_ALIGN_CENTER -> "ALIGN_CENTER" 96 | NVG_ALIGN_RIGHT -> "ALIGN_RIGHT" 97 | NVG_ALIGN_TOP -> "ALIGN_TOP" 98 | NVG_ALIGN_MIDDLE -> "ALIGN_MIDDLE" 99 | NVG_ALIGN_BOTTOM -> "ALIGN_BOTTOM" 100 | NVG_ALIGN_BASELINE -> "ALIGN_BASELINE" 101 | 102 | #nimLuaOptions(nloDebug, true) 103 | LX.bindFunction("nvg"): 104 | nvgRGB -> "RGB" 105 | nvgRGBf -> "RGBf" 106 | nvgRGBA -> "RGBA" 107 | nvgRGBAf -> "RGBAf" 108 | nvgLerpRGBA -> "lerpRGBA" 109 | nvgTransRGBA -> "transRGBA" 110 | nvgTransRGBAf -> "transRGBAf" 111 | nvgHSL -> "HSL" 112 | nvgHSLA -> "HSLA" 113 | nvgDegToRad -> "degToRad" 114 | nvgRadToDeg -> "radtoDeg" 115 | nvgTransformIdentity -> "transformIdentity" 116 | nvgTransformTranslate -> "transformTranslate" 117 | nvgTransformScale -> "transformScale" 118 | nvgTransformRotate -> "transformRotate" 119 | nvgTransformSkewX -> "transformSkewX" 120 | nvgTransformSkewY -> "transformSkewY" 121 | nvgTransformMultiply -> "transformMultiply" 122 | nvgTransformPremultiply -> "transformPremultiply" 123 | nvgTransformInverse -> "transformInverse" 124 | nvgTransformPoint -> "transformPoint" 125 | 126 | # store Layout reference 127 | LX.pushLightUserData(cast[pointer](genLuaID())) # push key 128 | LX.pushLightUserData(cast[pointer](nvg)) # push value 129 | LX.setTable(LUA_REGISTRYINDEX) # registry[lay.addr] = lay 130 | 131 | # register the only entry point of layout hierarchy to lua 132 | proc nvgProxy(L: PState): cint {.cdecl.} = 133 | getRegisteredType(NVGcontext, mtName, pxName) 134 | var ret = cast[ptr pxName](L.newUserData(sizeof(pxName))) 135 | # retrieve Layout 136 | L.pushLightUserData(cast[pointer](getPrevID())) # push key 137 | L.getTable(LUA_REGISTRYINDEX) # retrieve value 138 | ret.ud = cast[NVGcontext](L.toUserData(-1)) # convert to layout 139 | L.pop(1) # remove userdata 140 | L.nimGetMetaTable(mtName) 141 | discard L.setMetatable(-2) 142 | return 1 143 | 144 | LX.pushCfunction(nvgProxy) 145 | LX.setGlobal("getNVG") 146 | 147 | proc loadMainScript(ctx: RazContext): Layout = 148 | let fileName = if paramCount() == 0: "main.raz" else: paramStr(1) 149 | var input = newFileStream(fileName) 150 | var knownFile = false 151 | let fileIndex = ctx.fileInfoIdx(fileName, knownFile) 152 | 153 | try: 154 | #block: 155 | var p = openParser(input, ctx, fileIndex) 156 | var root = p.parseAll() 157 | p.close() 158 | 159 | var lay = newLayout(0, ctx) 160 | lay.semCheck(root) 161 | result = lay 162 | except SourceError as srcErr: 163 | ctx.printError(srcErr) 164 | except InternalError as ex: 165 | ctx.printError(ex) 166 | except OtherError as ex: 167 | echo ex.msg 168 | except Exception as ex: 169 | echo "unknown error: ", ex.msg 170 | writeStackTrace() 171 | 172 | proc callF(ctx: RazContext, funcName: string) = 173 | var L = ctx.getLua() 174 | L.getGlobal(funcName) 175 | if L.pcall(0, 0, 0) != 0: 176 | let errorMsg = L.toString(-1) 177 | L.pop(1) 178 | ctx.otherError(errLua, errorMsg) 179 | 180 | proc loadFonts(nvg: NVGContext) = 181 | let icons = nvg.createFont("icons", "examples/fonts/entypo.ttf") 182 | if icons == -1: 183 | echo "Could not add font icons." 184 | return 185 | 186 | let sans = nvg.createFont("sans", "examples/fonts/Roboto-Regular.ttf") 187 | if sans == -1: 188 | echo "Could not add font italic." 189 | return 190 | 191 | let bold = nvg.createFont("sans-bold", "examples/fonts/Roboto-Bold.ttf") 192 | if bold == -1: 193 | echo "Could not add font bold." 194 | return 195 | 196 | let emoji = nvg.createFont("emoji", "examples/fonts/NotoEmoji-Regular.ttf") 197 | if emoji == -1: 198 | echo "Could not add font emoji." 199 | return 200 | 201 | discard nvg.addFallbackFontId(sans, emoji) 202 | discard nvg.addFallbackFontId(bold, emoji) 203 | 204 | proc drawButton(nvg: NVGContext, x, y, w, h: float64, col: NVGcolor, text: string) = 205 | let cornerRadius = 4.0 206 | let bg = nvg.linearGradient(x,y,x,y+h, nvgRGBA(255,255,255,32), nvgRGBA(0,0,0,32)) 207 | let tw = nvg.textBounds(0,0, text) 208 | 209 | nvg.beginPath() 210 | nvg.roundedRect(x+1,y+1, w-2,h-2, cornerRadius-1) 211 | nvg.fillColor(col) 212 | nvg.fill() 213 | nvg.fillPaint(bg) 214 | nvg.fill() 215 | 216 | nvg.beginPath() 217 | nvg.roundedRect(x+0.5,y+0.5, w-1,h-1, cornerRadius-0.5) 218 | #nvg.nvgStrokeColor(nvgRGBA(0,0,0,48)) 219 | nvg.stroke(0, 0, 0, 48, 1.0) 220 | nvg.stroke(1.0, 0.0, 0.0, 1.0, 2.0) 221 | 222 | if text.len > 0: 223 | nvg.fontSize(20.0) 224 | nvg.fontFace("sans-bold") 225 | nvg.textAlign(NVG_ALIGN_LEFT + NVG_ALIGN_MIDDLE) 226 | nvg.fillColor(nvgRGBA(0,0,0,160)) 227 | discard nvg.text(x+w*0.5-tw*0.5,y+h*0.5-1,text) 228 | nvg.fillColor(nvgRGBA(255,255,255,160)) 229 | discard nvg.text(x+w*0.5-tw*0.5,y+h*0.5,text) 230 | 231 | proc drawView*(view: View, nvg: NVGContext) = 232 | let red = view.curProp.bgColor 233 | 234 | if view.curProp.visible: 235 | nvg.save() 236 | nvg.translate(view.getCenterX(), view.getCenterY()) 237 | nvg.rotate(nvgDegToRad(view.curProp.rotate)) 238 | nvg.translate(-view.getCenterX(), -view.getCenterY()) 239 | nvg.drawButton(view.getLeft(), view.getTop(), 240 | view.getWidth(), view.getHeight(), red, view.content) 241 | nvg.restore() 242 | 243 | for child in view.children: 244 | child.drawView(nvg) 245 | 246 | type 247 | ANIM_STATE = enum 248 | ANIM_NONE 249 | ANIM_START 250 | ANIM_RUN 251 | ANIM_STOP 252 | 253 | proc main = 254 | var ctx = openRazContext() 255 | var L = ctx.getLua() 256 | let lay = ctx.loadMainScript() 257 | if lay == nil: return 258 | 259 | 260 | glfw.init() 261 | var w = newGlWin(dim = (w: screenWidth, h: screenHeight), nMultiSamples = 6) 262 | w.makeContextCurrent() 263 | load_glex() 264 | opengl.loadExtensions() 265 | 266 | var nvg = nvgCreate(NVG_STENCIL_STROKES or NVG_DEBUG) 267 | if nvg.pointer == nil: 268 | echo "Could not init nanovg." 269 | return 270 | 271 | nvg.loadFonts() 272 | L.bindNVG(nvg) 273 | 274 | var actorstate = ANIM_NONE 275 | var anim = Animation(nil) 276 | 277 | proc keyboardCB(win: Win, key: Key, scanCode: int, action: KeyAction, modKeys: ModifierKeySet) = 278 | if key in {keyF1..keyF12}: 279 | if actorstate == ANIM_NONE: 280 | let id = "anim" & $(ord(key) - ord(keyF1) + 1) 281 | let ani = lay.getAnimation(id) 282 | if anim != ani and ani != nil: 283 | anim = ani 284 | actorstate = ANIM_START 285 | 286 | w.keyCb = keyboardCB 287 | var 288 | startTime = 0.0 289 | pos_x = 0.0 290 | pos_y = 0.0 291 | 292 | let s = w.framebufSize() 293 | glViewport(0, 0, GLsizei(s.w), GLsizei(s.h)) 294 | 295 | while not w.shouldClose(): 296 | glClearColor(0.3, 0.3, 0.32, 1.0) 297 | glClear(GL_COLOR_BUFFER_BIT or GL_STENCIL_BUFFER_BIT) 298 | 299 | nvg.beginFrame(s.w.cint, s.h.cint, 1.0) 300 | nvg.beginPath() 301 | nvg.circle(pos_x, pos_y, 50.0) 302 | nvg.fillColor(nvgRGBAf(1.0, 0.7, 0.0, 1.0)) 303 | let stroke_width = 10.0 304 | nvg.stroke(0.0, 0.5, 1.0, 1.0, stroke_width) 305 | nvg.endFrame() 306 | 307 | case actorstate 308 | of ANIM_START: 309 | startTime = getTime() 310 | actorstate = ANIM_RUN 311 | for a in anim.actors: 312 | a.interpolator(a.view.origin, a.destination, a.current, 0.0) 313 | a.view.current = a.current 314 | a.view.curProp = a.curProp 315 | of ANIM_RUN: 316 | nvg.beginFrame(s.w.cint, s.h.cint, 1.0) 317 | 318 | let elapsed = getTime() - startTime 319 | for a in anim.actors: 320 | if elapsed >= a.startAni: 321 | let timeCurve = (elapsed - a.startAni) / a.duration 322 | a.interpolator(a.view.origin, a.destination, a.current, timeCurve) 323 | a.curProp.rotate = a.easing(a.view.oriProp.rotate, a.destProp.rotate, timeCurve) 324 | a.curProp.bgColor.r = a.easing(a.view.oriProp.bgColor.r, a.destProp.bgColor.r, timeCurve) 325 | a.curProp.bgColor.g = a.easing(a.view.oriProp.bgColor.g, a.destProp.bgColor.g, timeCurve) 326 | a.curProp.bgColor.b = a.easing(a.view.oriProp.bgColor.b, a.destProp.bgColor.b, timeCurve) 327 | a.curProp.bgColor.a = a.easing(a.view.oriProp.bgColor.a, a.destProp.bgColor.a, timeCurve) 328 | 329 | lay.root.drawView(nvg) 330 | nvg.endFrame() 331 | w.swapBufs() 332 | if elapsed > anim.duration: actorstate = ANIM_STOP 333 | of ANIM_STOP: 334 | for a in anim.actors: 335 | a.view.setOrigin(a.destination, a.destProp) 336 | actorstate = ANIM_NONE 337 | else: 338 | lay.root.drawView(nvg) 339 | nvg.endFrame() 340 | w.swapBufs() 341 | waitEvents() 342 | 343 | nvg.nvgDelete() 344 | w.destroy() 345 | glfw.terminate() 346 | ctx.close() 347 | 348 | 349 | main() 350 | -------------------------------------------------------------------------------- /razcal.nim.cfg: -------------------------------------------------------------------------------- 1 | define:nvgGL3 -------------------------------------------------------------------------------- /razcontext.nim: -------------------------------------------------------------------------------- 1 | import strutils, idents, tables, utils, namedcolors 2 | import nimLUA, types, os, interpolator, ast 3 | 4 | var IDSeed {.compileTime.} = 0 5 | 6 | macro genLuaID*(): untyped = 7 | result = newIntLitNode(NLMaxID-IDSeed) 8 | inc IDSeed 9 | 10 | macro getPrevID*(): untyped = 11 | result = newIntLitNode(NLMaxID-IDSeed+1) 12 | 13 | type 14 | # global app context, one per app 15 | RazContext* = ref object 16 | identCache: IdentCache # only one IdentCache per app 17 | fileInfos: seq[RazFileInfo] # FileInfo list 18 | fileNameToIndex: Table[string, int32] # map canonical fileName into FileInfo index 19 | binaryPath: string # app path 20 | lua: lua_State 21 | interpolator: Table[Ident, Interpolator] 22 | easing: Table[Ident, EasingFN] 23 | namedColors: Table[Ident, uint32] 24 | 25 | MsgKind* = enum 26 | # lexer's errors 27 | errMissingFinalQuote 28 | errInvalidCharacterConstant 29 | errClosingQuoteExpected 30 | errWrongEscapeChar 31 | errUnknownNumberType 32 | errInvalidToken 33 | errInvalidNumberRange 34 | errNumberOverflow 35 | errUnexpectedEOLinMultiLineComment 36 | errTabsAreNotAllowed 37 | 38 | # parser's errors 39 | errClosingBracketExpected 40 | errClosingParExpected 41 | errInvalidIndentation 42 | errExprExpected 43 | errIdentExpected 44 | errTokenExpected 45 | errSourceEndedUnexpectedly 46 | errInvalidExpresion 47 | errOnlyAsgnAllowed 48 | errConstOprNeeded 49 | errPropExpected 50 | 51 | # semcheck's errors 52 | errUnknownNode 53 | errDuplicateView 54 | errDuplicateClass 55 | errDuplicateAlias 56 | errDuplicateAnim 57 | errDuplicateParam 58 | errRecursiveAlias 59 | errClassNotFound 60 | errParamCountNotMatch 61 | errUndefinedVar 62 | errUnknownVar 63 | errUndefinedRel 64 | errUnknownRel 65 | errRelationNotFound 66 | errWrongRelationIndex 67 | errIllegalBinaryOpr 68 | errUnknownBinaryOpr 69 | errUnknownOperation 70 | errIllegalOperation 71 | errStringNotAllowed 72 | errFloatNotAllowed 73 | errNoValidBranch 74 | errIllegalPrefixOpr 75 | errUnknownPrefixOpr 76 | errUnknownPrefix 77 | errIllegalPrefix 78 | errUnknownEqualityOpr 79 | errUndefinedProp 80 | errUnknownProp 81 | errUndefinedEvent 82 | errUnknownEvent 83 | errUnbalancedArm 84 | errUnsatisfiableConstraint 85 | errUndefinedView 86 | errUndefinedInterpolator 87 | errNeedToParticipate 88 | 89 | # other's errors 90 | errCannotOpenFile 91 | errLua 92 | 93 | warnParamNotUsed 94 | warnClassNotUsed 95 | warnAliasNotUsed 96 | 97 | const 98 | InvalidFileIDX* = int32(-1) 99 | 100 | const 101 | MsgKindToStr*: array[MsgKind, string] = [ 102 | # lexer's errors 103 | errMissingFinalQuote: "missing final quote", 104 | errInvalidCharacterConstant: "invalid character constant `0x$1`", 105 | errClosingQuoteExpected: "closing quote expected", 106 | errWrongEscapeChar: "wrong escape character in string `$1`", 107 | errUnknownNumberType: "unknown number type", 108 | errInvalidToken: "invalid token `$1`", 109 | errInvalidNumberRange: "invalid number range: $1", 110 | errNumberOverflow: "number overflow", 111 | errUnexpectedEOLinMultiLineComment: "unexpected end of file in multi line comment", 112 | errTabsAreNotAllowed: "tabs are not allowed", 113 | 114 | # parser`s errors 115 | errClosingBracketExpected: "closing bracket expected", 116 | errClosingParExpected: "closing parenthesis expected", 117 | errInvalidIndentation: "invalid indentation", 118 | errExprExpected: "expr expected", 119 | errIdentExpected: "ident expected", 120 | errTokenExpected: "token expected: $1, not $2", 121 | errSourceEndedUnexpectedly: "source ended unexpectedly", 122 | errInvalidExpresion: "invalid expression", 123 | errOnlyAsgnAllowed: "only assignment allowed here", 124 | errConstOprNeeded: "one of constraint [in]equality needed: `=`, `<=`, `>=`", 125 | errPropExpected: "prop expected, missing '.' perhaps?", 126 | 127 | # semcheck`s errors 128 | errUnknownNode: "unknown node $1", 129 | errDuplicateView: "duplicate view not allowed: `$1`, the other one is here: $2", 130 | errDuplicateClass: "duplicate class not allowed: `$1`, the other one is here: $2", 131 | errDuplicateAlias: "duplicate alias not allowed: `$1`, the other one is here: $2", 132 | errDuplicateAnim: "duplicate animation not allowed: `$1`, the other one is here: $2", 133 | errDuplicateParam: "duplicate param name: `$1`", 134 | errRecursiveAlias: "alias recursion not allowed: `$1`, the other one is here: $2", 135 | errClassNotFound: "class `$1` not found", 136 | errParamCountNotMatch: "expected $1 param(s) but got $2 param(s)", 137 | errUndefinedVar: "`$1` is an undefined constraint variable", 138 | errUnknownVar: "`$1` is an unknown constraint variable", 139 | errUndefinedRel: "`$1` is an undefined relation", 140 | errUnknownRel: "`$1` is an unknown relation", 141 | errRelationNotFound: "relation not found: `$1` is not available for `$2`", 142 | errWrongRelationIndex: "relation at index $1 not found", 143 | errIllegalBinaryOpr: "illegal binary operator: `$1`", 144 | errUnknownBinaryOpr: "unknown binary operator: `$1`", 145 | errUnknownOperation: "unknown operation $1 $2 $3", 146 | errIllegalOperation: "illegal operation $1 $2 $3", 147 | errStringNotAllowed: "string not allowed here", 148 | errFloatNotAllowed: "float not allowed here", 149 | errNoValidBranch: "no valid branch of choices", 150 | errIllegalPrefixOpr: "illegal prefix operator: `$1`", 151 | errUnknownPrefixOpr: "unknown prefix operator: `$1`", 152 | errUnknownPrefix: "unknown prefix operation: `$1` $2", 153 | errIllegalPrefix: "illegal prefix operation: `$1` $2", 154 | errUnknownEqualityOpr: "unknown constraint equality operator: `$1`", 155 | errUndefinedProp: "`$1` is an undefined view property", 156 | errUnknownProp: "`$1` is an unknown view property", 157 | errUndefinedEvent: "`$1` is an undefined view event", 158 | errUnknownEvent: "`$1` is an unknown view event", 159 | errUnbalancedArm: "left and right arm is not balanced here", 160 | errUnsatisfiableConstraint: "unsatisfiable constraint", 161 | errUndefinedView: "`$1` is an undefined view", 162 | errUndefinedInterpolator: "`$1` is an undefined interpolator", 163 | errNeedToParticipate: "`$1` need to participate in `$2` animation", 164 | 165 | # other errors 166 | errCannotOpenFile: "cannot open file: $1", 167 | errLua: "lua VM error: $1", 168 | 169 | warnParamNotUsed: "param `$1` not used", 170 | warnClassNotUsed: "class `$1` not used", 171 | warnAliasNotUsed: "alias `$1` not used", 172 | ] 173 | 174 | proc openRazContext*(): RazContext = 175 | new(result) 176 | result.identCache = newIdentCache() 177 | result.fileInfos = @[] 178 | result.fileNameToIndex = initTable[string, int32]() 179 | result.binaryPath = getAppDir() 180 | result.lua = newNimLua() 181 | result.interpolator = initTable[Ident, Interpolator]() 182 | result.easing = initTable[Ident, EasingFN]() 183 | result.namedColors = initTable[Ident, uint32]() 184 | 185 | for c in interpolatorList: 186 | result.interpolator[result.identCache.getIdent(c[0])] = c[1] 187 | 188 | for c in easingList: 189 | result.easing[result.identCache.getIdent(c[0])] = c[1] 190 | 191 | for c in NamedColors: 192 | result.namedColors[result.identCache.getIdent(c[0])] = c[1] 193 | 194 | proc close*(ctx: RazContext) = 195 | ctx.lua.close() 196 | 197 | proc getLua*(ctx: RazContext): lua_State = 198 | ctx.lua 199 | 200 | proc getIdent*(ctx: RazContext, ident: string): Ident {.inline.} = 201 | # a helper proc to get ident 202 | result = ctx.identCache.getIdent(ident) 203 | 204 | proc msgKindToString*(ctx: RazContext, kind: MsgKind, args: varargs[string]): string = 205 | # later versions may provide translated error messages 206 | result = MsgKindToStr[kind] % args 207 | 208 | proc otherError*(ctx: RazContext, kind: MsgKind, args: varargs[string, `$`]) = 209 | var err = new(OtherError) 210 | err.msg = ctx.msgKindToString(kind, args) 211 | raise err 212 | 213 | proc executeLua*(ctx: RazContext, fileName: string) = 214 | if ctx.lua.doFile(fileName) != 0.cint: 215 | let errorMsg = ctx.lua.toString(-1) 216 | ctx.lua.pop(1) 217 | ctx.otherError(errLua, errorMsg) 218 | 219 | proc marker(err: SourceError): string = 220 | # the purpose of this function is try to trim a very long line 221 | # into reasonable length and put error marker '^' below it 222 | if err.lineContent.len <= 80: 223 | return err.lineContent & spaces(err.column) & "^" 224 | 225 | var start = err.column - 40 226 | var stop = err.column + 40 227 | var trimStart = false 228 | var trimStop = false 229 | if start < 0: 230 | start = 0 231 | trimStart = true 232 | if stop > 80: 233 | stop = 80 234 | trimStop = true 235 | 236 | result = err.lineContent.substr(start, stop) 237 | if trimStart: 238 | var i = 0 239 | while i < 3 and i < result.len: 240 | result[i] = '.' 241 | inc i 242 | 243 | if trimStop: 244 | var i = result.len - 1 245 | while i > result.len - 4 and i > 0: 246 | result[i] = '.' 247 | dec i 248 | 249 | proc printMessage*(ctx: RazContext, err: SourceError, errClass: string) = 250 | assert(err.fileIndex >= 0 and err.fileIndex < ctx.fileInfos.len) 251 | let info = ctx.fileInfos[err.fileIndex] 252 | let msg = "$1($2,$3) $4: $5" % [info.fileName, $err.line, $(err.column + 1), errClass, err.msg] 253 | echo err.marker() 254 | echo msg 255 | 256 | proc printWarning*(ctx: RazContext, err: SourceError) = 257 | ctx.printMessage(err, "Warning") 258 | 259 | proc printError*(ctx: RazContext, err: SourceError) = 260 | ctx.printMessage(err, "Error") 261 | 262 | proc printError*(ctx: RazContext, err: InternalError) = 263 | let msg = "$1:$2 -> Internal Error: $3" % [err.fileName, $err.line, err.msg] 264 | echo msg 265 | 266 | proc newFileInfo(fullPath, projPath: string): RazFileInfo = 267 | result.fullPath = fullPath 268 | result.projPath = projPath 269 | let fileName = projPath.extractfileName 270 | result.fileName = fileName 271 | result.shortName = fileName.changeFileExt("") 272 | 273 | proc fileInfoIdx*(ctx: RazContext, fileName: string; isKnownFile: var bool): int32 = 274 | # map file name into FileInfo list's index 275 | var 276 | canon: string 277 | pseudoPath = false 278 | 279 | try: 280 | canon = canonicalizePath(fileName) 281 | shallow(canon) 282 | except: 283 | canon = fileName 284 | # The compiler uses "fileNames" such as `command line` or `stdin` 285 | # This flag indicates that we are working with such a path here 286 | pseudoPath = true 287 | 288 | if ctx.fileNameToIndex.hasKey(canon): 289 | result = ctx.fileNameToIndex[canon] 290 | else: 291 | isKnownFile = false 292 | result = ctx.fileInfos.len.int32 293 | ctx.fileInfos.add(newFileInfo(canon, if pseudoPath: fileName 294 | else: shortenDir(ctx.binaryPath, canon))) 295 | ctx.fileNameToIndex[canon] = result 296 | 297 | proc toFileName*(ctx: RazContext, info: RazLineInfo): string = 298 | assert(info.fileIndex >= 0 and info.fileIndex < ctx.fileInfos.len) 299 | result = ctx.fileInfos[info.fileIndex].fileName 300 | 301 | proc toFullPath*(ctx: RazContext, info: RazLineInfo): string = 302 | assert(info.fileIndex >= 0 and info.fileIndex < ctx.fileInfos.len) 303 | result = ctx.fileInfos[info.fileIndex].fullPath 304 | 305 | proc toString*(ctx: RazContext, info: RazLineInfo): string = 306 | result = "$1($2:$3)" % [ctx.toFileName(info), $info.line, $(info.col+1)] 307 | 308 | proc getInterpolator*(ctx: RazContext, ident: Ident): Interpolator {.inline.} = 309 | result = ctx.interpolator.getOrDefault(ident) 310 | 311 | proc getEasing*(ctx: RazContext, ident: Ident): EasingFN {.inline.} = 312 | result = ctx.easing.getOrDefault(ident) 313 | 314 | proc getNamedColor*(ctx: RazContext, ident: Ident): uint32 {.inline.} = 315 | result = ctx.namedColors.getOrDefault(ident) 316 | -------------------------------------------------------------------------------- /types.nim: -------------------------------------------------------------------------------- 1 | type 2 | # information about a file(raz, lua, etc) 3 | RazFileInfo* = object 4 | fullPath*: string # This is a canonical full filesystem path 5 | projPath*: string # This is relative to the project's root 6 | shortName*: string # short name of the module 7 | fileName*: string # name.ext 8 | 9 | # used in Node and Symbol 10 | RazLineInfo* = object 11 | line*, col*: int16 12 | fileIndex*: int32 # index into FileInfo list 13 | 14 | # Lexer and Parser throw this exception 15 | SourceError* = ref object of Exception 16 | line*, column*: int 17 | lineContent*: string # full source line content 18 | fileIndex*: int32 # index into FileInfo list 19 | 20 | # Semcheck and friends throw this exception 21 | # useful for debugging purpose 22 | # A stable app should never throw this exception 23 | InternalError* = ref object of Exception 24 | line*: int # Nim source line 25 | fileName*: string # Nim source file name 26 | 27 | OtherError* = ref object of Exception 28 | -------------------------------------------------------------------------------- /utils.nim: -------------------------------------------------------------------------------- 1 | import os, strutils 2 | 3 | proc canonicalizePath*(path: string): string = 4 | # on Windows, 'expandFilename' calls getFullPathName which doesn't do 5 | # case corrections, so we have to use this convoluted way of retrieving 6 | # the true filename 7 | when defined(windows): 8 | result = path.expandFilename 9 | for x in walkFiles(result): 10 | return x 11 | else: 12 | result = path.expandFilename 13 | 14 | proc shortenDir*(projectPath, dir: string): string = 15 | # returns the interesting part of a dir 16 | var prefix = projectPath & DirSep 17 | if startsWith(dir, prefix): 18 | return substr(dir, len(prefix)) 19 | result = dir 20 | 21 | proc removeTrailingDirSep*(path: string): string = 22 | if (len(path) > 0) and (path[len(path) - 1] == DirSep): 23 | result = substr(path, 0, len(path) - 2) 24 | else: 25 | result = path 26 | --------------------------------------------------------------------------------