Skip to content

Instantly share code, notes, and snippets.

@nsmaciej
Last active August 29, 2015 14:07
Show Gist options
  • Save nsmaciej/8ad2dcc0039a90d3e280 to your computer and use it in GitHub Desktop.
Save nsmaciej/8ad2dcc0039a90d3e280 to your computer and use it in GitHub Desktop.
usaco-mode.el
;;; usaco-mode.el - Mode for quickly testing USACO solutions
;; Copyright (C) 2014 Maciej Goszczycki
;; Author: Maciej Goszczycki <[email protected]>
;; Created: 9 October 2014
;; Version: 0.0.0
;; Keywords: compile run convenience
;;; Commentary:
;; The code is a minor mess mostly due to `compile' messing with the current buffer
;; Once in a lifetime. You may find yourself writing a emacs mode!
;;; Code:
(defgroup usaco-mode nil
"Mode for quickly testing USACO solutons"
:group 'convenience
:group 'compilation)
(defcustom usaco-user-id nil
"User id to use in the USACO headers, when using `usaco-insert-header'"
:group 'usaco-mode
:type 'string)
;; Used to be local.. see usaco-run description for details
(defvar usaco-input-buffer nil
"Local to current usaco-mode file buffer to use as input during `usaco-run'")
;; Once again used because compile functions hook changes the current buffer *sigh*
(defcustom usaco-code-buffer nil
"Name of the buffer which contains the code being run by `usaco-run'"
:group 'usaco-mode
:type 'string)
(defvar-local usaco-compiler "gcc"
"The compiler `usaco-make' should use by default")
(defvar-local usaco-c-flags "-Wall -Wextra -pedantic -ld"
"Extra flags `usaco-c-flags-function' should use. I consider those sensible defaults")
(defvar-local usaco-cpp-flags "-Wall -Wextra -Wshadow"
"Extra flags `usaco-cpp-flags-function' should use. I consider those sensible defaults")
(defvar usaco-c-flags-function (lambda ()
(format "%s -std=c99 -O%d"
usaco-c-flags
usaco-optimize-level))
"Function that generates flags `usaco-compiler' should use by default when compiling C files.")
(defvar usaco-cpp-flags-function (lambda ()
(format "-Wall -Wextra -std=%s -pedantic -ansi -Wshadow -O%d"
(if usaco-use-cpp11 "C++11" "C++98")
usaco-optimize-level))
"Function that generates flags `usaco-make' should use by default when compiling C++ files.
By default takes `usaco-use-cpp11' into account and switches the -std flag accordingly")
(defcustom usaco-use-cpp11 t
"Should `usaco-insert-header' specify language to \"C++11f\" when editing C++ files
and should `usaco-make' use \"C++98\" or \"C++11\" standard"
:group 'usaco-mode
:type 'boolean)
(defvar-local usaco-optimize-level 0
"Tells `usaco-make' how much to optimize \(-Ox flag).
0 by default because it makes program nicer to debug")
(defmacro if-cpp (cpp &optional justc)
"Macro for quickly executing different code depending on
buffer being in c++ or c mode
Example: \(if-cpp \"C++\" \"C\" \"Not C or C++\")"
`(if (string= (file-name-extension (or (buffer-file-name) (buffer-name))) "cpp")
,cpp ,justc))
;; Nice for quickly starting USACO tasks
(defun usaco-insert-header (task-id)
"Insert the required USACO heading"
(interactive "sTask ID: ")
(unless usaco-user-id
(setq usaco-user-id (read-string "Your USACO user ID: ")))
(let ((language (if-cpp (if usaco-use-cpp11 "C++11f" "C++") "C")))
(goto-char 1)
(insert (format "/*\nID: %s\nLANG: %s\nTASK: %s\n*/\n" usaco-user-id language task-id))
(goto-char (point-max))))
(defun usaco-make ()
"Compile the current C++ or C buffer using `compile'.
Useful for when compiling a single file in a folder of files (like with USACO)
The program is compiled using the `compile' with `usaco-compiler' as compiler and
`usaco-c-flags' or `usaco-cpp-flags' - both being buffer local variables.
Flags passed to `compile' are generated by `usaco-cpp-flags-function' or
`usaco-c-flags-function' they takes into account `usaco-cpp-flags' and `usaco-c-flags'
as well as `usaco-optimize-level' and `usaco-use-cpp11' so it's best to not change them
The binaries' name is the result of `file-name-sans-extension'. And optimization level
is chosen using `usaco-optimize-level'"
(interactive)
(usaco-mode)
(compile (format "%s %s %s -o %s"
usaco-compiler
(if-cpp
(funcall usaco-cpp-flags-function)
(funcall usaco-c-flags-function))
(buffer-file-name)
(file-name-sans-extension (buffer-file-name)))))
;; This gets tricky. Since usaco-actaully-run is called somewhere else I can't pass
;; variables using the dynamic scope.. the call also changes the current buffer so buffer
;; local variables fail (out variable _is_ a buffer - Catch 22). It seems the easiest solution
;; is just to assume I won't be running multiple programs at once and use a global variable
(defun usaco-run (input-buffer)
"Run the C/C++ source in the current buffer using a buffer as input.
Buffer is compiled using `usaco-make' and then run
INPUT-BUFFER is a buffer that contains the text to be send to the running process
When called interactively usaco-run allows user to specify the buffer"
(interactive "bBuffer to use as input: ")
(usaco-mode)
(setq usaco-input-buffer input-buffer)
(setq usaco-code-buffer (buffer-name))
(unless (buffer-file-name) (save-buffer)) ; Make sure it's saved
(add-hook 'compilation-finish-functions 'usaco-actaully-run)
(usaco-make))
(defun usaco-actaully-run (buffer cstatus)
"Run this once we tried compiling. Ran as `compilation-finish-functions' hook"
(when (string= cstatus "finished\n") ; Compiled nicely
(message "Running process...")
(remove-hook 'compilation-finish-functions 'usaco-actaully-run)
(let* ((proc-name (file-name-sans-extension usaco-code-buffer))
(buffer-name "*usaco-output*")
(input-text (with-current-buffer usaco-input-buffer
(buffer-substring-no-properties (point-min) (point-max))))
(proc (start-process-shell-command proc-name buffer-name (concat "./" proc-name))))
;; This was tricky to debug.. it turns out in emacs (current-buffer) doesn't need to be in
;; (selected-window).. aka when this hook was fired it was in *compilation* buffer but the
;; selected window was the source. Using (display-buffer) instead of (switch-buffer) fixes
;; the issue and makes usaco-mode better in general: now my cursor stays in source files at
;; all times, just as with (compile)!
(display-buffer buffer-name)
(with-current-buffer buffer-name
(erase-buffer)
(special-mode))
(message "Sending input text from %s to process.." usaco-input-buffer)
(process-send-string proc-name (if (string= (substring input-text -1) "\n")
input-text
(concat input-text "\n"))) ; Add newline - just in case
(process-send-eof proc-name))))
(define-minor-mode usaco-mode
"Convenient key bindings for working with USACO submissions
Allows specifying arbitrary buffers as input, quickly running
\(and compiling using `compile' and seeing output of programs)"
:lighter " Usaco"
:keymap `((,(kbd "C-c C-m") . usaco-make)
(,(kbd "C-c C-i") . usaco-insert-header)
(,(kbd "C-c C-r") . usaco-run)))
;;; usaco-mode.el ends here
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment