Six Months of C

The past year has brought a lot of changes, not the least of which is that I'm now working primarily in C after a few years of web development in NodeJS/React/general JavaScript land. There's some irony to this given that in my first PyCon talk back in 2022, I proudly declared that I wanted to write as little C/C++ as possible when reimplementing a constraint library in Python using Cython.

However, I've never been particularly happy with high levels of abstraction and black-box implementations going back to my days of frustration with structural engineering software, so maybe it's fitting now that I'm working at a lower level and pursuing performant programming in C. Of course, the switch has meant working with a completely different toolset than I've used in the past, and learning a lot about Emacs along the way.

Base Emacs Configuration

I use Emacs 30.2 installed via the Emacs Plus Homebrew tap with native compilation, along with Doom Emacs to configure most of the features that I want to use.

Some of the packages not included in Doom Emacs that I find particularly useful for general development are:

Tree-sitter

My main mode when working in C is c-ts-mode, which is the C editing mode powered by tree-sitter

I believe installing tree-sitter per language now is much easier than it used to be, as I only needed to install the C grammar as outlined in this Mastering Emacs article per the "Compiling and Installing with the builtin method in Emacs" section. While the notes say that it doesn't work well unless you're using GCC and running Linux, I didn't have any problems with Clang and MacOS.

I still don't think I'm using Tree-sitter to its full capabilities in Emacs, but it has been fun to explore the code base using treesit-explore-mode and treesit-inspect-mode. I'd like to do more with its code navigation and highlighting, as I'm still relying primarily on projectile for code navigation. While researching some of the links for this blog post, I discovered the Combobulate package (disclaimer, from the same author as Mastering Emacs), which looks like it could be helpful.

Custom formatting rules

Unfortunately, I can't use .clang-format, as there is an unorthodox set of formatting requirements that don't fit into any of the common C standards. As a result, I needed to add the following .clang-format to my base directory to avoid significant frustration.

DisableFormat: true

Org Mode Improvements

One of the biggest boons for navigating a large, unfamiliar codebase (in a new language!) has been utilizing org mode to its fullest. It's how I write out work plans, write code snippets for investigation or debugging, and add in-line images to track current application state.

Early on, I added a function to copy an org mode link to the current file and line number (appropriately named copy-org-link-to-line). This has made it easy to navigate to functions I need to modify in my work plan and include additional context as my understanding of the codebase improves.

(defun copy-org-link-to-line ()
  "Copy an Org mode link to the current file and line number.
  Format: [[file+emacs:/absolute/path/to/file.txt::$line_number][file.txt:$line_number]]"
  (interactive)
  (if-let ((file (buffer-file-name))
           (line (line-number-at-pos)))
        (let ((org-link (format "[[file+emacs:%s::%d][%s:%d]]" file line (file-name-nondirectory file) line)))
            (kill-new org-link)
            (message "Copied %s" org-link))
    (message "Buffer is not visiting a file")))

I've also struggled with tab/space presentation conflicting across different minor modes (probably an artifact from Doom, which has electric-indent-local-mode and ws-butler-mode), so I have a custom display setting for tabs in c-ts-mode:

(standard-display-ascii ?\t "••••")

Debugging with dape

I initially loaded and unloaded debugging files drafted in org-mode and loaded just into the command line lldb. This was largely because I couldn't get debugging with dap-mode to work at all with C. Surprise! This was because I use eglot instead of lsp-mode, as eglot seems to have won out for using language servers within Emacs, and dap-mode seems to be tied to lsp-mode.

In the interest of KISS, I opted to just use dape since it uses eglot out of the box.

I have two 'dape-configs set up in my config.el. One for launching/debugging the GUI, and another for launching/debugging a single test suite, which look something like this:

(after! dap-mode
    (after! dape
        :ensure t 
        :config 
        (add-to-list 'dape-configs
            `(debug-gui
              modes (c-ts-mode c-mode)
              ensure dape-ensure-command 
              command "lldb-dap"
              command-cwd "/Users/mclare/workspaces/prog/bin"
              :type "lldb-dap"
              :request "launch"
              :program "prog_dev"
              :initCommands ["command source -s 0 .lldbinit"]
              :args ["absolute/path/to/test/file"]
            )
        )
        (add-to-list 'dape-configs
            `(debug-test
              modes (c-ts-mode c-mode)
              ensure dape-ensure-command 
              command "lldb-dap"
              command-cwd "/Users/mclare/workspaces/prog/bin"
              :type "lldb-dap"
              :request "launch"
              :program "prog_test"
              :initCommands ["command source -s 0 .lldbinit"]
              :args ["--test_suite" "$test_suite_name"]
            )
        )
    )
)

The Good

dape-many-windows

Dape Many Windows (from readme)

The Bad

The ??

Other Resources

Especially in the age of LLMs, I'm becoming more and more leary of trusting online tutorials or resources. Since I'm working in C99, it seemed like a good investment to get some C standard texts, since the language is small enough to fit into pretty slim volumes.

Before starting my new job I read:

Since starting, I've also acquired:

and just for fun (related but not specifically C)...