Compare commits

...

2 Commits

Author SHA1 Message Date
2b2745d669
google adsense 2024-07-26 11:43:33 -04:00
a729386a92
Added table of contents 2024-07-25 21:20:40 -04:00
9 changed files with 146 additions and 93 deletions

View File

@ -33,5 +33,5 @@
"prettier": "^3.2.5", "prettier": "^3.2.5",
"prettier-plugin-astro": "^0.13.0" "prettier-plugin-astro": "^0.13.0"
}, },
"packageManager": "pnpm@9.4.0+sha512.f549b8a52c9d2b8536762f99c0722205efc5af913e77835dbccc3b0b0b2ca9e7dc8022b78062c17291c48e88749c70ce88eb5a74f1fa8c4bf5e18bb46c8bd83a" "packageManager": "pnpm@9.6.0+sha512.38dc6fba8dba35b39340b9700112c2fe1e12f10b17134715a4aa98ccf7bb035e76fd981cf0bb384dfa98f8d6af5481c2bef2f4266a24bfa20c34eb7147ce0b5e"
} }

View File

@ -1,23 +1,25 @@
--- ---
type Props = { measurementId: string } type Props = { measurementId: string };
const props = Astro.props as Props const props = Astro.props as Props;
const measurementId = props.measurementId const measurementId = props.measurementId;
--- ---
<script <script
type='text/partytown' type="text/partytown"
src={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`}></script> src={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`}
></script>
<script <script
type='text/partytown' type="text/partytown"
data-ga-measurement-id={measurementId} data-ga-measurement-id={measurementId}
id='ga-init' id="ga-init"
> >
const measurementId = document const measurementId = document
.getElementById('ga-init') .getElementById("ga-init")
.getAttribute('data-ga-measurement-id') .getAttribute("data-ga-measurement-id");
window.dataLayer = window.dataLayer || [] window.dataLayer = window.dataLayer || [];
function gtag() { function gtag() {
dataLayer.push(arguments) // eslint-disable-line dataLayer.push(arguments); // eslint-disable-line
} }
gtag('js', new Date()) gtag("js", new Date());
gtag('config', measurementId) gtag("config", measurementId);
</script> </script>

View File

