Skip to content

Commit 01302ef

Browse files
Fix syntax highlighting (#1842)
1 parent 5a0bcf7 commit 01302ef

12 files changed

Lines changed: 186 additions & 140 deletions

src/content/blog/a-peek-inside-elixir-s-parallel-compiler.md

Lines changed: 103 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,25 @@ The idea of the parallel compiler is very simple: for each file we want to compi
1515

1616
In Elixir, we could write this code as follows:
1717

18-
def spawn_compilers([current | files], output) do
19-
parent = Process.self()
20-
child = spawn_link(fn ->
21-
:elixir_compiler.file_to_path(current, output)
22-
send parent, { :compiled, Process.self() }
23-
end)
24-
receive do
25-
{ :compiled, ^child } ->
26-
spawn_compilers(files, output)
27-
{ :EXIT, ^child, { reason, where } } ->
28-
:erlang.raise(:error, reason, where)
29-
end
30-
end
31-
32-
def spawn_compilers([], _output) do
33-
:done
34-
end
18+
```elixir
19+
def spawn_compilers([current | files], output) do
20+
parent = Process.self()
21+
child = spawn_link(fn ->
22+
:elixir_compiler.file_to_path(current, output)
23+
send parent, { :compiled, Process.self() }
24+
end)
25+
receive do
26+
{ :compiled, ^child } ->
27+
spawn_compilers(files, output)
28+
{ :EXIT, ^child, { reason, where } } ->
29+
:erlang.raise(:error, reason, where)
30+
end
31+
end
32+
33+
def spawn_compilers([], _output) do
34+
:done
35+
end
36+
```
3537

3638
In the first line, we define a function named `spawn_compilers` that receives two arguments, the first is a list of files to compile and the second is a string telling us where to write the compiled file. The first argument is represented as a list with head and tail (`[current | files]`) where the top of the list is assigned to `current` and the remaining items to `files`. If the list is empty, the first clause of `spawn_compilers` is not going to match, the clause `spawn_compilers([], _output)` defined at the end will instead.
3739

@@ -53,19 +55,21 @@ With this code, we were able to compile each file inside a different process. Ho
5355

5456
Imagine that we have two files, `a.ex` and `b.ex`, with the following contents:
5557

56-
# a.ex
57-
defmodule A do
58-
B.define
59-
end
60-
61-
# b.ex
62-
defmodule B do
63-
defmacro define do
64-
quote do
65-
def one, do: 1
66-
end
67-
end
58+
```elixir
59+
# a.ex
60+
defmodule A do
61+
B.define
62+
end
63+
64+
# b.ex
65+
defmodule B do
66+
defmacro define do
67+
quote do
68+
def one, do: 1
6869
end
70+
end
71+
end
72+
```
6973

7074
In order to compile `A`, we need to ensure that `B` is already compiled and loaded so we can invoke the `define` macro. This means the file `a.ex` depends on the file `b.ex`. When compiling files in parallel, we want to be able to detect such cases and automatically handle them.
7175

@@ -79,87 +83,95 @@ By default, Elixir (and Erlang) code is autoloaded. This means that, if we invok
7983

8084
As discussed in the previous section, we want to extend the error handler to actually stop the currently running process whenever a module is not found and resume the process only after we ensure the module is compiled. To do that, we can simply define our own error handler and ask Erlang to use it. Our custom error handler is defined as follows:
8185

82-
defmodule Elixir.ErrorHandler do
83-
def undefined_function(module, fun, args) do
84-
ensure_loaded(module)
85-
:error_handler.undefined_function(module, fun, args)
86-
end
87-
88-
def undefined_lambda(module, fun, args) do
89-
ensure_loaded(module)
90-
:error_handler.undefined_lambda(module, fun, args)
91-
end
92-
93-
defp ensure_loaded(module) do
94-
case Code.ensure_loaded(module) do
95-
{ :module, _ } ->
96-
[]
97-
{ :error, _ } ->
98-
parent = Process.get(:elixir_parent_compiler)
99-
send parent, { :waiting, Process.self, module }
100-
receive do
101-
{ :release, ^parent } -> ensure_loaded(module)
102-
end
86+
```elixir
87+
defmodule Elixir.ErrorHandler do
88+
def undefined_function(module, fun, args) do
89+
ensure_loaded(module)
90+
:error_handler.undefined_function(module, fun, args)
91+
end
92+
93+
def undefined_lambda(module, fun, args) do
94+
ensure_loaded(module)
95+
:error_handler.undefined_lambda(module, fun, args)
96+
end
97+
98+
defp ensure_loaded(module) do
99+
case Code.ensure_loaded(module) do
100+
{ :module, _ } ->
101+
[]
102+
{ :error, _ } ->
103+
parent = Process.get(:elixir_parent_compiler)
104+
send parent, { :waiting, Process.self, module }
105+
receive do
106+
{ :release, ^parent } -> ensure_loaded(module)
103107
end
104-
end
105108
end
109+
end
110+
end
111+
```
106112

107113
Our error handler defines two public functions. Both those functions are callbacks required to be implemented by the error handler. They simply call `ensure_loaded(module)` and then delegate the remaining logic to Erlang's original `error_handler`.
108114

109115
The private `ensure_loaded` function calls `Code.ensure_loaded(module)` which checks if the given module is loaded and, if not, tries to load it. In case it succeeds, it returns `{ :module, _ }`, which means the module is available and we don't need to stop the current process. However, if it returns `{ :error, _ }`, it means the module cannot be found and we need to wait until it is compiled. For that, we invoke `Process.get(:elixir_parent_compiler)` to get the PID of the main process so we can notify it that we are waiting on a given module. Then we invoke the macro `receive` as a way to stop the current process until we receive a message from the parent saying new modules are available, starting the flow again.
110116

111117
With our error handler code in place, the first thing we need to do is to change the function given to `spawn_link` to use the new error handler:
112118

113-
spawn_link(fn ->
114-
Process.put(:elixir_parent_compiler, parent)
115-
Process.flag(:error_handler, Elixir.ErrorHandler)
119+
```elixir
120+
spawn_link(fn ->
121+
Process.put(:elixir_parent_compiler, parent)
122+
Process.flag(:error_handler, Elixir.ErrorHandler)
116123

117-
:elixir_compiler.file_to_path(current, output)
118-
send parent, { :compiled, Process.self() }
119-
end)
124+
:elixir_compiler.file_to_path(current, output)
125+
send parent, { :compiled, Process.self() }
126+
end)
127+
```
120128

121129
Notice that we have two small additions. First we store the `:elixir_parent_compiler` PID in the process dictionary so we are able to read it from the error handler and then we proceed to configure a flag in our process so our new error handler is invoked whenever a module or function cannot be found.
122130

123131
Second, our main process can now receive a new `{ :waiting, child, module }` message, so we need to extend it to account for those messages. Not only that, we need to control which PIDs we have spawned so we can notify them whenever a new module is compiled, forcing us to add a new argument to the `spawn_compilers` function. `spawn_compilers` would then be rewritten as follows:
124132

125-
def spawn_compilers([current | files], output, stack) do
126-
parent = Process.self()
127-
child = spawn_link(fn ->
128-
:elixir_compiler.file_to_path(current, output)
129-
send parent, { :compiled, Process.self() }
130-
end)
131-
wait_for_messages(files, output, [child | stack])
132-
end
133-
134-
# No more files and stack is empty, we are done
135-
def spawn_compilers([], _output, []) do
136-
:done
137-
end
138-
139-
# No more files and stack is not empty, wait for all messages
140-
def spawn_compilers([], output, stack) do
141-
wait_for_messages([], output, stack)
142-
end
133+
```elixir
134+
def spawn_compilers([current | files], output, stack) do
135+
parent = Process.self()
136+
child = spawn_link(fn ->
137+
:elixir_compiler.file_to_path(current, output)
138+
send parent, { :compiled, Process.self() }
139+
end)
140+
wait_for_messages(files, output, [child | stack])
141+
end
142+
143+
# No more files and stack is empty, we are done
144+
def spawn_compilers([], _output, []) do
145+
:done
146+
end
147+
148+
# No more files and stack is not empty, wait for all messages
149+
def spawn_compilers([], output, stack) do
150+
wait_for_messages([], output, stack)
151+
end
152+
```
143153

144154
Notice we added an extra clause to `spawn_compilers` so we can properly handle the case where we don't have more files to spawn but we are still waiting for processes in the stack. We have also moved our `receive` logic to a new private function called `wait_for_messages`, implemented as follows:
145155

146-
defp wait_for_messages(files, output, stack) do
147-
receive do
148-
{ :compiled, child } ->
149-
new_stack = List.delete(stack, child)
150-
Enum.each new_stack, fn(pid) ->
151-
send pid, { :release, Process.self }
152-
end
153-
spawn_compilers(files, output, new_stack)
154-
{ :waiting, _child, _module } ->
155-
spawn_compilers(files, output, stack)
156-
{ :EXIT, _child, { reason, where } } ->
157-
:erlang.raise(:error, reason, where)
158-
after
159-
10_000 ->
160-
raise "dependency on nonexistent module or possible deadlock"
156+
```elixir
157+
defp wait_for_messages(files, output, stack) do
158+
receive do
159+
{ :compiled, child } ->
160+
new_stack = List.delete(stack, child)
161+
Enum.each new_stack, fn(pid) ->
162+
send pid, { :release, Process.self }
161163
end
162-
end
164+
spawn_compilers(files, output, new_stack)
165+
{ :waiting, _child, _module } ->
166+
spawn_compilers(files, output, stack)
167+
{ :EXIT, _child, { reason, where } } ->
168+
:erlang.raise(:error, reason, where)
169+
after
170+
10_000 ->
171+
raise "dependency on nonexistent module or possible deadlock"
172+
end
173+
end
174+
```
163175

164176
The implementation for `wait_for_messages` is now broken into 4 clauses:
165177

src/content/blog/elixir-v0-8-0-released.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,15 @@ On the last 9th January, we celebrated [two years since Elixir's first commit](h
1313

1414
One of the goals for the v0.8 release was better integration with OTP applications. By passing the `--sup` option to Mix, you can start a new OTP Application containing application callbacks and a supervisor:
1515

16-
mix new my_app --sup
16+
```bash
17+
mix new my_app --sup
18+
```
1719

1820
And applications can be started directly from the command line as well:
1921

20-
elixir --app my_app
22+
```bash
23+
elixir --app my_app
24+
```
2125

2226
We have written a whole [guide chapter about creating OTP applications, supervisors and servers](https://hexdocs.pm/elixir/supervisor-and-application.html). Give it a try!
2327

src/content/blog/elixir-v0-9-0-released.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ $ mix new my_project --umbrella
4343

4444
The generated project will have the following structure:
4545

46-
apps/
47-
mix.exs
48-
README.md
46+
```
47+
apps/
48+
mix.exs
49+
README.md
50+
```
4951

5052
Now, inside the `apps` directory, you can create as many applications as you want and running `mix compile` inside the umbrella project will automatically compile all applications. The [original discussion for this feature](https://github.com/elixir-lang/elixir/issues/667) contains more details about how it all works.
5153

src/content/blog/elixir-v1-10-0-released.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ end
9797

9898
This approach has one big limitation: if you change the value of the application environment after the code is compiled, the value used at runtime is not going to change! For example, if you are using `mix release` and your `config/releases.exs` has:
9999

100-
config :my_app, :db_host, "db.production"
100+
```elixir
101+
config :my_app, :db_host, "db.production"
102+
```
101103

102104
Because `config/releases.exs` is read after the code is compiled, the new value will have no effect as the code was compiled to connect to "db.local".
103105

@@ -143,8 +145,10 @@ In Elixir v1.10, we have addressed these problems by [introducing compiler traci
143145

144146
Elixir itself is using the new compiler tracing to provide new functionality. One advantage of this approach is that developers can now disable undefined function warnings directly on the callsite. For example, imagine you have an optional dependency which may not be available in some cases. You can tell the compiler to skip warning on calls to optional modules with:
145147

146-
@compile {:no_warn_undefined, OptionalDependency}
147-
defdelegate my_function_call(arg), to: OptionalDependency
148+
```elixir
149+
@compile {:no_warn_undefined, OptionalDependency}
150+
defdelegate my_function_call(arg), to: OptionalDependency
151+
```
148152

149153
Previously, this information had to be added to the overall project configuration, which was far away from where the optional call effectively happened.
150154

src/content/blog/elixir-v1-13-0-released.md

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,23 @@ Those improvements came from direct feedback from the community. A special shout
8282

8383
Thanks to its sigils, Elixir provides the ability of embedding snippets in other languages inside its source code. One could use it to embed XML:
8484

85-
~X"""
86-
<?xml version="1.0" encoding="UTF-8"?>
87-
<text><![CDATA[Hello World]]></text>
88-
"""
85+
```elixir
86+
~X"""
87+
<?xml version="1.0" encoding="UTF-8"?>
88+
<text><![CDATA[Hello World]]></text>
89+
"""
90+
```
8991

9092
Or even [Zig](https://ziglang.org/), [via the Zigler project](https://github.com/ityonemo/zigler):
9193

92-
~Z"""
93-
/// nif: example_fun/2
94-
fn example_fun(value1: f64, value2: f64) bool {
95-
return value1 > value2;
96-
}
97-
"""
94+
```elixir
95+
~Z"""
96+
/// nif: example_fun/2
97+
fn example_fun(value1: f64, value2: f64) bool {
98+
return value1 > value2;
99+
}
100+
"""
101+
```
98102

99103
However, while you can format Elixir source code with [`mix format`](https://hexdocs.pm/mix/Mix.Tasks.Format.html), you could not format the code inside snippets.
100104

@@ -134,11 +138,13 @@ We are looking forward to see how this new functionality will be used by communi
134138

135139
`SyntaxError` and `TokenMissingError` were improved to show a code snippet whenever possible:
136140

137-
$ elixir -e "hello + * world"
138-
** (SyntaxError) nofile:1:9: syntax error before: '*'
139-
|
140-
1 | hello + * world
141-
| ^
141+
```bash
142+
$ elixir -e "hello + * world"
143+
** (SyntaxError) nofile:1:9: syntax error before: '*'
144+
|
145+
1 | hello + * world
146+
| ^
147+
```
142148

143149
The `Code` module has also been augmented with two functions: [`Code.string_to_quoted_with_comments/2`](https://hexdocs.pm/elixir/Code.html#string_to_quoted_with_comments/2) and [`Code.quoted_to_algebra/2`](https://hexdocs.pm/elixir/Code.html#quoted_to_algebra/2). Those functions allow someone to retrieve the Elixir AST with their original source code comments, and then convert this AST to formatted code. In other words, those functions provide a wrapper around the Elixir Code Formatter, supporting developers who wish to create tools that directly manipulate and custom format Elixir source code.
144150

src/content/blog/elixir-v1-15-0-released.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,19 @@ sure more feedback is reported to developers before compilation is aborted.
8989

9090
In Elixir v1.14, an undefined function would be reported as:
9191

92-
** (CompileError) undefined function foo/0 (there is no such import)
93-
my_file.exs:1
92+
```
93+
** (CompileError) undefined function foo/0 (there is no such import)
94+
my_file.exs:1
95+
```
9496

9597
In Elixir v1.15, the new reports will look like:
9698

97-
error: undefined function foo/0 (there is no such import)
98-
my_file.exs:1
99+
```
100+
error: undefined function foo/0 (there is no such import)
101+
my_file.exs:1
99102
100-
** (CompileError) my_file.exs: cannot compile file (errors have been logged)
103+
** (CompileError) my_file.exs: cannot compile file (errors have been logged)
104+
```
101105

102106
A new function, called `Code.with_diagnostics/2`, has been added so this
103107
information can be leveraged by editors, allowing them to point to several

src/content/blog/elixir-v1-3-0-released.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ Elixir v1.3 includes improvements to the option parser, including `OptionParser.
167167

168168
For example, invoking `mix test --unknown` in earlier Elixir versions would silently discard the `--unknown` option. Now `mix test` correctly reports such errors:
169169

170-
```
170+
```bash
171171
$ mix test --unknown
172172
** (Mix) Could not invoke task "test": 1 error found!
173173
--unknown : Unknown option
@@ -251,7 +251,7 @@ end
251251

252252
Every test inside a describe block will be tagged with the describe block name. This allows developers to run tests that belong to particular blocks, be them in the same file or across many files:
253253

254-
```
254+
```bash
255255
$ mix test --only describe:"String.capitalize/2"
256256
```
257257

0 commit comments

Comments
 (0)