summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arena.c38
-rw-r--r--arena.h17
-rwxr-xr-xbuild.sh31
-rw-r--r--example/utf8.txt204
-rw-r--r--linux.c70
-rw-r--r--macros.h50
-rw-r--r--prbm.c297
-rw-r--r--prbm.h112
-rw-r--r--prbs.c132
-rw-r--r--prbs.h45
-rw-r--r--prbwin.c77
-rw-r--r--sys.h14
-rw-r--r--types.h19
13 files changed, 1106 insertions, 0 deletions
diff --git a/arena.c b/arena.c
new file mode 100644
index 0000000..03a1c1c
--- /dev/null
+++ b/arena.c
@@ -0,0 +1,38 @@
+#include "arena.h"
+#include "macros.h"
+#include "sys.h"
+
+struct arena alloc_arena(u64 capacity)
+{
+ /* TODO(pryazha): Find reasonable maximum capacity through testing */
+ assert(capacity <= (u64)gigabytes(16));
+ if (!capacity)
+ capacity = kilobytes(4);
+ void *memory = sys_alloc(capacity);
+ assert(memory);
+ return (struct arena){memory, capacity, 0};
+}
+
+void release_arena(struct arena *arena)
+{
+ assert(arena);
+ sys_free(arena->memory, arena->capacity);
+ arena->memory = 0;
+ arena->capacity = 0;
+ arena->used = 0;
+}
+
+void *push_arena(struct arena *arena, u64 size)
+{
+ assert(arena);
+ assert(arena->used + size <= arena->capacity);
+ void *memory = arena->memory + arena->used;
+ arena->used += size;
+ return memory;
+}
+
+void pop_arena(struct arena *arena, u64 size)
+{
+ assert(arena);
+ arena->used -= min(size, arena->used);
+}
diff --git a/arena.h b/arena.h
new file mode 100644
index 0000000..38e3483
--- /dev/null
+++ b/arena.h
@@ -0,0 +1,17 @@
+#ifndef arena_h
+#define arena_h
+
+#include "types.h"
+
+struct arena {
+ void *memory;
+ u64 capacity;
+ u64 used;
+};
+
+struct arena alloc_arena(u64 capacity);
+void release_arena(struct arena *a);
+void *push_arena(struct arena *arena, u64 size);
+void pop_arena(struct arena *arena, u64 size);
+
+#endif
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..53ad6cb
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+start=`pwd`
+dir=`dirname "$0"`
+if [ "$1" = 'clean' ] ; then
+ set -x
+ cd $dir
+ rm -f *.o
+ cd "$start"
+ exit
+fi
+cc='tcc'
+debug='-g'
+release='-O2'
+cflags="-c -std=c99"
+if [ "$1" = "debug" ] ; then
+ cflags="$debug $cflags"
+else
+ cflags="$release $cflags"
+fi
+set -x
+cd $dir
+$cc $cflags prbm.c
+$cc $cflags arena.c
+$cc $cflags prbs.c
+$cc $cflags linux.c
+ar rcs libprb.a\
+ prbm.o \
+ arena.o \
+ prbs.o \
+ linux.o
+cd "$start"
diff --git a/example/utf8.txt b/example/utf8.txt
new file mode 100644
index 0000000..3af7875
--- /dev/null
+++ b/example/utf8.txt
@@ -0,0 +1,204 @@
+// Original by Markus Kuhn, adapted for HTML by Martin D
+
+UTF-8 encoded sample plain-text file
+‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+
+Markus Kuhn [ˈmaʳkʊs kuːn] mkuhn@acm.org — 1999-08-20
+
+
+The ASCII compatible UTF-8 encoding of ISO 10646 and Unicode
+plain-text files is defined in RFC 2279 and in ISO 10646-1 Annex R.
+
+
+Using Unicode/UTF-8, you can write in emails and source code things such as
+
+Mathematics and Sciences:
+
+ ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β),
+
+ ℕ ⊆ ℕ₀ ⊂ ℤ ⊂ ℚ ⊂ ℝ ⊂ ℂ, ⊥ &lt; a ≠ b ≡ c ≤ d ≪ ⊤ ⇒ (A ⇔ B),
+
+ 2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm
+
+Linguistics and dictionaries:
+
+ ði ıntəˈnæʃənəl fəˈnɛtık əsoʊsiˈeıʃn
+ Y [ˈʏpsilɔn], Yen [jɛn], Yoga [ˈjoːgɑ]
+
+APL:
+
+ ((V⍳V)=⍳⍴V)/V←,V ⌷←⍳→⍴∆∇⊃‾⍎⍕⌈
+
+Nicer typography in plain text files:
+
+ ╔══════════════════════════════════════════╗
+ ║ ║
+ ║ • ‘single’ and “double” quotes ║
+ ║ ║
+ ║ • Curly apostrophes: “We’ve been here” ║
+ ║ ║
+ ║ • Latin-1 apostrophe and accents: '´` ║
+ ║ ║
+ ║ • ‚deutsche‘ „Anführungszeichen“ ║
+ ║ ║
+ ║ • †, ‡, ‰, •, 3–4, —, −5/+5, ™, … ║
+ ║ ║
+ ║ • ASCII safety test: 1lI|, 0OD, 8B ║
+ ║ ╭─────────╮ ║
+ ║ • the euro symbol: │ 14.95 € │ ║
+ ║ ╰─────────╯ ║
+ ╚══════════════════════════════════════════╝
+
+Greek (in Polytonic):
+
+ The Greek anthem:
+
+ Σὲ γνωρίζω ἀπὸ τὴν κόψη
+ τοῦ σπαθιοῦ τὴν τρομερή,
+ σὲ γνωρίζω ἀπὸ τὴν ὄψη
+ ποὺ μὲ βία μετράει τὴ γῆ.
+
+ ᾿Απ᾿ τὰ κόκκαλα βγαλμένη
+ τῶν ῾Ελλήνων τὰ ἱερά
+ καὶ σὰν πρῶτα ἀνδρειωμένη
+ χαῖρε, ὦ χαῖρε, ᾿Ελευθεριά!
+
+ From a speech of Demosthenes in the 4th century BC:
+
+ Οὐχὶ ταὐτὰ παρίσταταί μοι γιγνώσκειν, ὦ ἄνδρες ᾿Αθηναῖοι,
+ ὅταν τ᾿ εἰς τὰ πράγματα ἀποβλέψω καὶ ὅταν πρὸς τοὺς
+ λόγους οὓς ἀκούω· τοὺς μὲν γὰρ λόγους περὶ τοῦ
+ τιμωρήσασθαι Φίλιππον ὁρῶ γιγνομένους, τὰ δὲ πράγματ᾿
+ εἰς τοῦτο προήκοντα, ὥσθ᾿ ὅπως μὴ πεισόμεθ᾿ αὐτοὶ
+ πρότερον κακῶς σκέψασθαι δέον. οὐδέν οὖν ἄλλο μοι δοκοῦσιν
+ οἱ τὰ τοιαῦτα λέγοντες ἢ τὴν ὑπόθεσιν, περὶ ἧς βουλεύεσθαι,
+ οὐχὶ τὴν οὖσαν παριστάντες ὑμῖν ἁμαρτάνειν. ἐγὼ δέ, ὅτι μέν
+ ποτ᾿ ἐξῆν τῇ πόλει καὶ τὰ αὑτῆς ἔχειν ἀσφαλῶς καὶ Φίλιππον
+ τιμωρήσασθαι, καὶ μάλ᾿ ἀκριβῶς οἶδα· ἐπ᾿ ἐμοῦ γάρ, οὐ πάλαι
+ γέγονεν ταῦτ᾿ ἀμφότερα· νῦν μέντοι πέπεισμαι τοῦθ᾿ ἱκανὸν
+ προλαβεῖν ἡμῖν εἶναι τὴν πρώτην, ὅπως τοὺς συμμάχους
+ σώσομεν. ἐὰν γὰρ τοῦτο βεβαίως ὑπάρξῃ, τότε καὶ περὶ τοῦ
+ τίνα τιμωρήσεταί τις καὶ ὃν τρόπον ἐξέσται σκοπεῖν· πρὶν δὲ
+ τὴν ἀρχὴν ὀρθῶς ὑποθέσθαι, μάταιον ἡγοῦμαι περὶ τῆς
+ τελευτῆς ὁντινοῦν ποιεῖσθαι λόγον.
+
+ Δημοσθένους, Γ´ ᾿Ολυνθιακὸς
+
+Georgian:
+
+ From a Unicode conference invitation:
+
+ გთხოვთ ახლავე გაიაროთ რეგისტრაცია Unicode-ის მეათე საერთაშორისო
+ კონფერენციაზე დასასწრებად, რომელიც გაიმართება 10-12 მარტს,
+ ქ. მაინცში, გერმანიაში. კონფერენცია შეჰკრებს ერთად მსოფლიოს
+ ექსპერტებს ისეთ დარგებში როგორიცაა ინტერნეტი და Unicode-ი,
+ ინტერნაციონალიზაცია და ლოკალიზაცია, Unicode-ის გამოყენება
+ ოპერაციულ სისტემებსა, და გამოყენებით პროგრამებში, შრიფტებში,
+ ტექსტების დამუშავებასა და მრავალენოვან კომპიუტერულ სისტემებში.
+
+Russian:
+
+ From a Unicode conference invitation:
+
+ Зарегистрируйтесь сейчас на Десятую Международную Конференцию по
+ Unicode, которая состоится 10-12 марта 1997 года в Майнце в Германии.
+ Конференция соберет широкий круг экспертов по вопросам глобального
+ Интернета и Unicode, локализации и интернационализации, воплощению и
+ применению Unicode в различных операционных системах и программных
+ приложениях, шрифтах, верстке и многоязычных компьютерных системах.
+
+Thai (UCS Level 2):
+
+ Excerpt from a poetry on The Romance of The Three Kingdoms (a Chinese
+ classic 'San Gua'):
+
+ [----------------------------|------------------------]
+ ๏ แผ่นดินฮั่นเสื่อมโทรมแสนสังเวช พระปกเกศกองบู๊กู้ขึ้นใหม่
+ สิบสองกษัตริย์ก่อนหน้าแลถัดไป สององค์ไซร้โง่เขลาเบาปัญญา
+ ทรงนับถือขันทีเป็นที่พึ่ง บ้านเมืองจึงวิปริตเป็นนักหนา
+ โฮจิ๋นเรียกทัพทั่วหัวเมืองมา หมายจะฆ่ามดชั่วตัวสำคัญ
+ เหมือนขับไสไล่เสือจากเคหา รับหมาป่าเข้ามาเลยอาสัญ
+ ฝ่ายอ้องอุ้นยุแยกให้แตกกัน ใช้สาวนั้นเป็นชนวนชื่นชวนใจ
+ พลันลิฉุยกุยกีกลับก่อเหตุ ช่างอาเพศจริงหนาฟ้าร้องไห้
+ ต้องรบราฆ่าฟันจนบรรลัย ฤๅหาใครค้ำชูกู้บรรลังก์ ฯ
+
+ (The above is a two-column text. If combining characters are handled
+ correctly, the lines of the second column should be aligned with the
+ | character above.)
+
+Ethiopian:
+
+ Proverbs in the Amharic language:
+
+ ሰማይ አይታረስ ንጉሥ አይከሰስ።
+ ብላ ካለኝ እንደአባቴ በቆመጠኝ።
+ ጌጥ ያለቤቱ ቁምጥና ነው።
+ ደሀ በሕልሙ ቅቤ ባይጠጣ ንጣት በገደለው።
+ የአፍ ወለምታ በቅቤ አይታሽም።
+ አይጥ በበላ ዳዋ ተመታ።
+ ሲተረጉሙ ይደረግሙ።
+ ቀስ በቀስ፥ ዕንቁላል በእግሩ ይሄዳል።
+ ድር ቢያብር አንበሳ ያስር።
+ ሰው እንደቤቱ እንጅ እንደ ጉረቤቱ አይተዳደርም።
+ እግዜር የከፈተውን ጉሮሮ ሳይዘጋው አይድርም።
+ የጎረቤት ሌባ፥ ቢያዩት ይስቅ ባያዩት ያጠልቅ።
+ ሥራ ከመፍታት ልጄን ላፋታት።
+ ዓባይ ማደሪያ የለው፥ ግንድ ይዞ ይዞራል።
+ የእስላም አገሩ መካ የአሞራ አገሩ ዋርካ።
+ ተንጋሎ ቢተፉ ተመልሶ ባፉ።
+ ወዳጅህ ማር ቢሆን ጨርስህ አትላሰው።
+ እግርህን በፍራሽህ ልክ ዘርጋ።
+
+Runes:
+
+ ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ
+
+ (Old English, which transcribed into Latin reads 'He cwaeth that he
+ bude thaem lande northweardum with tha Westsae.' and means 'He said
+ that he lived in the northern land near the Western Sea.')
+
+Braille:
+
+ ⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌
+
+ ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠙⠑⠁⠙⠒ ⠞⠕ ⠃⠑⠛⠔ ⠺⠊⠹⠲ ⡹⠻⠑ ⠊⠎ ⠝⠕ ⠙⠳⠃⠞
+ ⠱⠁⠞⠑⠧⠻ ⠁⠃⠳⠞ ⠹⠁⠞⠲ ⡹⠑ ⠗⠑⠛⠊⠌⠻ ⠕⠋ ⠙⠊⠎ ⠃⠥⠗⠊⠁⠇ ⠺⠁⠎
+ ⠎⠊⠛⠝⠫ ⠃⠹ ⠹⠑ ⠊⠇⠻⠛⠹⠍⠁⠝⠂ ⠹⠑ ⠊⠇⠻⠅⠂ ⠹⠑ ⠥⠝⠙⠻⠞⠁⠅⠻⠂
+ ⠁⠝⠙ ⠹⠑ ⠡⠊⠑⠋ ⠍⠳⠗⠝⠻⠲ ⡎⠊⠗⠕⠕⠛⠑ ⠎⠊⠛⠝⠫ ⠊⠞⠲ ⡁⠝⠙
+ ⡎⠊⠗⠕⠕⠛⠑⠰⠎ ⠝⠁⠍⠑ ⠺⠁⠎ ⠛⠕⠕⠙ ⠥⠏⠕⠝ ⠰⡡⠁⠝⠛⠑⠂ ⠋⠕⠗ ⠁⠝⠹⠹⠔⠛ ⠙⠑
+ ⠡⠕⠎⠑ ⠞⠕ ⠏⠥⠞ ⠙⠊⠎ ⠙⠁⠝⠙ ⠞⠕⠲
+
+ ⡕⠇⠙ ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲
+
+ ⡍⠔⠙⠖ ⡊ ⠙⠕⠝⠰⠞ ⠍⠑⠁⠝ ⠞⠕ ⠎⠁⠹ ⠹⠁⠞ ⡊ ⠅⠝⠪⠂ ⠕⠋ ⠍⠹
+ ⠪⠝ ⠅⠝⠪⠇⠫⠛⠑⠂ ⠱⠁⠞ ⠹⠻⠑ ⠊⠎ ⠏⠜⠞⠊⠊⠥⠇⠜⠇⠹ ⠙⠑⠁⠙ ⠁⠃⠳⠞
+ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲ ⡊ ⠍⠊⠣⠞ ⠙⠁⠧⠑ ⠃⠑⠲ ⠔⠊⠇⠔⠫⠂ ⠍⠹⠎⠑⠇⠋⠂ ⠞⠕
+ ⠗⠑⠛⠜⠙ ⠁ ⠊⠕⠋⠋⠔⠤⠝⠁⠊⠇ ⠁⠎ ⠹⠑ ⠙⠑⠁⠙⠑⠌ ⠏⠊⠑⠊⠑ ⠕⠋ ⠊⠗⠕⠝⠍⠕⠝⠛⠻⠹
+ ⠔ ⠹⠑ ⠞⠗⠁⠙⠑⠲ ⡃⠥⠞ ⠹⠑ ⠺⠊⠎⠙⠕⠍ ⠕⠋ ⠳⠗ ⠁⠝⠊⠑⠌⠕⠗⠎
+ ⠊⠎ ⠔ ⠹⠑ ⠎⠊⠍⠊⠇⠑⠆ ⠁⠝⠙ ⠍⠹ ⠥⠝⠙⠁⠇⠇⠪⠫ ⠙⠁⠝⠙⠎
+ ⠩⠁⠇⠇ ⠝⠕⠞ ⠙⠊⠌⠥⠗⠃ ⠊⠞⠂ ⠕⠗ ⠹⠑ ⡊⠳⠝⠞⠗⠹⠰⠎ ⠙⠕⠝⠑ ⠋⠕⠗⠲ ⡹⠳
+ ⠺⠊⠇⠇ ⠹⠻⠑⠋⠕⠗⠑ ⠏⠻⠍⠊⠞ ⠍⠑ ⠞⠕ ⠗⠑⠏⠑⠁⠞⠂ ⠑⠍⠏⠙⠁⠞⠊⠊⠁⠇⠇⠹⠂ ⠹⠁⠞
+ ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲
+
+ (The first couple of paragraphs of "A Christmas Carol" by Dickens)
+
+Compact font selection example text:
+
+ ABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789
+ abcdefghijklmnopqrstuvwxyz £©µÀÆÖÞßéöÿ
+ –—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩαβγδω АБВГДабвгд
+ ∀∂∈ℝ∧∪≡∞ ↑↗↨↻⇣ ┐┼╔╘░►☺♀ fi�⑀₂ἠḂӥẄɐː⍎אԱა
+
+Greetings in various languages:
+
+ Hello world, Καλημέρα κόσμε, コンニチハ
+
+Box drawing alignment tests: █
+ ▉
+ ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳
+ ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳
+ ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳
+ ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳
+ ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎
+ ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏
+ ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█
diff --git a/linux.c b/linux.c
new file mode 100644
index 0000000..ff20e92
--- /dev/null
+++ b/linux.c
@@ -0,0 +1,70 @@
+#include "sys.h"
+#include "macros.h"
+#include <sys/mman.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+void *sys_alloc(u64 length)
+{
+ assert(length);
+ i32 prot = PROT_READ | PROT_WRITE;
+ i32 flags = MAP_PRIVATE|MAP_ANONYMOUS;
+ void *result = mmap(0, length, prot, flags, -1, 0);
+ return result;
+}
+
+void sys_free(void *memory, u64 length)
+{
+ assert(memory);
+ assert(length);
+ munmap(memory, length);
+}
+
+void die(const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ fprintf(stderr, "error: ");
+ vfprintf(stderr, format, args);
+ fprintf(stderr, "\n");
+ va_end(args);
+ _exit(1);
+}
+
+void info(const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ vprintf(format, args);
+ putchar('\n');
+ va_end(args);
+}
+
+char *read_entire_file(struct arena *arena, u64 *len, const char *filename)
+{
+ FILE *file = fopen(filename, "rb");
+ if (!file)
+ return 0;
+ if (fseek(file, 0, SEEK_END) == -1)
+ goto error;
+ i64 n = ftell(file);
+ if (n <= 0)
+ goto error;
+ if (len)
+ *len = n;
+ if (fseek(file, 0, SEEK_SET) == -1)
+ goto error;
+ char *buffer = push_arena(arena, n + 1);
+ if (!fread(buffer, 1, n, file)) {
+ pop_arena(arena, n + 1);
+ goto error;
+ }
+ fclose(file);
+ buffer[n] = 0;
+ return buffer;
+error:
+ fclose(file);
+ return 0;
+}
diff --git a/macros.h b/macros.h
new file mode 100644
index 0000000..eb514b5
--- /dev/null
+++ b/macros.h
@@ -0,0 +1,50 @@
+#ifndef macros_h
+#define macros_h
+
+#undef assert
+#define assert(expr) if (!(expr)) { *(int *)0 = 0; }
+
+#define array_count(array) (sizeof(array)/sizeof(*(array)))
+
+#undef offsetof
+#define offsetof(type, element) ((u64)(&(((type *)0)->element)))
+
+#define min(a, b) (((a) < (b)) ? (a) : (b))
+#define max(a, b) (((a) > (b)) ? (a) : (b))
+
+#define clamp(a, x, b) \
+ (((x) < (a)) ? (a) : \
+ (((x) > (b)) ? (b) : (x)))
+
+#define swap(type, a, b) { type tmp = (a); (a) = (b); (b) = tmp; }
+
+#define kilobytes(n) n*1024
+#define megabytes(n) kilobytes(n)*1024
+#define gigabytes(n) megabytes(n)*1024
+
+/* NOTE(pryazha): Singly linked list */
+#define sllpush(first, last, node) \
+ ((first) == 0 ? \
+ ((first) = (last) = (node), (node)->next = 0) : \
+ ((last)->next = (node), (last) = (node), (node)->next = 0))
+#define sllpop(first, last) \
+ ((first) == (last) ? \
+ ((first) = (last) = 0) : \
+ ((first) = (first)->next))
+
+/* NOTE(pryazha): Doubly linked list */
+#define dllpush(first, last, node, next, prev) \
+ ((first) == 0 ? \
+ ((first) = (last) = (node), (node)->next = (node)->prev = 0) : \
+ ((node)->prev = (last), (last)->next = (node), (last) = (node), (node)->next = 0))
+#define dllpushback(first, last, node) dllpush(first, last, node, next, prev)
+#define dllpushfront(first, last, node) dllpush(last, first, node, prev, next)
+
+#define dllremove(first, last, node) \
+ ((first) == (node) ? \
+ ((first) == (last) ? (first) = (last) = 0 : \
+ ((first) = (first)->next, (first)->prev = 0)) : \
+ ((last) == (node) ? ((last) = (last)->prev, (last)->next = 0) : \
+ ((node)->next->prev = (node)->prev, (node)->prev->next = (node)->next)))
+
+#endif
diff --git a/prbm.c b/prbm.c
new file mode 100644
index 0000000..ae1ac97
--- /dev/null
+++ b/prbm.c
@@ -0,0 +1,297 @@
+#include "prbm.h"
+#include <math.h>
+
+struct v2 fillv2(f32 a)
+{
+ return (struct v2){a, a};
+}
+
+struct v2 invv2(struct v2 a)
+{
+ return (struct v2){-a.x, -a.y};
+}
+
+struct v2 addv2(struct v2 a, struct v2 b)
+{
+ return (struct v2){a.x + b.x, a.y + b.y};
+}
+
+struct v2 subv2(struct v2 a, struct v2 b)
+{
+ return (struct v2){a.x - b.x, a.y - b.y};
+}
+
+struct v2 scalev2(struct v2 a, f32 s)
+{
+ return (struct v2){a.x * s, a.y * s};
+}
+
+struct v2 scalevv2(struct v2 a, struct v2 s)
+{
+ return (struct v2){a.x * s.x, a.y * s.y};
+}
+
+f32 dotv2(struct v2 a, struct v2 b)
+{
+ return (a.x * b.x + a.y * b.y);
+}
+
+f32 len2v2(struct v2 a)
+{
+ return dotv2(a, a);
+}
+
+f32 lenv2(struct v2 a)
+{
+ return sqrtf(len2v2(a));
+}
+
+struct v2 normv2(struct v2 a)
+{
+ f32 len = lenv2(a);
+ if (len) {
+ return v2_zero;
+ }
+ f32 ilen = 1.0f / len;
+ return (struct v2){a.x * len, a.y * len};
+}
+
+struct v3 fillv3(f32 a)
+{
+ return (struct v3){a, a, a};
+}
+
+struct v3 invv3(struct v3 a)
+{
+ return (struct v3){-a.x, -a.y, -a.z};
+}
+
+struct v3 addv3(struct v3 a, struct v3 b)
+{
+ return (struct v3){a.x + b.x, a.y + b.y, a.z + b.z};
+}
+
+struct v3 subv3(struct v3 a, struct v3 b)
+{
+ return (struct v3){a.x - b.x, a.y - b.y, a.z - b.z};
+}
+
+struct v3 scalev3(struct v3 a, f32 s)
+{
+ return (struct v3){a.x * s, a.y * s, a.z * s};
+}
+
+struct v3 scalevv3(struct v3 a, struct v3 s)
+{
+ return (struct v3){a.x * s.x, a.y * s.y, a.z * s.z};
+}
+
+f32 dotv3(struct v3 a, struct v3 b)
+{
+ return (a.x * b.x + a.y * b.y + a.z * b.z);
+}
+
+struct v3 crossv3(struct v3 l, struct v3 r)
+{
+ return (struct v3){
+ (l.y * r.z - r.y * l.z),
+ (r.x * l.z - l.x * r.z),
+ (l.x * r.y - r.x * l.y)
+ };
+}
+
+f32 len2v3(struct v3 a)
+{
+ return dotv3(a, a);
+}
+
+f32 lenv3(struct v3 a)
+{
+ return sqrtf(len2v3(a));
+}
+
+struct v3 normv3(struct v3 a)
+{
+ f32 len = lenv3(a);
+ if (!len)
+ return v3_zero;
+ f32 ilen = 1.0f / len;
+ return (struct v3){a.x * len, a.y * len, a.z * len};
+}
+
+struct v4 fillv4(f32 a)
+{
+ return (struct v4){a, a, a, a};
+}
+
+struct v4 invv4(struct v4 a)
+{
+ return (struct v4){-a.x, -a.y, -a.z, -a.w};
+}
+
+struct v4 addv4(struct v4 a, struct v4 b)
+{
+ return (struct v4){a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w};
+}
+
+struct v4 subv4(struct v4 a, struct v4 b)
+{
+ return (struct v4){a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w};
+}
+
+struct v4 scalev4(struct v4 a, f32 s)
+{
+ return (struct v4){a.x * s, a.y * s, a.z * s, a.w * s};
+}
+
+struct v4 scalevv4(struct v4 a, struct v4 s)
+{
+ return (struct v4){a.x * s.x, a.y * s.y, a.z * s.z, a.w * s.w};
+}
+
+f32 dotv4(struct v4 a, struct v4 b)
+{
+ return (a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w);
+}
+
+f32 len2v4(struct v4 a)
+{
+ return dotv4(a, a);
+}
+
+f32 lenv4(struct v4 a)
+{
+ return sqrtf(len2v4(a));
+}
+
+struct v4 normv4(struct v4 a)
+{
+ f32 len = lenv4(a);
+ if (!len)
+ return v4_zero;
+ f32 ilen = 1.0f / len;
+ return (struct v4){a.x * ilen, a.y * ilen, a.z * ilen, a.w * ilen};
+}
+
+struct mat transpmat(struct mat m)
+{
+ struct mat r = m;
+ r.c0.y = m.c1.x;
+ r.c0.z = m.c2.x;
+ r.c0.w = m.c3.x;
+ r.c1.z = m.c2.y;
+ r.c1.w = m.c3.y;
+ r.c2.w = m.c3.z;
+ return r;
+}
+
+struct mat mulmat(struct mat left, struct mat right)
+{
+ f32 l00 = left.c0.x, l01 = left.c0.y, l02 = left.c0.z, l03 = left.c0.w;
+ f32 l10 = left.c1.x, l11 = left.c1.y, l12 = left.c1.z, l13 = left.c1.w;
+ f32 l20 = left.c2.x, l21 = left.c2.y, l22 = left.c2.z, l23 = left.c2.w;
+ f32 l30 = left.c3.x, l31 = left.c3.y, l32 = left.c3.z, l33 = left.c3.w;
+ f32 r00 = right.c0.x, r01 = right.c0.y, r02 = right.c0.z, r03 = right.c0.w;
+ f32 r10 = right.c1.x, r11 = right.c1.y, r12 = right.c1.z, r13 = right.c1.w;
+ f32 r20 = right.c2.x, r21 = right.c2.y, r22 = right.c2.z, r23 = right.c2.w;
+ f32 r30 = right.c3.x, r31 = right.c3.y, r32 = right.c3.z, r33 = right.c3.w;
+ return (struct mat){
+ {
+ l00*r00 + l10*r01 + l20*r02 + l30*r03,
+ l01*r00 + l11*r01 + l21*r02 + l31*r03,
+ l02*r00 + l12*r01 + l22*r02 + l32*r03,
+ l03*r00 + l13*r01 + l23*r02 + l33*r03
+ },
+ {
+ l00*r10 + l10*r11 + l20*r12 + l30*r13,
+ l01*r10 + l11*r11 + l21*r12 + l31*r13,
+ l02*r10 + l12*r11 + l22*r12 + l32*r13,
+ l03*r10 + l13*r11 + l23*r12 + l33*r13
+ },
+ {
+ l00*r20 + l10*r21 + l20*r22 + l30*r23,
+ l01*r20 + l11*r21 + l21*r22 + l31*r23,
+ l02*r20 + l12*r21 + l22*r22 + l32*r23,
+ l03*r20 + l13*r21 + l23*r22 + l33*r23
+ },
+ {
+ l00*r30 + l10*r31 + l20*r32 + l30*r33,
+ l01*r30 + l11*r31 + l21*r32 + l31*r33,
+ l02*r30 + l12*r31 + l22*r32 + l32*r33,
+ l03*r30 + l13*r31 + l23*r32 + l33*r33
+ }
+ };
+}
+
+struct mat make_translate(struct v3 v)
+{
+ return (struct mat){
+ {1.0f, 0.0f, 0.0f, 0.0f},
+ {0.0f, 1.0f, 0.0f, 0.0f},
+ {0.0f, 0.0f, 1.0f, 0.0f},
+ {v.x, v.y, v.z, 1.0f}
+ };
+}
+
+struct mat make_scale(struct v3 v)
+{
+ return (struct mat){
+ {v.x, 0.0f, 0.0f, 0.0f},
+ {0.0f, v.y, 0.0f, 0.0f},
+ {0.0f, 0.0f, v.z, 0.0f},
+ {0.0f, 0.0f, 0.0f, 1.0f}
+ };
+}
+
+struct mat make_rotate(struct v3 x, struct v3 y, struct v3 z)
+{
+ return (struct mat){
+ {x.x, x.y, x.z, 0.0f},
+ {y.x, y.y, y.z, 0.0f},
+ {z.x, z.y, z.z, 0.0f},
+ {0.0f, 0.0f, 0.0f, 1.0f}
+ };
+}
+
+struct mat translate_mat(struct mat m, struct v3 v)
+{
+ return mulmat(make_translate(v), m);
+}
+
+struct mat scale_mat(struct mat m, struct v3 v)
+{
+ return mulmat(make_scale(v), m);
+}
+
+struct mat rotate_mat(struct mat m, struct v3 angles)
+{
+ f32 angle = deg2rad(angles.x);
+ f32 cx = cosf(angle);
+ f32 sx = sinf(angle);
+ angle = deg2rad(angles.y);
+ f32 cy = cosf(angle);
+ f32 sy = sinf(angle);
+ angle = deg2rad(angles.z);
+ f32 cz = cosf(angle);
+ f32 sz = sinf(angle);
+ struct v3 x = { cy*cz, sx*sy*cz + cx*sz, -cx*sy*cz + sx*sz};
+ struct v3 y = {-cy*sz, -sx*sy*sz + cx*cz, cx*sy*sz + sx*cz};
+ struct v3 z = { sy, -sx*cy, cx*cy};
+ return mulmat(make_rotate(x, y, z), m);
+}
+
+struct v4 mulmatv4(struct mat m, struct v4 v)
+{
+ return (struct v4){
+ m.c0.x*v.x + m.c1.x*v.y + m.c2.x*v.z + m.c3.x*v.w,
+ m.c0.y*v.x + m.c1.y*v.y + m.c2.y*v.z + m.c3.y*v.w,
+ m.c0.z*v.x + m.c1.z*v.y + m.c2.z*v.z + m.c3.z*v.w,
+ m.c0.w*v.x + m.c1.w*v.y + m.c2.w*v.z + m.c3.w*v.w
+ };
+}
+
+i32 in_rect(struct v2 pos, struct rect rect)
+{
+ return (pos.x > rect.start.x) && (pos.x < rect.end.x) &&
+ (pos.y > rect.start.y) && (pos.y < rect.end.y);
+}
diff --git a/prbm.h b/prbm.h
new file mode 100644
index 0000000..f3932dd
--- /dev/null
+++ b/prbm.h
@@ -0,0 +1,112 @@
+#ifndef prbm_h
+#define prbm_h
+
+#include "types.h"
+
+struct v2 {
+ f32 x;
+ f32 y;
+};
+
+struct v3 {
+ f32 x;
+ f32 y;
+ f32 z;
+};
+
+struct v4 {
+ f32 x;
+ f32 y;
+ f32 z;
+ f32 w;
+};
+
+// column-major
+struct mat {
+ struct v4 c0;
+ struct v4 c1;
+ struct v4 c2;
+ struct v4 c3;
+};
+
+struct rect {
+ struct v2 start;
+ struct v2 end;
+};
+
+#define v2_zero (struct v2){ 0.0f, 0.0f}
+#define v2_one (struct v2){ 1.0f, 1.0f}
+#define v2_right (struct v2){ 1.0f, 0.0f}
+#define v2_up (struct v2){ 0.0f, 1.0f}
+#define v2_left (struct v2){-1.0f, 0.0f}
+#define v2_down (struct v2){ 0.0f, -1.0f}
+
+#define v3_zero (struct v3){ 0.0f, 0.0f, 0.0f}
+#define v3_one (struct v3){ 1.0f, 1.0f, 1.0f}
+#define v3_right (struct v3){ 1.0f, 0.0f, 0.0f}
+#define v3_up (struct v3){ 0.0f, 1.0f, 0.0f}
+#define v3_left (struct v3){-1.0f, 0.0f, 0.0f}
+#define v3_down (struct v3){ 0.0f, -1.0f, 0.0f}
+#define v3_forward (struct v3){ 0.0f, 0.0f, 1.0f}
+#define v3_backward (struct v3){ 0.0f, 0.0f, -1.0f}
+
+#define v4_zero (struct v4){0.0f, 0.0f, 0.0f, 0.0f}
+#define v4_one (struct v4){1.0f, 1.0f, 1.0f, 1.0f}
+
+#define mat_identity (struct mat){ \
+ {1.0f, 0.0f, 0.0f, 0.0f}, \
+ {0.0f, 1.0f, 0.0f, 0.0f}, \
+ {0.0f, 0.0f, 1.0f, 0.0f}, \
+ {0.0f, 0.0f, 0.0f, 1.0f}}
+
+#define f32pi 3.14159265359f
+
+#define deg2rad(angle) (f32pi/180.0f*(angle))
+
+struct v2 fillv2(f32 a);
+struct v2 invv2(struct v2 a);
+struct v2 addv2(struct v2 a, struct v2 b);
+struct v2 subv2(struct v2 a, struct v2 b);
+struct v2 scalev2(struct v2 a, f32 s);
+struct v2 scalevv2(struct v2 a, struct v2 s);
+f32 dotv2(struct v2 a, struct v2 b);
+f32 len2v2(struct v2 a);
+f32 lenv2(struct v2 a);
+struct v2 normv2(struct v2 a);
+
+struct v3 fillv3(f32 a);
+struct v3 invv3(struct v3 a);
+struct v3 addv3(struct v3 a, struct v3 b);
+struct v3 subv3(struct v3 a, struct v3 b);
+struct v3 scalev3(struct v3 a, f32 s);
+struct v3 scalevv3(struct v3 a, struct v3 s);
+f32 dotv3(struct v3 a, struct v3 b);
+struct v3 crossv3(struct v3 l, struct v3 r);
+f32 len2v3(struct v3 a);
+f32 lenv3(struct v3 a);
+struct v3 normv3(struct v3 a);
+
+struct v4 fillv4(f32 a);
+struct v4 invv4(struct v4 a);
+struct v4 addv4(struct v4 a, struct v4 b);
+struct v4 subv4(struct v4 a, struct v4 b);
+struct v4 scalev4(struct v4 a, f32 s);
+struct v4 scalevv4(struct v4 a, struct v4 s);
+f32 dotv4(struct v4 a, struct v4 b);
+f32 len2v4(struct v4 a);
+f32 lenv4(struct v4 a);
+struct v4 normv4(struct v4 a);
+
+struct mat transpmat(struct mat m);
+struct mat mulmat(struct mat left, struct mat right);
+struct mat make_translate(struct v3 v);
+struct mat make_scale(struct v3 v);
+struct mat make_rotate(struct v3 x, struct v3 y, struct v3 z);
+struct mat translate_mat(struct mat m, struct v3 v);
+struct mat scale_mat(struct mat m, struct v3 v);
+struct mat rotate_mat(struct mat m, struct v3 angles);
+struct v4 mulmatv4(struct mat m, struct v4 v);
+
+i32 in_rect(struct v2 pos, struct rect rect);
+
+#endif
diff --git a/prbs.c b/prbs.c
new file mode 100644
index 0000000..818a110
--- /dev/null
+++ b/prbs.c
@@ -0,0 +1,132 @@
+#include "prbs.h"
+#include "macros.h"
+#include <string.h>
+#include <stdio.h>
+
+i32 cstrings_equal(const char *s1, const char *s2)
+{
+ assert(s1 && s2);
+ while (*s1++ == *s2++)
+ if (!*s1)
+ return 1;
+ return 0;
+}
+
+char *string_to_cstring(struct arena *arena, struct string str)
+{
+ u64 len = str.len + 1;
+ char *cstr = push_arena(arena, len);
+ memmove(cstr, str.ptr, len);
+ cstr[str.len] = 0;
+ return cstr;
+}
+
+i32 strings_equal(struct string str1, struct string str2)
+{
+ if (str1.len != str2.len)
+ return 0;
+ for (u64 i = 0; i < str1.len; ++i)
+ if (str1.ptr[i] != str2.ptr[i])
+ return 0;
+ return 1;
+}
+
+u8 *find_char(struct string str, i32 c)
+{
+ for (u64 i = 0; i < str.len; ++i)
+ if (str.ptr[i] == c)
+ return str.ptr + i;
+ return 0;
+}
+
+u8 *find_last_char(struct string str, i32 c)
+{
+ for (u64 i = str.len - 1; i > 0; --i)
+ if (str.ptr[i] == c)
+ return str.ptr + i;
+ return 0;
+}
+
+struct string string_from_range(u8 *start, u8 *end)
+{
+ assert(start);
+ assert(end);
+ assert(start < end);
+ return (struct string){start, end - start};
+}
+
+struct string get_dirname(struct string str)
+{
+ assert(str.len);
+ u8 *slash = find_last_char(str, '/');
+ if (!slash)
+ return str;
+ return string_from_range(str.ptr, slash);
+}
+
+struct string push_fstringv(struct arena *arena, const char *format, va_list args)
+{
+ char tmp[kilobytes(4) + 1];
+ i32 len = vsnprintf(tmp, kilobytes(4), format, args);
+ struct string str = {0};
+ if (len < 0)
+ return str;
+ u8 *ptr = push_arena(arena, len);
+ memmove(ptr, tmp, len);
+ return (struct string){ptr, len};
+}
+
+struct string push_fstring(struct arena *arena, const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ struct string str = push_fstringv(arena, format, args);
+ va_end(args);
+ return str;
+}
+
+void print_string(struct string str)
+{
+ assert(str.ptr && str.len);
+ printf("%.*s", str.len, str.ptr);
+}
+
+void push_string_to_list(struct arena *arena, struct string_list *list, i32 front, struct string str)
+{
+ struct string_node *node = push_arena(arena, sizeof(struct string_node));
+ node->str = str;
+ if (front)
+ dllpushfront(list->first, list->last, node);
+ else
+ dllpushback(list->first, list->last, node);
+ list->len += str.len;
+ list->cnt++;
+}
+
+void push_fstring_to_list(struct arena *arena, struct string_list *list, i32 front, const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ struct string str = push_fstringv(arena, format, args);
+ va_end(args);
+ push_string_to_list(arena, list, front, str);
+}
+
+struct string join_string_list(struct arena *arena, struct string_list *list)
+{
+ assert(arena);
+ assert(list);
+ struct string str = {arena->memory + arena->used, 0};
+ for (struct string_node *node = list->first; node; node = node->next) {
+ u8 *ptr = push_arena(arena, node->str.len);
+ memmove(ptr, node->str.ptr, node->str.len);
+ str.len += node->str.len;
+ }
+ return str;
+}
+
+void print_string_list(struct string_list *list)
+{
+ for (struct string_node *node = list->first; node; node = node->next)
+ print_string(node->str);
+}
diff --git a/prbs.h b/prbs.h
new file mode 100644
index 0000000..4547d7c
--- /dev/null
+++ b/prbs.h
@@ -0,0 +1,45 @@
+#ifndef prbs_h
+#define prbs_h
+
+#include "types.h"
+#include "arena.h"
+#include <stdarg.h>
+
+i32 cstrings_equal(const char *s1, const char *s2);
+
+struct string {
+ u8 *ptr;
+ u64 len;
+};
+
+struct string_node {
+ struct string str;
+ struct string_node *next;
+ struct string_node *prev;
+};
+
+struct string_list {
+ i32 cnt;
+ u64 len;
+ struct string_node *first;
+ struct string_node *last;
+};
+
+#define string_from_cliteral(str) (sturct string){(u8 *)(str), sizeof(str) - 1}
+#define expand_string(str) (i32)((str).len), ((str).ptr)
+
+char *string_to_cstring(struct arena *arena, struct string str);
+i32 strings_equal(struct string str1, struct string str2);
+u8 *find_char(struct string str, i32 c);
+u8 *find_last_char(struct string str, i32 c);
+struct string string_from_range(u8 *start, u8 *end);
+struct string get_dirname(struct string str);
+struct string push_fstringv(struct arena *arena, const char *format, va_list args);
+struct string push_fstring(struct arena *arena, const char *format, ...);
+void print_string(struct string str);
+void push_string_to_list(struct arena *arena, struct string_list *list, i32 front, struct string str);
+void push_fstring_to_list(struct arena *arena, struct string_list *list, i32 front, const char *format, ...);
+struct string join_string_list(struct arena *arena, struct string_list *list);
+void print_string_list(struct string_list *list);
+
+#endif
diff --git a/prbwin.c b/prbwin.c
new file mode 100644
index 0000000..81ad626
--- /dev/null
+++ b/prbwin.c
@@ -0,0 +1,77 @@
+#include "sys.h"
+
+void *sys_alloc(u64 length)
+{
+ assert(length);
+ void *result = VirtualAlloc(0, length, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
+ return result;
+}
+
+void sys_free(void *memory, u64 length)
+{
+ assert(memory);
+ assert(length);
+ assert(VirtualFree(memory, 0, MEM_RELEASE));
+}
+
+void die(const char *error_string, ...)
+{
+ va_list args;
+ va_start(args, error_string);
+ printf("error: ");
+ vprintf(error_string, args);
+ printf("\n");
+ va_end(args);
+ exit(1);
+}
+
+void info(const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ vprintf(format, args);
+ putchar('\n');
+ va_end(args);
+}
+
+char *read_entire_file(struct arena *arena, u64 *len, const char *filename)
+{
+ HANDLE file = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ,
+ 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+ if (file == INVALID_HANDLE_VALUE)
+ return 0;
+ LARGE_INTEGER lisize;
+ if (!GetFileSizeEx(file, &lisize))
+ goto error;
+ *len = lisize.QuadPart;
+ if (!*len)
+ goto error;
+ char *buffer = push_arena(arena, *len + 1);
+ DWORD bytes_read;
+ if (!ReadFile(file, *buffer, size, &bytes_read, 0)) {
+ pop_arena(arena, *len + 1);
+ goto error;
+ }
+ CloseHandle(file);
+ buffer[*len] = 0;
+ return buffer;
+error:
+ CloseHandle(file);
+ return 0;
+}
+
+char *get_exe_path(arena_t *arena)
+{
+ char path[max_path_len] = {0};
+ u32 len = GetModuleFileName(0, path, max_path_len);
+ if (!len)
+ return 0;
+ char *dir = strrchr(path, '\\');
+ if (!dir)
+ return path;
+ len = dir - path;
+ dir = push_arena(arena, len + 1);
+ memmove(dir, path, len);
+ dir[len] = 0;
+ return dir;
+}
diff --git a/sys.h b/sys.h
new file mode 100644
index 0000000..81d9f0c
--- /dev/null
+++ b/sys.h
@@ -0,0 +1,14 @@
+#ifndef sys_h
+#define sys_h
+
+#include "types.h"
+#include "arena.h"
+
+#define max_path_len 1024
+void *sys_alloc(u64 length);
+void sys_free(void *memory, u64 length);
+void die(const char *format, ...);
+void info(const char *format, ...);
+char *read_entire_file(struct arena *arena, u64 *len, const char *filename);
+
+#endif
diff --git a/types.h b/types.h
new file mode 100644
index 0000000..8297917
--- /dev/null
+++ b/types.h
@@ -0,0 +1,19 @@
+#ifndef types_h
+#define types_h
+
+#include <stdint.h>
+
+typedef int8_t i8;
+typedef int16_t i16;
+typedef int32_t i32;
+typedef int64_t i64;
+
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+typedef uint64_t u64;
+
+typedef float f32;
+typedef double f64;
+
+#endif