@ -3,6 +3,18 @@ title: Building the Same App 5 Times
publishedTime: 2021-08-18 publishedTime: 2021-08-18
--- ---
<!--toc:start-->
- [Originally Published on Dev.To](#originally-published-on-devto)
- [Building the App(s)](#building-the-apps)
- [jQuery](#jquery)
- [Angular](#angular)
- [React](#react)
- [Vue](#vue)
- [Svelte](#svelte)
- [Conclusion](#conclusion)
<!--toc:end-->
### Originally Published on Dev.To ### Originally Published on Dev.To
This is something that I was inspired to do because of the YouTube channel Fireship, which makes great videos about web development that I highly recommend if you're interested. This is something that I was inspired to do because of the YouTube channel Fireship, which makes great videos about web development that I highly recommend if you're interested.

View File

@ -3,6 +3,15 @@ title: I Followed a 40 year old Pascal Book (in Rust)
publishedTime: 2024-01-05 publishedTime: 2024-01-05
--- ---
<!--toc:start-->
- [What is this book?](#what-is-this-book)
- [What will I be doing?](#what-will-i-be-doing)
- [Part 1 (Chapter 2): Murder at the Metropolitan Club](#part-1-chapter-2-murder-at-the-metropolitan-club)
- [Part 2 (Chapter 3): Holmes Gives a Demonstration](#part-2-chapter-3-holmes-gives-a-demonstration)
- [Rust](#rust)
<!--toc:end-->
My father is a big fan of mystery novels, certainly more than I am. I enjoy a Sherlock Holmes story now and again, and I have had fun reading Agatha Christie novels, but he is and always will be a bigger fan of the genre than I. That being said, there is one mystery book I find much more interesting to me than he ever has, and that is "Elementary Pascal" by Henry Ledgard and Andrew Singer. My father will happily tell you the furthest he got in computer programming was making a 3D Cube in Basic and reading the programs for his Commodore 64 in a magazine, but almost never running them as he would have had to copy them each by hand. I, on the other hand, have much more of an interest in programming, as might be evident from the fact I am writing this blog post where I do so. When I was first learning to program as a kid, my father gave me this book that he'd had since he was a kid. He claimed to have hardly read it, but that I might be interested in it, and compared to him I certainly was. That said, I only ever read a few of the examples, as I had very little actual interest in learning Pascal. In more recent years, the book has sat on my shelf, undisturbed, waiting to once again be opened. Today was that day. My father is a big fan of mystery novels, certainly more than I am. I enjoy a Sherlock Holmes story now and again, and I have had fun reading Agatha Christie novels, but he is and always will be a bigger fan of the genre than I. That being said, there is one mystery book I find much more interesting to me than he ever has, and that is "Elementary Pascal" by Henry Ledgard and Andrew Singer. My father will happily tell you the furthest he got in computer programming was making a 3D Cube in Basic and reading the programs for his Commodore 64 in a magazine, but almost never running them as he would have had to copy them each by hand. I, on the other hand, have much more of an interest in programming, as might be evident from the fact I am writing this blog post where I do so. When I was first learning to program as a kid, my father gave me this book that he'd had since he was a kid. He claimed to have hardly read it, but that I might be interested in it, and compared to him I certainly was. That said, I only ever read a few of the examples, as I had very little actual interest in learning Pascal. In more recent years, the book has sat on my shelf, undisturbed, waiting to once again be opened. Today was that day.
### What is this book? ### What is this book?

View File

@ -3,6 +3,18 @@ title: Writing My Own Language Server in Go (to Parse Chess PGNs)
publishedTime: 2024-06-26 publishedTime: 2024-06-26
--- ---
<!--toc:start-->
- [What is a Language Server?](#what-is-a-language-server)
- [What is a Chess PGN?](#what-is-a-chess-pgn)
- [What can this language server do?](#what-can-this-language-server-do)
- [Making the Server](#making-the-server)
- [Notes and Issues](#notes-and-issues)
- [The Protocol](#the-protocol)
- [Analysis](#analysis)
- [Conclusion](#conclusion)
<!--toc:end-->
### What is a Language Server? ### What is a Language Server?
If you're unfamiliar, the Language Server Protocol is a protocol by which a client (usually a code editor) can talk to a language server and get information about an open file and/or workspace. This is made use of in editors like VS Code and Neovim (my editor of choice, by the way). The protocol passes data around in JSON which essentially allows for an RPC where the client tells the server to do something or vice versa. These can be built in any language, as long as said language supports whatever transport method you choose for the JSON RPC (usually stdout and stdin, but TCP is another example of an option). For this language server I built it in Go using stdout and stdin. If you're unfamiliar, the Language Server Protocol is a protocol by which a client (usually a code editor) can talk to a language server and get information about an open file and/or workspace. This is made use of in editors like VS Code and Neovim (my editor of choice, by the way). The protocol passes data around in JSON which essentially allows for an RPC where the client tells the server to do something or vice versa. These can be built in any language, as long as said language supports whatever transport method you choose for the JSON RPC (usually stdout and stdin, but TCP is another example of an option). For this language server I built it in Go using stdout and stdin.
@ -17,11 +29,12 @@ In its current basic form, it can parse a PGN of a single chess game, report err
## Making the Server ## Making the Server
### Notes and Issues ### Notes and Issues
Since this was my first time ever creating a language server, I no doubt made some mistakes, and I'm also fairly new to Go, so I have no doubt that will have contributed to any issues as well, but all that being said, this went over pretty painlessly. I had some hiccups with the parser itself, but as far as implementing the protocol it went off without much of a hitch. The only real warning I'd give to people following suit on this is that you should probably design your analysis tool before doing much else because the way I did it, I felt a lot like I was jumping around my codebase continuously adding and removing from the LSP to fit the demands of the analysis tool. That might seem self-evident, especially if you're writing a language server for a programming language, but I figured I should mention it. Since this was my first time ever creating a language server, I no doubt made some mistakes, and I'm also fairly new to Go, so I have no doubt that will have contributed to any issues as well, but all that being said, this went over pretty painlessly. I had some hiccups with the parser itself, but as far as implementing the protocol it went off without much of a hitch. The only real warning I'd give to people following suit on this is that you should probably design your analysis tool before doing much else because the way I did it, I felt a lot like I was jumping around my codebase continuously adding and removing from the LSP to fit the demands of the analysis tool. That might seem self-evident, especially if you're writing a language server for a programming language, but I figured I should mention it.
### The Protocol ### The Protocol
I started with the help of TJ Devries' [educationalsp repo](https://github.com/tjdevries/educationalsp) creating some helper functions for the RPC: I started with the help of TJ Devries' [educationalsp repo](https://github.com/tjdevries/educationalsp) creating some helper functions for the RPC:
```go ```go
@ -92,6 +105,7 @@ func Split(data []byte, _ bool) (advance int, token []byte, err error) {
return totalLength, data[:totalLength], nil return totalLength, data[:totalLength], nil
} }
``` ```
```go ```go
// main.go // main.go
package main package main
@ -145,6 +159,7 @@ func writeResponse(w io.Writer, msg any) {
The `main.go` code has been truncated to only show the initialize event, but I left in the bits of it (i.e. the `state` parameter of the `handleMessage` function that won't become clear just yet). The `main.go` code has been truncated to only show the initialize event, but I left in the bits of it (i.e. the `state` parameter of the `handleMessage` function that won't become clear just yet).
If you don't fully understand the code in `rpc.go`, I recommend watching the beginning portion of [TJ's video about the LSP spec](https://youtu.be/YsdlcQoHqPY?si=Sq9mlljv4PgBLpMI). If you don't fully understand the code in `rpc.go`, I recommend watching the beginning portion of [TJ's video about the LSP spec](https://youtu.be/YsdlcQoHqPY?si=Sq9mlljv4PgBLpMI).
The real meat of this, however, comes in the analysis portion, as that's where everything actually happens. Before we look at that though, let's look at some of the implementations for the actual Language Server Protocol's JSON messages. The real meat of this, however, comes in the analysis portion, as that's where everything actually happens. Before we look at that though, let's look at some of the implementations for the actual Language Server Protocol's JSON messages.
```go ```go
// lsp/message.go // lsp/message.go
package lsp package lsp
@ -165,21 +180,27 @@ type Notification struct {
Method string `json:"method"` Method string `json:"method"`
} }
``` ```
These are the basic structures for the types of messages, an example of which is the `DidOpenTextDocumentNotification`: These are the basic structures for the types of messages, an example of which is the `DidOpenTextDocumentNotification`:
```go ```go
type DidOpenTextDocumentNotification struct { type DidOpenTextDocumentNotification struct {
Notification Notification
Params DidOpenTextDocumentParams `json:"params"` Params DidOpenTextDocumentParams `json:"params"`
} }
``` ```
Since Go doesn't have traditional inheritance, the `Notification` is just passed as a field in this struct. Since Go doesn't have traditional inheritance, the `Notification` is just passed as a field in this struct.
The `Params` are then a type of another struct `DidOpenTextDocumentParams`: The `Params` are then a type of another struct `DidOpenTextDocumentParams`:
```go ```go
type DidOpenTextDocumentParams struct { type DidOpenTextDocumentParams struct {
TextDocument TextDocumentItem `json:"textDocument"` TextDocument TextDocumentItem `json:"textDocument"`
} }
``` ```
The `TextDocumentItem` struct is what describes the actual file: The `TextDocumentItem` struct is what describes the actual file:
```go ```go
type TextDocumentItem struct { type TextDocumentItem struct {
URI string `json:"uri"` URI string `json:"uri"`
@ -188,8 +209,10 @@ type TextDocumentItem struct {
Text string `json:"text"` Text string `json:"text"`
} }
``` ```
There are other similar structs that are used for other events, but this `Notification` is sent when a file is, shocker, opened. There are other similar structs that are used for other events, but this `Notification` is sent when a file is, shocker, opened.
As an LSP is expanded you can add more and more of these. The most important `Request` and `Response`, however, are the ones for the initialize event. These define what both the client and server are capable of. The `IntializeResponse` can be viewed here: As an LSP is expanded you can add more and more of these. The most important `Request` and `Response`, however, are the ones for the initialize event. These define what both the client and server are capable of. The `IntializeResponse` can be viewed here:
```go ```go
type InitializeResponse struct { type InitializeResponse struct {
Response Response
@ -200,26 +223,32 @@ type InitializeResult struct {
ServerInfo ServerInfo `json:"serverInfo"` ServerInfo ServerInfo `json:"serverInfo"`
} }
``` ```
When the response is given, we give the client these `ServerCapabilities`: When the response is given, we give the client these `ServerCapabilities`:
```go ```go
Capabilities: ServerCapabilities{ Capabilities:
ServerCapabilities{
TextDocumentSync: 2, TextDocumentSync: 2,
CompletionProvider: map[string]any{}, CompletionProvider: map[string]any{},
} }
``` ```
### Analysis ### Analysis
When actually doing the analysis I went through three stages: When actually doing the analysis I went through three stages:
1. I will use someone's existing PGN parser 1. I will use someone's existing PGN parser
2. I'm having some trouble making use of these existing parsers, I'm going to design one myself 2. I'm having some trouble making use of these existing parsers, I'm going to design one myself
3. Man, just writing that lexer was a lot, I should try someone else's parser again. 3. Man, just writing that lexer was a lot, I should try someone else's parser again.
The one that stuck was one from 5 years ago [by malbrecht](https://github.com/malbrecht/chess), but there are multiple others that exist, and if I continue working on this, I'll probably go back to building my own as I require more customizability. For now though, this parser works out just fine. The one that stuck was one from 5 years ago [by malbrecht](https://github.com/malbrecht/chess), but there are multiple others that exist, and if I continue working on this, I'll probably go back to building my own as I require more customizability. For now though, this parser works out just fine.
The two features I wanted for this were diagnostics and completions, and this parser (obviously) returns errors if it can't parse the PGN, and is part of a larger package that allows for looking up legal moves from a position. The two features I wanted for this were diagnostics and completions, and this parser (obviously) returns errors if it can't parse the PGN, and is part of a larger package that allows for looking up legal moves from a position.
The actual analysis tool runs by having a `State` struct that stores the text documents (in this case just the one) and a database of PGNs from the parser (in this case just the one). There are then some functions that are called every time an event happens, like opening a document, updating a document, or asking for completions. I won't put any actual code here, but I'll run you through the basic order in which things happen when running the LSP in the editor. The actual analysis tool runs by having a `State` struct that stores the text documents (in this case just the one) and a database of PGNs from the parser (in this case just the one). There are then some functions that are called every time an event happens, like opening a document, updating a document, or asking for completions. I won't put any actual code here, but I'll run you through the basic order in which things happen when running the LSP in the editor.
1. The Initialize Request is sent, the server responds telling info about the server and its capabilities. 4. The Initialize Request is sent, the server responds telling info about the server and its capabilities.
2. The `textDocument/didOpen` notification is sent, and the server loads the text of the document into the state of the analysis tool. This also sends back any diagnostic information the parser returns. 5. The `textDocument/didOpen` notification is sent, and the server loads the text of the document into the state of the analysis tool. This also sends back any diagnostic information the parser returns.
3. If the user makes any changes to the PGN, the `textDocument/didChange` notification is sent, and the server loads the text of the document in the state changes to reflect these changes. This also updates the diagnostic information. 6. If the user makes any changes to the PGN, the `textDocument/didChange` notification is sent, and the server loads the text of the document in the state changes to reflect these changes. This also updates the diagnostic information.
4. Presumably, these changes cause a completion request to be sent, and the server looks through the legal moves at the position at the cursor in the file, and returns them as a list of completion items. 7. Presumably, these changes cause a completion request to be sent, and the server looks through the legal moves at the position at the cursor in the file, and returns them as a list of completion items.
## Conclusion ## Conclusion
Hopefully this was a cool look into what the LSP can do for you, and why it's such a cool technology. I didn't go into a whole lot of detail here, but if you want to take a look at the code for this, it's all on [GitHub](https://github.com/sammyshear/chesslsp). Feel free to make issues or pull requests if you want, as there are definitely problems, I just haven't found them yet. Hopefully this was a cool look into what the LSP can do for you, and why it's such a cool technology. I didn't go into a whole lot of detail here, but if you want to take a look at the code for this, it's all on [GitHub](https://github.com/sammyshear/chesslsp). Feel free to make issues or pull requests if you want, as there are definitely problems, I just haven't found them yet.

View File

@ -25,15 +25,13 @@ const {
<meta name="author" content={author} /> <meta name="author" content={author} />
<meta name="description" content={description} /> <meta name="description" content={description} />
<link rel="sitemap" href="/sitemap-index.xml" /> <link rel="sitemap" href="/sitemap-index.xml" />
<meta name="google-adsense-account" content="ca-pub-2655673927481085" />
<title>{title}</title> <title>{title}</title>
</head> </head>
<body <body
class="flex flex-col place-items-center bg-ctp-base ctp-latte text-ctp-text dark:ctp-mocha" class="flex flex-col place-items-center bg-ctp-base ctp-latte text-ctp-text dark:ctp-mocha"
> >
<slot /> <slot />
</body>
</html>
<style is:global> <style is:global>
html.dark .astro-code, html.dark .astro-code,
html.dark .astro-code span { html.dark .astro-code span {
@ -67,3 +65,5 @@ const {
} }
window.localStorage.setItem("theme", theme); window.localStorage.setItem("theme", theme);
</script> </script>
</body>
</html>

View File

@ -17,7 +17,8 @@ import Layout from "../layouts/Layout.astro";
frameworks and technologies in general. On the backend at this point frameworks and technologies in general. On the backend at this point
I most often use Go's default HTTP server or Rust with Actix Web or I most often use Go's default HTTP server or Rust with Actix Web or
Axum, but I also have experience with Node and Express, as well as Axum, but I also have experience with Node and Express, as well as
RPC using tRPC (a TypeScript RPC implementation). RPC using tRPC (a TypeScript RPC implementation) or twirp (a gRPC
alternative for Go).
<br /> <br />
This site mostly serves as a blog for me to write about things I do or This site mostly serves as a blog for me to write about things I do or
am interested in, but I will probably add more uses for the site as I am interested in, but I will probably add more uses for the site as I