Last active
August 29, 2015 14:07
-
-
Save nsmaciej/8ad2dcc0039a90d3e280 to your computer and use it in GitHub Desktop.
usaco-mode.el
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;; 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