Compare commits
15 Commits
1a8a1c3052
...
sidenotes
| Author | SHA1 | Date | |
|---|---|---|---|
| f75a47e273 | |||
| 9eae560cae | |||
| b0529a9124 | |||
| 3df9c57482 | |||
| cb5163e1d9 | |||
| c309ac4c14 | |||
| 58c9d5f982 | |||
| dc9a68ad10 | |||
| db16dbda18 | |||
| 172630c2ee | |||
| 6dc7734c70 | |||
| 19a1ffbc98 | |||
| 2cce2859bb | |||
| 654239e29f | |||
| 50fbe3e196 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
**/build/*
|
||||
@@ -1,14 +1,13 @@
|
||||
$basic-border: 1px solid #bfbfbf;
|
||||
@import "style.scss";
|
||||
|
||||
.gmachine-instruction {
|
||||
display: flex;
|
||||
border: $basic-border;
|
||||
border-radius: 2px;
|
||||
@include bordered-block;
|
||||
}
|
||||
|
||||
.gmachine-instruction-name {
|
||||
padding: 10px;
|
||||
border-right: $basic-border;
|
||||
border-right: $standard-border;
|
||||
flex-grow: 1;
|
||||
flex-basis: 20%;
|
||||
text-align: center;
|
||||
@@ -20,7 +19,7 @@ $basic-border: 1px solid #bfbfbf;
|
||||
}
|
||||
|
||||
.gmachine-inner {
|
||||
border-bottom: $basic-border;
|
||||
border-bottom: $standard-border;
|
||||
width: 100%;
|
||||
|
||||
&:last-child {
|
||||
|
||||
@@ -37,6 +37,6 @@ add_executable(compiler
|
||||
# Configure compiler executable
|
||||
target_include_directories(compiler PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_include_directories(compiler PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
target_include_directories(compiler PUBLIC ${LLVM_DEFINITIONS})
|
||||
target_include_directories(compiler PUBLIC ${LLVM_INCLUDE_DIRS})
|
||||
target_compile_definitions(compiler PUBLIC ${LLVM_DEFINITIONS})
|
||||
target_link_libraries(compiler ${LLVM_LIBS})
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
defn main = { plus 320 6 }
|
||||
defn plus x y = { x + y }
|
||||
defn main = { sum 320 6 }
|
||||
defn sum x y = { x + y }
|
||||
|
||||
|
||||
@@ -5,3 +5,4 @@ defn length l = {
|
||||
Cons x xs -> { 1 + length xs }
|
||||
}
|
||||
}
|
||||
defn main = { length (Cons 1 (Cons 2 (Cons 3 Nil))) }
|
||||
|
||||
16
code/compiler/08/examples/works4.txt
Normal file
16
code/compiler/08/examples/works4.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
data List = { Nil, Cons Int List }
|
||||
|
||||
defn add x y = { x + y }
|
||||
defn mul x y = { x * y }
|
||||
|
||||
defn foldr f b l = {
|
||||
case l of {
|
||||
Nil -> { b }
|
||||
Cons x xs -> { f x (foldr f b xs) }
|
||||
}
|
||||
}
|
||||
|
||||
defn main = {
|
||||
foldr add 0 (Cons 1 (Cons 2 (Cons 3 (Cons 4 Nil)))) +
|
||||
foldr mul 1 (Cons 1 (Cons 2 (Cons 3 (Cons 4 Nil))))
|
||||
}
|
||||
17
code/compiler/08/examples/works5.txt
Normal file
17
code/compiler/08/examples/works5.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
data List = { Nil, Cons Int List }
|
||||
|
||||
defn sumZip l m = {
|
||||
case l of {
|
||||
Nil -> { 0 }
|
||||
Cons x xs -> {
|
||||
case m of {
|
||||
Nil -> { 0 }
|
||||
Cons y ys -> { x + y + sumZip xs ys }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defn ones = { Cons 1 ones }
|
||||
|
||||
defn main = { sumZip ones (Cons 1 (Cons 2 (Cons 3 Nil))) }
|
||||
@@ -108,12 +108,12 @@ void output_llvm(llvm_context& ctx, const std::string& filename) {
|
||||
std::error_code ec;
|
||||
llvm::raw_fd_ostream file(filename, ec, llvm::sys::fs::F_None);
|
||||
if (ec) {
|
||||
std::cerr << "Could not open output file: " << ec.message() << std::endl;
|
||||
throw 0;
|
||||
} else {
|
||||
llvm::TargetMachine::CodeGenFileType type = llvm::TargetMachine::CGFT_ObjectFile;
|
||||
llvm::legacy::PassManager pm;
|
||||
if (targetMachine->addPassesToEmitFile(pm, file, NULL, type)) {
|
||||
std::cerr << "Unable to emit target code" << std::endl;
|
||||
throw 0;
|
||||
} else {
|
||||
pm.run(ctx.module);
|
||||
file.close();
|
||||
@@ -136,7 +136,6 @@ void gen_llvm(const std::vector<definition_ptr>& prog) {
|
||||
for(auto& definition : prog) {
|
||||
definition->gen_llvm_second(ctx);
|
||||
}
|
||||
llvm::verifyModule(ctx.module);
|
||||
ctx.module.print(llvm::outs(), nullptr);
|
||||
output_llvm(ctx, "program.o");
|
||||
}
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
title: Compiling a Functional Language Using C++, Part 0 - Intro
|
||||
date: 2019-08-03T01:02:30-07:00
|
||||
tags: ["C and C++", "Functional Languages", "Compilers"]
|
||||
draft: true
|
||||
---
|
||||
During my last academic term, I was enrolled in a compilers course.
|
||||
We had a final project - develop a compiler for a basic Python subset,
|
||||
using LLVM. It was a little boring - virtually nothing about the compiler
|
||||
was __not__ covered in class, and it felt more like putting two puzzles
|
||||
was __not__ covered in class, and it felt more like putting two puzzle
|
||||
pieces together than building a real project.
|
||||
|
||||
Instead, I chose to implement a compiler for a functional programming language,
|
||||
@@ -138,4 +137,6 @@ Here are the posts that I've written so far for this series:
|
||||
* [Typechecking]({{< relref "03_compiler_typechecking.md" >}})
|
||||
* [Small Improvements]({{< relref "04_compiler_improvements.md" >}})
|
||||
* [Execution]({{< relref "05_compiler_execution.md" >}})
|
||||
* [Compilation]({{< relref "06_compiler_semantics.md" >}})
|
||||
* [Compilation]({{< relref "06_compiler_compilation.md" >}})
|
||||
* [Runtime]({{< relref "07_compiler_runtime.md" >}})
|
||||
* [LLVM]({{< relref "08_compiler_llvm.md" >}})
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
title: Compiling a Functional Language Using C++, Part 1 - Tokenizing
|
||||
date: 2019-08-03T01:02:30-07:00
|
||||
tags: ["C and C++", "Functional Languages", "Compilers"]
|
||||
draft: true
|
||||
---
|
||||
It makes sense to build a compiler bit by bit, following the stages we outlined in
|
||||
the first post of the series. This is because these stages are essentially a pipeline,
|
||||
@@ -48,7 +47,7 @@ are fairly simple - one or more digits is an integer, a few letters together
|
||||
are a variable name. In order to be able to efficiently break text up into
|
||||
such tokens, we restrict ourselves to __regular languages__. A language
|
||||
is defined as a set of strings (potentially infinite), and a regular
|
||||
language for which we can write a __regular expression__ to check if
|
||||
language is one for which we can write a __regular expression__ to check if
|
||||
a string is in the set. Regular expressions are a way of representing
|
||||
patterns that a string has to match. We define regular expressions
|
||||
as follows:
|
||||
@@ -77,7 +76,7 @@ Let's see some examples. An integer, such as 326, can be represented with \\([0-
|
||||
This means, one or more characters between 0 or 9. Some (most) regex implementations
|
||||
have a special symbol for \\([0-9]\\), written as \\(\\setminus d\\). A variable,
|
||||
starting with a lowercase letter and containing lowercase or uppercase letters after it,
|
||||
can be written as \\(\[a-z\]([a-z]+)?\\). Again, most regex implementations provide
|
||||
can be written as \\(\[a-z\]([a-zA-Z]+)?\\). Again, most regex implementations provide
|
||||
a special operator for \\((r_1+)?\\), written as \\(r_1*\\).
|
||||
|
||||
So how does one go about checking if a regular expression matches a string? An efficient way is to
|
||||
@@ -115,8 +114,8 @@ represent numbers directly into numbers, and do other small tasks.
|
||||
|
||||
So, what tokens do we have? From our arithmetic definition, we see that we have integers.
|
||||
Let's use the regex `[0-9]+` for those. We also have the operators `+`, `-`, `*`, and `/`.
|
||||
`-` is simple enough: the corresponding regex is `-`. We need to
|
||||
preface our `/`, `+` and `*` with a backslash, though, since they happen to also be modifiers
|
||||
The regex for `-` is simple enough: it's just `-`. However, we need to
|
||||
preface our `/`, `+` and `*` with a backslash, since they happen to also be modifiers
|
||||
in flex's regular expressions: `\/`, `\+`, `\*`.
|
||||
|
||||
Let's also represent some reserved keywords. We'll say that `defn`, `data`, `case`, and `of`
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
title: Compiling a Functional Language Using C++, Part 2 - Parsing
|
||||
date: 2019-08-03T01:02:30-07:00
|
||||
tags: ["C and C++", "Functional Languages", "Compilers"]
|
||||
draft: true
|
||||
---
|
||||
In the previous post, we covered tokenizing. We learned how to convert an input string into logical segments, and even wrote up a tokenizer to do it according to the rules of our language. Now, it's time to make sense of the tokens, and parse our language.
|
||||
|
||||
@@ -38,7 +37,7 @@ $$
|
||||
In practice, there are many ways of using a CFG to parse a programming language. Various parsing algorithms support various subsets
|
||||
of context free languages. For instance, top down parsers follow nearly exactly the structure that we had. They try to parse
|
||||
a nonterminal by trying to match each symbol in its body. In the rule \\(S \\rightarrow \\alpha \\beta \\gamma\\), it will
|
||||
first try to match \\(alpha\\), then \\(beta\\), and so on. If one of the three contains a nonterminal, it will attempt to parse
|
||||
first try to match \\(\\alpha\\), then \\(\\beta\\), and so on. If one of the three contains a nonterminal, it will attempt to parse
|
||||
that nonterminal following the same strategy. However, this leaves a flaw - For instance, consider the grammar
|
||||
$$
|
||||
\\begin{align}
|
||||
@@ -105,7 +104,7 @@ A\_{add} & \\rightarrow A\_{add}-A\_{mult} \\\\\\
|
||||
A\_{add} & \\rightarrow A\_{mult}
|
||||
\\end{align}
|
||||
$$
|
||||
The first rule matches another addition, added to the result of another addition. We use the addition in the body
|
||||
The first rule matches another addition, added to the result of a multiplication. Similarly, the second rule matches another addition, from which the result of a multiplication is then subtracted. We use the \\(A\_{add}\\) on the left side of \\(+\\) and \\(-\\) in the body
|
||||
because we want to be able to parse strings like `1+2+3+4`, which we want to view as `((1+2)+3)+4` (mostly because
|
||||
subtraction is [left-associative](https://en.wikipedia.org/wiki/Operator_associativity)). So, we want the top level
|
||||
of the tree to be the rightmost `+` or `-`, since that means it will be the "last" operation. You may be asking,
|
||||
@@ -150,7 +149,7 @@ What's the last \\(C\\)? We also want a "thing" to be a case expression. Here ar
|
||||
$$
|
||||
\\begin{align}
|
||||
C & \\rightarrow \\text{case} \\; A\_{add} \\; \\text{of} \\; \\{ L\_B\\} \\\\\\
|
||||
L\_B & \\rightarrow R \\; , \\; L\_B \\\\\\
|
||||
L\_B & \\rightarrow R \\; L\_B \\\\\\
|
||||
L\_B & \\rightarrow R \\\\\\
|
||||
R & \\rightarrow N \\; \\text{arrow} \\; \\{ A\_{add} \\} \\\\\\
|
||||
N & \\rightarrow \\text{lowerVar} \\\\\\
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
title: Compiling a Functional Language Using C++, Part 3 - Type Checking
|
||||
date: 2019-08-06T14:26:38-07:00
|
||||
draft: true
|
||||
tags: ["C and C++", "Functional Languages", "Compilers"]
|
||||
---
|
||||
I think tokenizing and parsing are boring. The thing is, looking at syntax
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
title: Compiling a Functional Language Using C++, Part 4 - Small Improvements
|
||||
date: 2019-08-06T14:26:38-07:00
|
||||
draft: true
|
||||
tags: ["C and C++", "Functional Languages", "Compilers"]
|
||||
---
|
||||
We've done quite a big push in the previous post. We defined
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
title: Compiling a Functional Language Using C++, Part 5 - Execution
|
||||
date: 2019-08-06T14:26:38-07:00
|
||||
draft: true
|
||||
tags: ["C and C++", "Functional Languages", "Compilers"]
|
||||
---
|
||||
{{< gmachine_css >}}
|
||||
@@ -47,7 +46,7 @@ defn snd p = {
|
||||
P x y -> { y }
|
||||
}
|
||||
}
|
||||
defn slow x = { returns x after waiting for 4 seconds }
|
||||
defn slow x = { returns x after waiting for 1 second }
|
||||
defn main = { fst (P (slow 320) (slow 6)) }
|
||||
```
|
||||
|
||||
@@ -559,7 +558,9 @@ rule to Unwind:
|
||||
{{< /gmachine_inner >}}
|
||||
{{< /gmachine >}}
|
||||
|
||||
Just one more! Sometimes, it's possible for a tree node to reference itself.
|
||||
Just a couple more special-purpose instructions, and we're done!
|
||||
|
||||
Sometimes, it's possible for a tree node to reference itself.
|
||||
For instance, Haskell defines the
|
||||
[fixpoint combinator](https://en.wikipedia.org/wiki/Fixed-point_combinator)
|
||||
as follows:
|
||||
@@ -587,9 +588,27 @@ We can allocate an indirection on the stack, and call Update on it when
|
||||
we've constructed a node. While we're constructing the tree, we can
|
||||
refer to the indirection when a self-reference is required.
|
||||
|
||||
Lastly, we also define a Pop instruction, which just removes
|
||||
some number of nodes from the stack. We want this because
|
||||
calling Update at the end of a function modifies a node further up the stack,
|
||||
leaving anything on top of the stack after that node as scratch work. We get
|
||||
rid of that scratch work simply by popping it.
|
||||
|
||||
{{< gmachine "Pop" >}}
|
||||
{{< gmachine_inner "Before">}}
|
||||
\( \text{Pop} \; n : i \quad a_1, a_2, ..., a_n : s \quad d \quad h \quad m \)
|
||||
{{< /gmachine_inner >}}
|
||||
{{< gmachine_inner "After" >}}
|
||||
\( i \quad s \quad d \quad h \quad m \)
|
||||
{{< /gmachine_inner >}}
|
||||
{{< gmachine_inner "Description" >}}
|
||||
Pop \(n\) nodes from the stack.
|
||||
{{< /gmachine_inner >}}
|
||||
{{< /gmachine >}}
|
||||
|
||||
That's it for the instructions. Knowing them, however, doesn't
|
||||
tell us what to do with our `ast` structs. We'll need to define
|
||||
rules to translate trees into these instructions, and I've already
|
||||
alluded to this when we went over `double 326`.
|
||||
However, this has already gotten pretty long,
|
||||
so we'll do it in the next post: [Part 6 - Compilation]({{< relref "06_compiler_semantics.md" >}}).
|
||||
so we'll do it in the next post: [Part 6 - Compilation]({{< relref "06_compiler_compilation.md" >}}).
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
title: Compiling a Functional Language Using C++, Part 6 - Compilation
|
||||
date: 2019-08-06T14:26:38-07:00
|
||||
draft: true
|
||||
tags: ["C and C++", "Functional Languages", "Compilers"]
|
||||
---
|
||||
In the previous post, we defined a machine for graph reduction,
|
||||
@@ -286,7 +285,7 @@ struct, called `type_data`:
|
||||
|
||||
When we create types from `definition_data`, we tag the corresponding constructors:
|
||||
|
||||
{{< codelines "C++" "compiler/06/definition.cpp" 53 69 >}}
|
||||
{{< codelines "C++" "compiler/06/definition.cpp" 54 71 >}}
|
||||
|
||||
Ah, but adding constructor info to the type doesn't solve the problem.
|
||||
Once we performed type checking, we don't keep
|
||||
@@ -407,11 +406,12 @@ We also add a `compile` method to definitions, since they contain
|
||||
our AST nodes. The method is empty for `defn_data`, and
|
||||
looks as follows for `definition_defn`:
|
||||
|
||||
{{< codelines "C++" "compiler/06/definition.cpp" 44 51 >}}
|
||||
{{< codelines "C++" "compiler/06/definition.cpp" 44 52 >}}
|
||||
|
||||
Notice that we terminate the function with Update. This
|
||||
Notice that we terminate the function with Update and Pop. This
|
||||
will turn the `ast_app` node that served as the "root"
|
||||
of the application into an indirection to the value that we have computed.
|
||||
Doing so will also remove all "scratch work" from the stack.
|
||||
In essense, this is how we can lazily evaluate expressions.
|
||||
|
||||
Finally, we make a function in our `main.cpp` file to compile
|
||||
@@ -436,12 +436,16 @@ PushInt(320)
|
||||
PushGlobal(plus)
|
||||
MkApp()
|
||||
MkApp()
|
||||
Update(0)
|
||||
Pop(0)
|
||||
|
||||
Push(1)
|
||||
Push(1)
|
||||
PushGlobal(+)
|
||||
PushGlobal(plus)
|
||||
MkApp()
|
||||
MkApp()
|
||||
Update(2)
|
||||
Pop(2)
|
||||
```
|
||||
|
||||
The first sequence of instructions is clearly `main`. It creates
|
||||
@@ -474,13 +478,14 @@ Jump(
|
||||
PushGlobal(length)
|
||||
MkApp()
|
||||
PushInt(1)
|
||||
PushGlobal(+)
|
||||
PushGlobal(plus)
|
||||
MkApp()
|
||||
MkApp()
|
||||
Slide(2)
|
||||
|
||||
)
|
||||
Update(1)
|
||||
Pop(1)
|
||||
```
|
||||
|
||||
We push the first (and only) parameter onto the stack. We then make
|
||||
@@ -496,4 +501,4 @@ into G-machine code. We're not done, however - our computers
|
||||
aren't G-machines. We'll need to compile our G-machine code to
|
||||
__machine code__ (we will use LLVM for this), implement the
|
||||
__runtime__, and develop a __garbage collector__. We'll
|
||||
tackle the first of these in the next post - see you there!
|
||||
tackle the first of these in the next post - [Part 7 - Runtime]({{< relref "07_compiler_runtime.md" >}}).
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
title: Compiling a Functional Language Using C++, Part 7 - Runtime
|
||||
date: 2019-08-06T14:26:38-07:00
|
||||
draft: true
|
||||
tags: ["C and C++", "Functional Languages", "Compilers"]
|
||||
---
|
||||
Wikipedia has the following definition for a __runtime__:
|
||||
@@ -158,4 +157,4 @@ that going, we be able to compile our code!
|
||||
|
||||
Next time, we will start work on converting our G-machine instructions
|
||||
into machine code. We will set up LLVM and get our very first
|
||||
fully functional compiled programs in Part 8 - LLVM.
|
||||
fully functional compiled programs in [Part 8 - LLVM]({{< relref "08_compiler_llvm.md" >}}).
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
title: Compiling a Functional Language Using C++, Part 8 - LLVM
|
||||
date: 2019-10-30T22:16:22-07:00
|
||||
draft: true
|
||||
tags: ["C and C++", "Functional Languages", "Compilers"]
|
||||
---
|
||||
|
||||
@@ -54,8 +53,6 @@ a `Module` object, which represents some collection of code and declarations
|
||||
|
||||
{{< codeblock "C++" "compiler/08/llvm_context.hpp" >}}
|
||||
|
||||
{{< todo >}} Explain creation functions. {{< /todo >}}
|
||||
|
||||
We include the LLVM context, builder, and module as members
|
||||
of the context struct. Since the builder and the module need
|
||||
the context, we initialize them in the constructor, where they
|
||||
@@ -118,10 +115,46 @@ for specifying the body of `node_base` and `node_app`.
|
||||
There's still more functionality packed into `llvm_context`.
|
||||
Let's next take a look into `custom_function`, and
|
||||
the `create_custom_function` method. Why do we need
|
||||
these?
|
||||
these? To highlight the need for the custom class,
|
||||
let's take a look at `instruction_pushglobal` which
|
||||
occurs at the G-machine level, and then at `alloc_global`,
|
||||
which will be a function call generated as part of
|
||||
the PushGlobal instruction. `instruction_pushglobal`'s
|
||||
only member variable is `name`, which stands for
|
||||
the name of the global function it's referencing. However,
|
||||
`alloc_global` requires an arity argument! We can
|
||||
try to get this information from the `llvm::Function`
|
||||
corresponding to the global we're trying to reference,
|
||||
but this doesn't get us anywhere: as far as LLVM
|
||||
is concerned, any global function only takes one
|
||||
parameter, the stack. The rest of the parameters
|
||||
are given through that stack, and their number cannot
|
||||
be easily deduced from the function alone.
|
||||
|
||||
Instead, we decide to store global functions together
|
||||
with their arity. We thus create a class to combine
|
||||
these two things (`custom_function`), define
|
||||
a map from global function names to instances
|
||||
of `custom_function`, and add a convenience method
|
||||
(`create_custom_function`) that takes care of
|
||||
constructing an `llvm::Function` object, creating
|
||||
a `custom_function`, and storing it in the map.
|
||||
|
||||
The implementation for `custom_function` is
|
||||
straightforward:
|
||||
|
||||
{{< codelines "C++" "compiler/08/llvm_context.cpp" 234 252 >}}
|
||||
|
||||
We create a function type, then a function, and finally
|
||||
initialize a `custom_function`. There's one thing
|
||||
we haven't seen yet in this function, which is the
|
||||
`BasicBlock` class. We'll get to what basic blocks
|
||||
are shortly, but for now it's sufficient to
|
||||
know that the basic block gives us a place to
|
||||
insert code.
|
||||
|
||||
This isn't the end of our `llvm_context` class: it also
|
||||
has a variety of `create_*` methods! Let's take a look
|
||||
has a variety of other `create_*` methods! Let's take a look
|
||||
at their signatures. Most return either `void`,
|
||||
`llvm::ConstantInt*`, or `llvm::Value*`. Since
|
||||
`llvm::ConstantInt*` is a subclass of `llvm::Value*`, let's
|
||||
@@ -168,7 +201,7 @@ Assigned to each variable is `llvm::Value`. The LLVM documentation states:
|
||||
|
||||
It's important to understand that `llvm::Value` __does not store the result of the computation__.
|
||||
It rather represents how something may be computed. 1 is a value because it computed by
|
||||
just returning. `x + 1` is a value because it is computed by adding the value inside of
|
||||
just returning 1. `x + 1` is a value because it is computed by adding the value inside of
|
||||
`x` to 1. Since we cannot modify a variable once we've declared it, we will
|
||||
keep assigning intermediate results to new variables, constructing new values
|
||||
out of values that we've already specified.
|
||||
@@ -251,36 +284,27 @@ represented by the pointer, while the second offset
|
||||
gives the index of the field we want to access. We
|
||||
want to dereference the pointer (`num_pointer[0]`),
|
||||
and we want the second field (`1`, when counting from 0).
|
||||
Thus, we call CreateGEP with these offsets and our pointers.
|
||||
Thus, we call `CreateGEP` with these offsets and our pointers.
|
||||
|
||||
This still leaves us with a pointer to a number, rather
|
||||
than the number itself. To dereference the pointer, we use
|
||||
`CreateLoad`. This gives us the value of the number node,
|
||||
which we promptly return.
|
||||
|
||||
Let's envision a `gen_llvm` method on the `instruction` struct.
|
||||
We need access to all the other functions from our runtime,
|
||||
such as `stack_init`, and functions from our program such
|
||||
as `f_custom_function`. Thus, we need access to our
|
||||
`llvm_context`. The current basic block is part
|
||||
of the builder, which is part of the context, so that's
|
||||
also taken care of. There's only one more thing that we will
|
||||
need, and that's access to the `llvm::Function` that's
|
||||
currently being compiled. To understand why, consider
|
||||
the signature of `f_main` from the previous post:
|
||||
This concludes our implementation of the `llvm_context` -
|
||||
it's time to move on to the G-machine instructions.
|
||||
|
||||
```C
|
||||
void f_main(struct stack*);
|
||||
```
|
||||
### G-machine Instructions to LLVM IR
|
||||
|
||||
The function takes a stack as a parameter. What if
|
||||
we want to try use this stack in a method call, like
|
||||
`stack_push(s, node)`? We need to have access to the
|
||||
LLVM representation of the stack parameter. The easiest
|
||||
way to do this is to use `llvm::Function::arg_begin()`,
|
||||
which gives the first argument of the function. We thus
|
||||
carry the function pointer throughout our code generation
|
||||
methods.
|
||||
Let's now envision a `gen_llvm` method on the `instruction` struct,
|
||||
which will turn the still-abstract G-machine instruction
|
||||
into tangible, close-to-metal LLVM IR. As we've seen
|
||||
in our implementation of `llvm_context`, to access the stack, we need access to the first
|
||||
argument of the function we're generating. Thus, we need this method
|
||||
to accept the function whose instructions are
|
||||
being converted to LLVM. We also pass in the
|
||||
`llvm_context`, since it contains the LLVM builder,
|
||||
context, module, and a map of globally declared functions.
|
||||
|
||||
With these things in mind, here's the signature for `gen_llvm`:
|
||||
|
||||
@@ -288,4 +312,267 @@ With these things in mind, here's the signature for `gen_llvm`:
|
||||
virtual void gen_llvm(llvm_context&, llvm::Function*) const;
|
||||
```
|
||||
|
||||
{{< todo >}} Fix pointer type inconsistencies. {{< /todo >}}
|
||||
Let's get right to it! `instruction_pushint` gives us an easy
|
||||
start:
|
||||
|
||||
{{< codelines "C++" "compiler/08/instruction.cpp" 17 19 >}}
|
||||
|
||||
We create an LLVM integer constant with the value of
|
||||
our integer, and push it onto the stack.
|
||||
|
||||
`instruction_push` is equally terse:
|
||||
|
||||
{{< codelines "C++" "compiler/08/instruction.cpp" 37 39 >}}
|
||||
|
||||
We simply peek at the value of the stack at the given
|
||||
offset (an integer of the same size as `size_t`, which
|
||||
we create using `create_size`). Once we have the
|
||||
result of the peek, we push it onto the stack.
|
||||
|
||||
`instruction_pushglobal` is more involved. Let's take a look:
|
||||
|
||||
{{< codelines "C++" "compiler/08/instruction.cpp" 26 30 >}}
|
||||
|
||||
First, we retrive the `custom_function` associated with
|
||||
the given global name. We then create an LLVM integer
|
||||
constant representing the arity of the function,
|
||||
and then push onto the stack the result of `alloc_global`,
|
||||
giving it the function and arity just like it expects.
|
||||
|
||||
`instruction_pop` is also short, and doesn't require much
|
||||
further explanation:
|
||||
|
||||
{{< codelines "C++" "compiler/08/instruction.cpp" 46 48 >}}
|
||||
|
||||
Some other instructions, such as `instruction_update`,
|
||||
`instruction_pack`, `instruction_split`, `instruction_slide`,
|
||||
`instruction_alloc` and `instruction_eval` are equally as simple,
|
||||
and we omit them for the purpose of brevity.
|
||||
|
||||
What remains are two "meaty" functions, `instruction_jump` and
|
||||
`instruction_binop`. Let's start with the former:
|
||||
|
||||
{{< codelines "C++" "compiler/08/instruction.cpp" 101 123 >}}
|
||||
|
||||
This is the one and only function in which we have to take
|
||||
care of control flow. Conceptually, depending on the tag
|
||||
of the `node_data` at the top of the stack, we want
|
||||
to pick one of many branches and jump to it.
|
||||
As we discussed, a basic block has to be executed in
|
||||
its entirety; since the branches of a case expression
|
||||
are mutually exclusive (only one of them is executed in any given case),
|
||||
we have to create a separate basic block for each branch.
|
||||
Given these blocks, we then want to branch to the correct one
|
||||
using the tag of the node on top of the stack.
|
||||
|
||||
This is exactly what we do in this function. We first peek
|
||||
at the node on top of the stack, and use `CreateGEP` through
|
||||
`unwrap_data_tag` to get access to its tag. What we then
|
||||
need is LLVM's switch instruction, created using `CreateSwitch`.
|
||||
We must provide the switch with a "default" case in case
|
||||
the tag value is something we don't recognize. To do this,
|
||||
we create a "safety" `BasicBlock`. With this new safety
|
||||
block in hand, we're able to call `CreateSwitch`, giving it
|
||||
the tag value to switch on, the safety block to default to,
|
||||
and the expected number of branches (to optimize memory allocation).
|
||||
|
||||
Next, we create a vector of blocks, and for each branch,
|
||||
we append to it a corresponding block `branch_block`, into
|
||||
which we insert the LLVM IR corresponding to the
|
||||
instructions of the branch. No matter the branch we take,
|
||||
we eventually want to come back to the same basic block,
|
||||
which will perform the usual function cleanup via Update and Slide.
|
||||
We re-use the safety block for this, and use `CreateBr` at the
|
||||
end of each `branch_block` to perform an unconditional jump.
|
||||
|
||||
After we create each of the blocks, we use the `tag_mappings`
|
||||
to add cases to the switch instruction, using `addCase`. Finally,
|
||||
we set the builder's insertion point to the safety block,
|
||||
meaning that the next instructions will insert their
|
||||
LLVM IR into that block. Since we have all branches
|
||||
jump to the safety block at the end, this means that
|
||||
no matter which branch we take in the case expression,
|
||||
we will still execute the subsequent instructions as expected.
|
||||
|
||||
Let's now look at `instruction_binop`:
|
||||
|
||||
{{< codelines "C++" "compiler/08/instruction.cpp" 139 150 >}}
|
||||
|
||||
In this instruction, we pop and unwrap two integers from
|
||||
the stack (assuming they are integers). Depending on
|
||||
the type of operation the instruction is set to, we
|
||||
then push the result of the corresponding LLVM
|
||||
instruction. `PLUS` calls LLVM's `CreateAdd` to insert
|
||||
addition, `MINUS` calls `CreateSub`, and so on. No matter
|
||||
what the operation was, we push the result onto the stack.
|
||||
|
||||
That's all for our instructions! We're so very close now. Let's
|
||||
move on to compiling definitions.
|
||||
|
||||
### Definitions to LLVM IR
|
||||
As with typechecking, to allow for mutually recursive functions,
|
||||
we need to be able each global function from any other function.
|
||||
We then take the same approah as before, going in two passes.
|
||||
This leads to two new methods for `definition`:
|
||||
|
||||
```C++
|
||||
virtual void gen_llvm_first(llvm_context& ctx) = 0;
|
||||
virtual void gen_llvm_second(llvm_context& ctx) = 0;
|
||||
```
|
||||
|
||||
The first pass is intended to register all functions into
|
||||
the `llvm_context`, making them visible to other functions.
|
||||
The second pass is used to actually generate the code for
|
||||
each function, now having access to all the other global
|
||||
functions. Let's see the implementation for `gen_llvm_first`
|
||||
for `definition_defn`:
|
||||
|
||||
{{< codelines "C++" "compiler/08/definition.cpp" 58 60 >}}
|
||||
|
||||
Since `create_custom_function` already creates a function
|
||||
__and__ registers it with `llvm_context`, this is
|
||||
all we need. Note that we created a new member variable
|
||||
for `definition_defn` which stores this newly created
|
||||
function. In the second pass, we will populate this
|
||||
function with LLVM IR from the definition's instructions.
|
||||
|
||||
We actually create functions for each of the constructors
|
||||
of data types, but they're quite special: all they do is
|
||||
pack their arguments! Since they don't need access to
|
||||
the other global functions, we might as well create
|
||||
their bodies then and there:
|
||||
|
||||
{{< codelines "C++" "compiler/08/definition.cpp" 101 112 >}}
|
||||
|
||||
Like in `definition_defn`, we use `create_custom_function`.
|
||||
However, we then use `SetInsertPoint` to configure our builder to insert code into
|
||||
the newly created function (which already has a `BasicBlock`,
|
||||
thanks to that one previously unexplained line in `create_custom_function`!).
|
||||
Since we decided to only include the Pack instruction, we generate
|
||||
a call to it directly using `create_pack`. We follow this
|
||||
up with `CreateRetVoid`, which tells LLVM that this is
|
||||
the end of the function, and that it is now safe to return
|
||||
from it.
|
||||
|
||||
Great! We now implement the second pass of `gen_llvm`. In
|
||||
the case of `definition_defn`, we do almost exactly
|
||||
what we did in the first pass of `definition_data`:
|
||||
|
||||
{{< codelines "C++" "compiler/08/definition.cpp" 62 68 >}}
|
||||
|
||||
As for `definition_data`, we have nothing to do in the
|
||||
second pass. We're done!
|
||||
|
||||
### Getting Results
|
||||
We're almost there. Two things remain. The first: our implementation
|
||||
of `ast_binop`, implement each binary operation as simply a function call:
|
||||
`+` calls `f_plus`, and so on. But so far, we have not implemented
|
||||
`f_plus`, or any other binary operator function. We do this
|
||||
in `main.cpp`, creating a function `gen_llvm_internal_op`:
|
||||
|
||||
{{< codelines "C++" "compiler/08/main.cpp" 70 83 >}}
|
||||
|
||||
We create a simple function body. We then append G-machine
|
||||
instructions that take each argument, evaluate it,
|
||||
and then perform the corresponding binary operation.
|
||||
With these instructions in the body, we insert
|
||||
them into a new function, just like we did in our code
|
||||
for `definition_defn` and `definition_data`.
|
||||
|
||||
Finally, we write our `gen_llvm` function that we will
|
||||
call from `main`:
|
||||
|
||||
{{< codelines "C++" "compiler/08/main.cpp" 125 141 >}}
|
||||
|
||||
It first creates the functions for
|
||||
`+`, `-`, `*`, and `/`. Then, it calls the first
|
||||
pass of `gen_llvm` on all definitions, followed
|
||||
by the second pass. Lastly, it uses LLVM's built-in
|
||||
functionality to print out the generated IR in
|
||||
our module, and then uses a function `output_llvm`
|
||||
to create an object file ready for linking.
|
||||
|
||||
To be very honest, I took the `output_llvm` function
|
||||
almost entirely from instructional material for my university's
|
||||
compilers course. The gist of it, though, is: we determine
|
||||
the target architecture and platform, specify a "generic" CPU,
|
||||
create a default set of options, and then generate an object file.
|
||||
Here it is:
|
||||
|
||||
{{< codelines "C++" "compiler/08/main.cpp" 85 123 >}}
|
||||
|
||||
We now add a `generate_llvm` call to `main`.
|
||||
|
||||
Are we there?
|
||||
|
||||
Let's try to compile our first example, `works1.txt`. The
|
||||
file:
|
||||
|
||||
{{< rawblock "compiler/08/examples/works1.txt" >}}
|
||||
|
||||
We run the following commands in our build directory:
|
||||
|
||||
```
|
||||
./compiler < ../examples/work1.txt
|
||||
gcc -no-pie ../runtime.c program.o
|
||||
./a.out
|
||||
```
|
||||
|
||||
Nothing happens. How anticlimactic! Our runtime has no way of
|
||||
printing out the result of the evaluation. Let's change that:
|
||||
|
||||
{{< codelines "C++" "compiler/08/runtime.c" 157 183 >}}
|
||||
|
||||
Rerunning our commands, we get:
|
||||
|
||||
```
|
||||
Result: 326
|
||||
```
|
||||
|
||||
The correct result! Let's try it with `works2.txt`:
|
||||
|
||||
{{< rawblock "compiler/08/examples/works2.txt" >}}
|
||||
|
||||
And again, we get the right answer:
|
||||
|
||||
```
|
||||
Result: 326
|
||||
```
|
||||
|
||||
This is child's play, though. Let's try with something
|
||||
more complicated, like `works3.txt`:
|
||||
|
||||
{{< rawblock "compiler/08/examples/works3.txt" >}}
|
||||
|
||||
Once again, our program does exactly what we intended:
|
||||
|
||||
```
|
||||
Result: 3
|
||||
```
|
||||
|
||||
Alright, this is neat, but we haven't yet confirmed that
|
||||
lazy evaluation works. How about we try it with
|
||||
`works5.txt`:
|
||||
|
||||
{{< rawblock "compiler/08/examples/works5.txt" >}}
|
||||
|
||||
Yet again, the program works:
|
||||
|
||||
```
|
||||
Result: 9
|
||||
```
|
||||
|
||||
At last, we have a working compiler!
|
||||
|
||||
While this is a major victory, we are not yet
|
||||
finished with the compiler altogether. While
|
||||
we allocate nodes whenever we need them, we
|
||||
have not once uttered the phrase `free` in our
|
||||
runtime. Our language works, but we have no way
|
||||
of comparing numbers, no lambdas, no `let/in`.
|
||||
In the next several posts, we will improve
|
||||
our compiler to properly free unused memory
|
||||
usign a __garbage collector__, implement
|
||||
lambda functions using __lambda lifting__,
|
||||
and use our Alloc instruction to implement `let/in` expressions. See
|
||||
you there!
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
title: "Thoughts on Better Explanations"
|
||||
date: 2019-10-12T00:33:02-07:00
|
||||
tags: ["Language Server Protocol"]
|
||||
draft: true
|
||||
---
|
||||
|
||||
How do you explain how to write a program?
|
||||
|
||||
178
content/blog/sidenotes.md
Normal file
178
content/blog/sidenotes.md
Normal file
@@ -0,0 +1,178 @@
|
||||
---
|
||||
title: JavaScript-Free Sidenotes in Hugo
|
||||
date: 2019-12-07T00:23:34-08:00
|
||||
tags: ["Website", "Hugo", "CSS"]
|
||||
---
|
||||
|
||||
A friend recently showed me a website, the design of which I really liked:
|
||||
Gwern Branwen's [personal website](https://www.gwern.net/index). In particular,
|
||||
I found that __sidenotes__ were a feature that I didn't even know I needed.
|
||||
A lot of my writing seems to use small parenthesized remarks (like this), which,
|
||||
although it doesn't break the flow in a grammatical sense, lengthens the
|
||||
sentence, and makes it harder to follow. Since I do my best to write content
|
||||
to help explain stuff (like the [compiler series]({{ relref "00_compiler_intro.md" }})),
|
||||
making sentences __more__ difficult to understand is a no-go.
|
||||
|
||||
So, what do they look like?
|
||||
{{< sidenote "right" "example-note" "Here's an example sidenote." >}}
|
||||
This is this example note's content.
|
||||
{{< /sidenote >}}
|
||||
If you're on a mobile device, the content is hidden by default: there's no
|
||||
"side" on which the note fits. In this case, you can click or tap the underlined
|
||||
portion of the text, which is the part to which the sidenote is related or refers to.
|
||||
Otherwise, the example sidenote should be visible
|
||||
{{< sidenote "left" "left-note" "on the right side of the screen." >}}
|
||||
Sidenotes can also appear on the left of the screen, to help prevent situations
|
||||
in which there are too many sidenotes and not enough space.
|
||||
{{< /sidenote >}}
|
||||
|
||||
A major goal of mine in implementing these sidenotes was to avoid the use of JavaScript.
|
||||
This is driven by my recent installation of uMatrix. uMatrix is an extension
|
||||
that blocks JavaScript loaded from domains other than the one you're visiting.
|
||||
To my frustration, a lot of the websites I commonly visit ended up broken:
|
||||
[Canvas](https://github.com/instructure/canvas-lms), Discord, YouTube, and
|
||||
Disquss all fail catastrophically when they aren't allowed to load dozens of scripts
|
||||
from various sources. Out of spite, I want my site to work without any JavaScript,
|
||||
and these notes are no exception.
|
||||
|
||||
### Implementation
|
||||
Some of this work has been inspired by
|
||||
[this article](https://www.kooslooijesteijn.net/blog/semantic-sidenotes).
|
||||
The first concern was not having to write raw HTML to add the side notes,
|
||||
but this is fairly simple with Hugo's shortcodes: I write the HTML once in
|
||||
a new `sidenote` shortcode, then call the shortcode from my posts. The next
|
||||
issue is a matter of HTML standards. Markdown rendering generates `<p>` tags.
|
||||
According the to spec, `<p>` tags cannot have a block element inside
|
||||
them. When you _try_ to put a block element, such as `<div>` inside `<p>`,
|
||||
the browser will automatically close the `<p>` tag, breaking the rest of the page.
|
||||
So, even though conceptually (and visually) the content of the sidenote is a block,
|
||||
{{< sidenote "right" "markdown-note" "it has to be inside an inline element." >}}
|
||||
There's another consequence to this. Hugo implements Markdown inside shortcodes
|
||||
by rendering the "inner" part of the shortcode, substituting the result into the
|
||||
shortcode's definition, and then finally placing that into the final output. Since
|
||||
the Markdown renderer wraps text in paragraphs, which are block elements, the
|
||||
inside of the shortcode ends up with block elements. The same tag-closing issue
|
||||
manifests, and the view ends up broken. So, Markdown cannot be used inside sidenotes.
|
||||
{{< /sidenote >}}
|
||||
|
||||
That's not too bad, overall. We end up with a shortcode definition as follows:
|
||||
```HTML
|
||||
<span class="sidenote">
|
||||
<label class="sidenote-label" for="{{ .Get 1 }}">{{ .Get 2 }}</label>
|
||||
<input class="sidenote-checkbox" type="checkbox" id="{{ .Get 1 }}"></input>
|
||||
<span class="sidenote-content sidenote-{{ .Get 0 }}">
|
||||
{{ .Inner }}
|
||||
</span>
|
||||
</span>
|
||||
```
|
||||
As Koos points out, "label" works as a semantic tag for the text that references
|
||||
the sidenote. It also helps us with the checkbox
|
||||
`<input>`, which we will examine later. Since it will receive its own style,
|
||||
the inner content of the sidenote is wrapped in another `<span>`. Let's
|
||||
get started on styling the parts of a sidenote, beginning with the content:
|
||||
|
||||
```SCSS
|
||||
.sidenote-content {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: $sidenote-width;
|
||||
box-sizing: border-box;
|
||||
margin-top: -1.5em;
|
||||
|
||||
&.sidenote-right {
|
||||
right: 0;
|
||||
margin-right: -($sidenote-width + $sidenote-offset);
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
As you can see from the sidenotes on this page, they are displayed as a block.
|
||||
We start with that, then switch the sidenotes to be positioned absolutely, so that
|
||||
we can place them exactly to the right of the content, and then some. We also
|
||||
make sure that the box is sized __exactly__ the amount in `$sidenote-width`, by
|
||||
ensuring that the border and padding are included in the size calculation
|
||||
using `border-box`. We also hide the checkbox:
|
||||
|
||||
```SCSS
|
||||
.sidenote-checkbox {
|
||||
display: none;
|
||||
}
|
||||
```
|
||||
|
||||
Finally, let's make one more adjustment to the sidenote and its label: when
|
||||
you hover over one of them, the other will change its appearence slightly,
|
||||
so that you can tell which note refers to which label. We can do so by detecting
|
||||
hover of the parent element:
|
||||
|
||||
```SCSS
|
||||
.sidenote {
|
||||
&:hover {
|
||||
.sidenote-label { /* style for the label */ }
|
||||
.sidenote-content { /* style for the sidenote */ }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Hiding and Showing
|
||||
So far, it's hard to imagine where JavaScript would come in. If you were
|
||||
always looking at the page from a wide-screen machine, it wouldn't at all.
|
||||
Unfortunately phones don't leave a lot of room for margins and sidenotes, so to
|
||||
make sure that these notes are visible to mobile users, we want to show
|
||||
them inline. Since the entire idea of sidenotes is to present more information
|
||||
__without__ interrupting the main text, we don't want to plop something down
|
||||
in the middle of the screen by default. So we hide sidenotes, and show them only
|
||||
when their label is clicked.
|
||||
|
||||
Gwern's site doesn't show the notes on mobile at all (when simulated using Firefox's
|
||||
responsive design mode), and Koos uses JavaScript to toggle the sidenote text. We will
|
||||
go another route.
|
||||
|
||||
This is where the checkbox `<input>` comes in. When the `<input>` checkbox is
|
||||
checked, we show the sidenote text, as a block, in the middle of the page. When
|
||||
it is not checked, we keep it hidden. Of course, keeping a checkbox in the middle
|
||||
of the page is not pretty, so we keep it hidden. Rather than clicking the checkbox
|
||||
directly,
|
||||
{{< sidenote "right" "accessibility-note" "the users can click the text that refers to the sidenote," >}}
|
||||
I'm not sure about the accessibility of such an arrangement. The label is semantic, sure, but
|
||||
the checkbox is more sketchy. Take this design with a grain of salt.
|
||||
{{< /sidenote >}} which happens
|
||||
to also be a label for the checkbox input. Clicking the label toggles the checkbox,
|
||||
and with it the display of the sidenote. We can use the following CSS to
|
||||
get that to work:
|
||||
|
||||
```SCSS
|
||||
.sidenote-content {
|
||||
// ...
|
||||
@media screen and
|
||||
(max-width: $container-width + 2 * ($sidenote-width + 2 * $sidenote-offset)) {
|
||||
position: static;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
display: none;
|
||||
|
||||
.sidenote-checkbox:checked ~ & {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.sidenote-right {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
We put the position back to `static`, and add margins on the top and bottom of the node.
|
||||
We keep the `display` to `none`, unless the checkbox contained in the sidenote span
|
||||
is checked. Finally, we reset the margin we created earlier, since we're not moving this
|
||||
note anywhere.
|
||||
|
||||
### Conclusion
|
||||
Here, we've implemented sidenotes in Hugo with zero JavaScript. They work well on
|
||||
both mobile and desktop devices, though their accessibility is, at present,
|
||||
somewhat uncertain.
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
title: Switching to a Static Site Generator
|
||||
date: 2019-08-05T01:13:58-07:00
|
||||
draft: true
|
||||
tags: ["Website"]
|
||||
---
|
||||
A long time ago, I decided to try out Jekyll for my website. However, it all felt too
|
||||
|
||||
70
themes/vanilla/assets/scss/sidenotes.scss
Normal file
70
themes/vanilla/assets/scss/sidenotes.scss
Normal file
@@ -0,0 +1,70 @@
|
||||
@import "style.scss";
|
||||
|
||||
$sidenote-width: 350px;
|
||||
$sidenote-offset: 15px;
|
||||
|
||||
.sidenote {
|
||||
&:hover {
|
||||
.sidenote-label {
|
||||
background-color: $primary-color;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sidenote-content {
|
||||
border: 2px dashed;
|
||||
padding: 9px;
|
||||
border-color: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidenote-label {
|
||||
border-bottom: 2px solid $primary-color;
|
||||
}
|
||||
|
||||
.sidenote-checkbox {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidenote-content {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: $sidenote-width;
|
||||
margin-top: -1.5em;
|
||||
|
||||
&.sidenote-right {
|
||||
right: 0;
|
||||
margin-right: -($sidenote-width + $sidenote-offset);
|
||||
}
|
||||
|
||||
&.sidenote-left {
|
||||
left: 0;
|
||||
margin-left: -($sidenote-width + $sidenote-offset);
|
||||
}
|
||||
|
||||
@media screen and
|
||||
(max-width: $container-width + 2 * ($sidenote-width + 2 * $sidenote-offset)) {
|
||||
position: static;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
display: none;
|
||||
|
||||
.sidenote-checkbox:checked ~ & {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.sidenote-left {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
&.sidenote-right {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
@include bordered-block;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
}
|
||||
@@ -3,9 +3,16 @@ $primary-color: #36e281;
|
||||
$primary-color-dark: darken($primary-color, 10%);
|
||||
$code-color: #f0f0f0;
|
||||
$code-color-dark: darken($code-color, 10%);
|
||||
$border-color: #bfbfbf;
|
||||
$font-heading: "Lora", serif;
|
||||
$font-body: "Raleway", serif;
|
||||
$font-code: "Inconsolata", monospace;
|
||||
$standard-border: 1px solid $border-color;
|
||||
|
||||
@mixin bordered-block {
|
||||
border: $standard-border;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: $font-body;
|
||||
@@ -15,11 +22,16 @@ body {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
main {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-bottom: .1em;
|
||||
margin-top: .5em;
|
||||
font-family: $font-heading;
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
|
||||
a {
|
||||
color: black;
|
||||
@@ -60,6 +72,7 @@ pre code {
|
||||
color: white;
|
||||
transition: color 0.25s;
|
||||
transition: background-color 0.25s;
|
||||
text-align: left;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="{{ .Site.Language.Lang }}">
|
||||
{{- partial "head.html" . -}}
|
||||
<body>
|
||||
{{- partial "header.html" . -}}
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
<link href="https://fonts.googleapis.com/css?family=Inconsolata|Lora|Raleway" rel="stylesheet">
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
|
||||
{{ $style := resources.Get "scss/style.scss" | resources.ToCSS | resources.Minify }}
|
||||
{{ $sidenotes:= resources.Get "scss/sidenotes.scss" | resources.ToCSS | resources.Minify }}
|
||||
<link rel="stylesheet" href="{{ $style.Permalink }}">
|
||||
<link rel="stylesheet" href="{{ $sidenotes.Permalink }}">
|
||||
|
||||
<script src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML' async></script>
|
||||
{{ template "_internal/google_analytics.html" . }}
|
||||
|
||||
7
themes/vanilla/layouts/shortcodes/sidenote
Normal file
7
themes/vanilla/layouts/shortcodes/sidenote
Normal file
@@ -0,0 +1,7 @@
|
||||
<span class="sidenote">
|
||||
<label class="sidenote-label" for="{{ .Get 1 }}">{{ .Get 2 }}</label>
|
||||
<input class="sidenote-checkbox" type="checkbox" id="{{ .Get 1 }}"></input>
|
||||
<span class="sidenote-content sidenote-{{ .Get 0 }}">
|
||||
{{ .Inner }}
|
||||
</span>
|
||||
</span>
|
||||
Reference in New Issue
Block a user