mirror of
https://github.com/samsonjs/samhuri.net.git
synced 2026-03-25 09:05:47 +00:00
fix code blocks on narrow screens, change post extensions
- Code blocks can't be in tables or they extend past the body width. Workaround by using floated line numbers instead. - Fix typocode CSS - Change post extension from .html.md to .md. This makes URLs without extensions work with harp server again. This change works now that article titles never contain periods.
This commit is contained in:
parent
43233d8497
commit
b34ddbb600
116 changed files with 908 additions and 1395 deletions
|
|
@ -62,7 +62,7 @@ class Blag
|
|||
|
||||
def find_post dir, slug
|
||||
# technically should look for slug.md, slug.html.md, etc.
|
||||
File.join dir, slug + '.html.md'
|
||||
File.join dir, slug + '.md'
|
||||
end
|
||||
|
||||
def posts
|
||||
|
|
|
|||
|
|
@ -291,7 +291,13 @@ ul.audio audio {
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table.code pre {
|
||||
width: 100%;
|
||||
overflow: scroll;
|
||||
pre {
|
||||
background-color: darken(@background, 5%);
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
pre.line-numbers {
|
||||
float: left;
|
||||
margin-top: 0;
|
||||
color: #888;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,13 +42,11 @@
|
|||
background-color:#eee;
|
||||
padding:2px;
|
||||
margin:5px;
|
||||
margin-left:1em;
|
||||
margin-bottom:1em;
|
||||
}
|
||||
|
||||
.typocode pre {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
padding: 0.2rem 0.5rem;
|
||||
margin: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-family: monospace;
|
||||
|
|
@ -58,20 +56,7 @@
|
|||
|
||||
.typocode .lineno {
|
||||
text-align: right;
|
||||
/* color: #B00;*/
|
||||
color: #B00;
|
||||
font-family: monospace;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "Andale Mono", "Lucida Typewriter", "Bitstream Vera Sans Mono", fixed-width, monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #555;
|
||||
border: 1px dashed #5189ea;
|
||||
margin-bottom: 30px;
|
||||
margin-top: -10px;
|
||||
padding: 5px;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,438 +0,0 @@
|
|||
/* Island screen styles */
|
||||
|
||||
body {
|
||||
font: 1em/1.15em "Hoefler Text", Georgia, "Adobe Garamond Pro", Palatino, "Times New Roman", serif;
|
||||
color: #eee;
|
||||
background: #000;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
#leftcolumn {
|
||||
width: 23%;
|
||||
float: right;
|
||||
margin: 0 10px 0 20px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#header {
|
||||
/* text-align: center;*/
|
||||
}
|
||||
|
||||
#content {
|
||||
/* padding-top: 50px;*/
|
||||
width: 72%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#main {
|
||||
}
|
||||
|
||||
#search {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
}
|
||||
|
||||
#footer {
|
||||
text-align: center;
|
||||
/* background: #333;*/
|
||||
border-top: 1px solid #666;
|
||||
padding-bottom: 5px;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
#footer a { border: none; }
|
||||
#footer img { border: none; }
|
||||
|
||||
/*+-------------------------------------------+
|
||||
| LINKS |
|
||||
+-------------------------------------------+*/
|
||||
|
||||
a {
|
||||
color: #5189ea;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #3179ca;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background: #ffa;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/*+-------------------------------------------+
|
||||
| UTILITY |
|
||||
+-------------------------------------------+*/
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
form, div { padding: 0; margin: 0; }
|
||||
p { margin: 0 0 1em 0; padding: 0; }
|
||||
img {
|
||||
border: 0;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
/*+-------------------------------------------+
|
||||
| HEADER |
|
||||
+-------------------------------------------+*/
|
||||
|
||||
#header h1 {
|
||||
margin-top: 30px;
|
||||
font-size: 5em;
|
||||
}
|
||||
|
||||
#header h1 a {
|
||||
color: #ffa;
|
||||
background: inherit;
|
||||
text-decoration: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#header h1 a:hover {
|
||||
background: #000;
|
||||
}
|
||||
|
||||
#header h2 {
|
||||
color: #555;
|
||||
padding-top: .2em;
|
||||
font-size: 1.2em;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
/*+-------------------------------------------+
|
||||
| SEARCH |
|
||||
+-------------------------------------------+*/
|
||||
|
||||
#sform {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
#search-results {
|
||||
text-align: left;
|
||||
padding-bottom: 5px;
|
||||
border-bottom: 1px dotted #666;
|
||||
}
|
||||
|
||||
/*+-------------------------------------------+
|
||||
| POST |
|
||||
+-------------------------------------------+*/
|
||||
|
||||
.post {
|
||||
padding: 10px 0 10px 0;
|
||||
margin: 5px 0 15px 5px;
|
||||
/* clear: left;*/
|
||||
}
|
||||
|
||||
.post h2 {
|
||||
font-size: 140%;
|
||||
line-height: 1.4em;
|
||||
margin: 5px 0;
|
||||
/* padding-top: 15px;*/
|
||||
}
|
||||
|
||||
.post h3 {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.post h2 a {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.post h2 a:before {
|
||||
content: '# ';
|
||||
}
|
||||
|
||||
.post h2 a:hover {
|
||||
color: #ffa;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.post p {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
p.meta {
|
||||
font-size: 75%;
|
||||
text-align: right;
|
||||
margin-right: 15px;
|
||||
/* border-right: 1px solid #999;*/
|
||||
/* float: left;*/
|
||||
/* width: 75px;*/
|
||||
}
|
||||
|
||||
p.auth {
|
||||
font-size: 85%;
|
||||
font-weight: bold;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/*+-------------------------------------------+
|
||||
| COMMENTS |
|
||||
+-------------------------------------------+*/
|
||||
|
||||
.comment-list li, #preview {
|
||||
background: #666;
|
||||
padding: 5px;
|
||||
margin-bottom: 8px;
|
||||
color: #eee;
|
||||
/*border-right: 20px solid #d3e0ea;*/
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.gravatar {
|
||||
float: right;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
/*+-------------------------------------------+
|
||||
| SIDEBAR |
|
||||
+-------------------------------------------+*/
|
||||
|
||||
#sidebar h3 {
|
||||
font-size: 110%;
|
||||
line-height: 1.5em;
|
||||
color: #ff9d47;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 2px;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
#sidebar h3:before {
|
||||
content: "<< ";
|
||||
font-size: small;
|
||||
}
|
||||
#sidebar h3:after {
|
||||
content: " >>";
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
#sidebar h3 a {
|
||||
color: #ff9d47;
|
||||
border-bottom: 1px dotted #ff9d47;
|
||||
}
|
||||
|
||||
#sidebar h3 a:hover {
|
||||
color: #000;
|
||||
background: #ffa;
|
||||
}
|
||||
|
||||
#sidebar ul {
|
||||
text-align: right;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#sidebar ul li {
|
||||
/* background: url(http://web.archive.org/web/20060321041209/http://sami.samhuri.net/images/theme/small_arrow.png) 45% no-repeat;*/
|
||||
/* padding-left: 12px;*/
|
||||
}
|
||||
|
||||
#sidebar a.feed,
|
||||
#sidebar a.feed:link,
|
||||
#sidebar a.feed:hover {
|
||||
display: inline;
|
||||
border: none;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
/*+-------------------------------------------+
|
||||
| FOOTER |
|
||||
+-------------------------------------------+*/
|
||||
|
||||
#footer ul {
|
||||
margin: 0;
|
||||
padding: 10px 0 0 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#footer ul li {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0 5px 0 0;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
/*+-------------------------------------------+
|
||||
| FORMS |
|
||||
+-------------------------------------------+*/
|
||||
|
||||
input, textarea, select {
|
||||
border: 1px solid #8ab3d1;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.frm-tbl td { vertical-align: top; padding: 5px; }
|
||||
td#frm-btns { text-align: right; }
|
||||
|
||||
#comment_body { height: 220px; }
|
||||
#form-submit-button { background: #d3e0ea; }
|
||||
|
||||
/*+-------------------------------------------+
|
||||
| MISC |
|
||||
+-------------------------------------------+*/
|
||||
|
||||
.powered { border: none; }
|
||||
.powered img { margin-top: 20px; }
|
||||
.pullquote {
|
||||
width: 30%;
|
||||
float:right;
|
||||
font: 150%/1.5em Times, Helvetica, "Times New Roman", serif;
|
||||
color: #666;
|
||||
margin:10px;
|
||||
background: url(http://web.archive.org/web/20060321041209/http://sami.samhuri.net/images/theme/q-close.gif) no-repeat 70% 100% !important;
|
||||
background /**/:url(); /* removing quote graphic in IE5+ */
|
||||
padding: 0 25px 5px 0;
|
||||
}
|
||||
.pullquote:first-letter {
|
||||
background: url(http://web.archive.org/web/20060321041209/http://sami.samhuri.net/images/theme/q-open.gif) no-repeat left top !important;
|
||||
padding:5px 2px 10px 35px!important;
|
||||
padding /**/:0px; /* resetting padding in IE5+ */
|
||||
background /**/: url(); /* removing quote graphic in IE5+ */
|
||||
}
|
||||
|
||||
.light-bg { background: #666; padding: 2px; }
|
||||
.clearfix:after {
|
||||
content: ".";
|
||||
display: block;
|
||||
height: 0;
|
||||
clear: both;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#errors {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.admintools {
|
||||
float: right;
|
||||
background-color: #fff;
|
||||
font-size: smaller;
|
||||
padding: 0 2px;
|
||||
margin: 0 1px;
|
||||
color: #ccc;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.admintools:hover {
|
||||
color: #c00;
|
||||
border: 1px solid #c00;
|
||||
}
|
||||
|
||||
a[name] { border: none; }
|
||||
|
||||
/* Hides from IE-mac \*/
|
||||
* html .clearfix {height: 1%;}
|
||||
/* End hide from IE-mac */
|
||||
|
||||
#categories li em, #archives li em
|
||||
{
|
||||
color: #98B4D1;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
/* Syntax highlighting */
|
||||
.typocode_ruby .normal {}
|
||||
.typocode_ruby .comment { color: #005; font-style: italic; }
|
||||
.typocode_ruby .keyword { color: #A00; font-weight: bold; }
|
||||
.typocode_ruby .method { color: #077; }
|
||||
.typocode_ruby .class { color: #074; }
|
||||
.typocode_ruby .module { color: #050; }
|
||||
.typocode_ruby .punct { color: #447; font-weight: bold; }
|
||||
.typocode_ruby .symbol { color: #099; }
|
||||
.typocode_ruby .string { color: #944; background: #FFE; }
|
||||
.typocode_ruby .char { color: #F07; }
|
||||
.typocode_ruby .ident { color: #004; }
|
||||
.typocode_ruby .constant { color: #07F; }
|
||||
.typocode_ruby .regex { color: #B66; background: #FEF; }
|
||||
.typocode_ruby .number { color: #F99; }
|
||||
.typocode_ruby .attribute { color: #7BB; }
|
||||
.typocode_ruby .global { color: #7FB; }
|
||||
.typocode_ruby .expr { color: #227; }
|
||||
.typocode_ruby .escape { color: #277; }
|
||||
.typocode_xml .normal {}
|
||||
.typocode_xml .namespace { color: #B66; font-weight: bold; }
|
||||
.typocode_xml .tag { color: #F88; }
|
||||
.typocode_xml .comment { color: #005; font-style: italic; }
|
||||
.typocode_xml .punct { color: #447; font-weight: bold; }
|
||||
.typocode_xml .string { color: #944; }
|
||||
.typocode_xml .number { color: #F99; }
|
||||
.typocode_xml .attribute { color: #BB7; }
|
||||
.typocode_yaml .normal {}
|
||||
.typocode_yaml .document { font-weight: bold; color: #07F; }
|
||||
.typocode_yaml .type { font-weight: bold; color: #05C; }
|
||||
.typocode_yaml .key { color: #F88; }
|
||||
.typocode_yaml .comment { color: #005; font-style: italic; }
|
||||
.typocode_yaml .punct { color: #447; font-weight: bold; }
|
||||
.typocode_yaml .string { color: #944; }
|
||||
.typocode_yaml .number { color: #F99; }
|
||||
.typocode_yaml .time { color: #F99; }
|
||||
.typocode_yaml .date { color: #F99; }
|
||||
.typocode_yaml .ref { color: #944; }
|
||||
.typocode_yaml .anchor { color: #944; }
|
||||
|
||||
.typocode {
|
||||
background-color:#eee;
|
||||
padding:2px;
|
||||
margin:5px;
|
||||
margin-left:1em;
|
||||
margin-bottom:1em;
|
||||
}
|
||||
|
||||
.typocode pre {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-family: monospace;
|
||||
overflow:auto;
|
||||
}
|
||||
|
||||
|
||||
.typocode .lineno {
|
||||
text-align: right;
|
||||
/* color: #B00;*/
|
||||
font-family: monospace;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "Andale Mono", "Lucida Typewriter", "Bitstream Vera Sans Mono", fixed-width, monospace;
|
||||
/* font-size: 120%;*/
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #555;
|
||||
border: 1px dashed #5189ea;
|
||||
margin-bottom: 30px;
|
||||
margin-top: -10px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
/*+-------------------------------------------+
|
||||
| TAGLIST |
|
||||
+-------------------------------------------+*/
|
||||
|
||||
ul#taglist li {
|
||||
display: inline;
|
||||
line-height: 3em;
|
||||
}
|
||||
|
||||
.tags .tags0 { font-size: 1.0em; }
|
||||
.tags .tags1 { font-size: 1.2em; }
|
||||
.tags .tags2 { font-size: 1.4em; }
|
||||
.tags .tags3 { font-size: 1.6em; }
|
||||
.tags .tags4 { font-size: 1.8em; }
|
||||
.tags .tags5 { font-size: 2.0em; }
|
||||
.tags .tags6 { font-size: 2.2em; }
|
||||
.tags .tags7 { font-size: 2.4em; }
|
||||
.tags .tags8 { font-size: 2.6em; }
|
||||
.tags .tags9 { font-size: 2.8em; }
|
||||
.tags .tags10 { font-size: 3.0em; }
|
||||
|
|
@ -85,7 +85,7 @@
|
|||
"author": "Sami Samhuri",
|
||||
"url": "/posts/2006/02/obligatory-post-about-ruby-on-rails",
|
||||
"styles": [
|
||||
"typocode"
|
||||
"/css/typocode.css"
|
||||
]
|
||||
},
|
||||
"textmate-snippets-for-rails-assertions": {
|
||||
|
|
@ -118,7 +118,10 @@
|
|||
"textmate"
|
||||
],
|
||||
"author": "Sami Samhuri",
|
||||
"url": "/posts/2006/02/textmate-move-selection-to-self-down"
|
||||
"url": "/posts/2006/02/textmate-move-selection-to-self-down",
|
||||
"styles": [
|
||||
"/css/typocode.css"
|
||||
]
|
||||
},
|
||||
"textmate-insert-text-into-self-down": {
|
||||
"title": "TextMate: Insert text into self.down",
|
||||
|
|
@ -137,7 +140,7 @@
|
|||
"author": "Sami Samhuri",
|
||||
"url": "/posts/2006/02/textmate-insert-text-into-self-down",
|
||||
"styles": [
|
||||
"typocode"
|
||||
"/css/typocode.css"
|
||||
]
|
||||
},
|
||||
"intelligent-migration-snippets-0_1-for-textmate": {
|
||||
|
|
@ -172,7 +175,7 @@
|
|||
"author": "Sami Samhuri",
|
||||
"url": "/posts/2006/02/sjs-rails-bundle-0_2-for-textmate",
|
||||
"styles": [
|
||||
"typocode"
|
||||
"/css/typocode.css"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@
|
|||
|
||||
The macro I'm thinking of to invoke this is tab-triggered and will simply:
|
||||
<ul>
|
||||
<li>Select word (<tt><strong>⌃W</strong></tt>)</li>
|
||||
<li>Delete (<tt><strong>⌫</strong></tt>)</li>
|
||||
<li>Select to end of file (<tt><strong>⇧⌘↓</strong></tt>)</li>
|
||||
<li>Select word (<code><strong>⌃W</strong></code>)</li>
|
||||
<li>Delete (<code><strong>⌫</strong></code>)</li>
|
||||
<li>Select to end of file (<code><strong>⇧⌘↓</strong></code>)</li>
|
||||
<li>Run command "Put in self.down"</li>
|
||||
</ul>
|
||||
|
||||
|
|
@ -2,7 +2,7 @@ The steps are well documented so I won’t get into detail here but if you have
|
|||
|
||||
* Install OS X to a single partition filling your disk (optionally use your existing OS X intall)
|
||||
* Install <a href="http://refit.sourceforge.net/">rEFIt</a> (no need to reboot just yet)
|
||||
* Re-partition your disk into 3 partitions with <tt>diskutil resizeVolume</tt>, reboot and confirm it all works
|
||||
* Re-partition your disk into 3 partitions with <code>diskutil resizeVolume</code>, reboot and confirm it all works
|
||||
* Boot the Vista install DVD and install to disk0s4 aka Partition 4
|
||||
* Install Gentoo (or other distro) to disk0s3 aka /dev/sda3
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ My initial experience with Vista is quite good. Sadly the <a href="http://www.ma
|
|||
|
||||
OS X is OS X. It’s my favourite desktop OS right now because of apps like LaunchBar/Quicksilver and TextMate, a generally excellent UI, good old *nix stability, zsh out of the box! When I need WireShark or the GIMP X11 is there waiting. Mac notebooks are great and tight integration with the hardware is a clear advantage for OS X.
|
||||
|
||||
Oh yeah, I also have a Parallels VM for Windows 3.11. It boots in about second to the <tt>C:\></tt> prompt and then another second to type <tt>win</tt> and Windows to start. Without TCP/IP there’s not much to do though (I’m not going to write a driver for Parallels’ ethernet adapter).
|
||||
Oh yeah, I also have a Parallels VM for Windows 3.11. It boots in about second to the <code>C:\></code> prompt and then another second to type <code>win</code> and Windows to start. Without TCP/IP there’s not much to do though (I’m not going to write a driver for Parallels’ ethernet adapter).
|
||||
|
||||
* Dual head setups are more work than plugging in a 2nd monitor, which is too much work.
|
||||
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
I've extended ActiveRecord with `find_or_create(params)` and `find_or_initialize(params)`. Those are actually just wrappers around `find_or_do(action, params)` which does the heavy lifting.
|
||||
|
||||
They work exactly as you'd expect them to work with possibly one gotcha. If you pass in an `id` attribute then it will just find that record directly. If it fails it will try and find the record using the other params as it would have done normally.
|
||||
|
||||
Enough chat, here's the self-explanatory code:
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="c"># extend ActiveRecord::Base with find_or_create and find_or_initialize.</span><tt>
|
||||
</tt><span class="co">ActiveRecord</span>::<span class="co">Base</span>.class_eval <span class="r">do</span><tt>
|
||||
</tt> include <span class="co">ActiveRecordExtensions</span><tt>
|
||||
</tt><span class="r">end</span></pre></td>
|
||||
</tr></table>
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6<tt>
|
||||
</tt>7<tt>
|
||||
</tt>8<tt>
|
||||
</tt>9<tt>
|
||||
</tt><strong>10</strong><tt>
|
||||
</tt>11<tt>
|
||||
</tt>12<tt>
|
||||
</tt>13<tt>
|
||||
</tt>14<tt>
|
||||
</tt>15<tt>
|
||||
</tt>16<tt>
|
||||
</tt>17<tt>
|
||||
</tt>18<tt>
|
||||
</tt>19<tt>
|
||||
</tt><strong>20</strong><tt>
|
||||
</tt>21<tt>
|
||||
</tt>22<tt>
|
||||
</tt>23<tt>
|
||||
</tt>24<tt>
|
||||
</tt>25<tt>
|
||||
</tt>26<tt>
|
||||
</tt>27<tt>
|
||||
</tt>28<tt>
|
||||
</tt>29<tt>
|
||||
</tt><strong>30</strong><tt>
|
||||
</tt>31<tt>
|
||||
</tt>32<tt>
|
||||
</tt>33<tt>
|
||||
</tt>34<tt>
|
||||
</tt>35<tt>
|
||||
</tt>36<tt>
|
||||
</tt>37<tt>
|
||||
</tt>38<tt>
|
||||
</tt>39<tt>
|
||||
</tt><strong>40</strong><tt>
|
||||
</tt>41 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="r">module</span> <span class="cl">ActiveRecordExtensions</span><tt>
|
||||
</tt> <span class="r">def</span> <span class="pc">self</span>.included(base)<tt>
|
||||
</tt> base.extend(<span class="co">ClassMethods</span>)<tt>
|
||||
</tt> <span class="r">end</span><tt>
|
||||
</tt> <tt>
|
||||
</tt> <span class="r">module</span> <span class="cl">ClassMethods</span><tt>
|
||||
</tt> <span class="r">def</span> <span class="fu">find_or_initialize</span>(params)<tt>
|
||||
</tt> find_or_do(<span class="s"><span class="dl">'</span><span class="k">initialize</span><span class="dl">'</span></span>, params)<tt>
|
||||
</tt> <span class="r">end</span><tt>
|
||||
</tt><tt>
|
||||
</tt> <span class="r">def</span> <span class="fu">find_or_create</span>(params)<tt>
|
||||
</tt> find_or_do(<span class="s"><span class="dl">'</span><span class="k">create</span><span class="dl">'</span></span>, params)<tt>
|
||||
</tt> <span class="r">end</span><tt>
|
||||
</tt> <tt>
|
||||
</tt> private<tt>
|
||||
</tt> <tt>
|
||||
</tt> <span class="c"># Find a record that matches the attributes given in the +params+ hash, or do +action+</span><tt>
|
||||
</tt> <span class="c"># to retrieve a new object with the given parameters and return that.</span><tt>
|
||||
</tt> <span class="r">def</span> <span class="fu">find_or_do</span>(action, params)<tt>
|
||||
</tt> <span class="c"># if an id is given just find the record directly</span><tt>
|
||||
</tt> <span class="pc">self</span>.find(params[<span class="sy">:id</span>])<tt>
|
||||
</tt><tt>
|
||||
</tt> <span class="r">rescue</span> <span class="co">ActiveRecord</span>::<span class="co">RecordNotFound</span> => e<tt>
|
||||
</tt> attrs = {} <span class="c"># hash of attributes passed in params</span><tt>
|
||||
</tt><tt>
|
||||
</tt> <span class="c"># search for valid attributes in params</span><tt>
|
||||
</tt> <span class="pc">self</span>.column_names.map(&<span class="sy">:to_sym</span>).each <span class="r">do</span> |attrib|<tt>
|
||||
</tt> <span class="c"># skip unknown columns, and the id field</span><tt>
|
||||
</tt> <span class="r">next</span> <span class="r">if</span> params[attrib].nil? || attrib == <span class="sy">:id</span><tt>
|
||||
</tt><tt>
|
||||
</tt> attrs[attrib] = params[attrib]<tt>
|
||||
</tt> <span class="r">end</span><tt>
|
||||
</tt><tt>
|
||||
</tt> <span class="c"># no valid params given, return nil</span><tt>
|
||||
</tt> <span class="r">return</span> <span class="pc">nil</span> <span class="r">if</span> attrs.empty?<tt>
|
||||
</tt><tt>
|
||||
</tt> <span class="c"># call the appropriate ActiveRecord finder method</span><tt>
|
||||
</tt> <span class="pc">self</span>.send(<span class="s"><span class="dl">"</span><span class="k">find_or_</span><span class="il"><span class="dl">#{</span>action<span class="dl">}</span></span><span class="k">_by_</span><span class="il"><span class="dl">#{</span>attrs.keys.join(<span class="s"><span class="dl">'</span><span class="k">_and_</span><span class="dl">'</span></span>)<span class="dl">}</span></span><span class="dl">"</span></span>, *attrs.values)<tt>
|
||||
</tt> <span class="r">end</span><tt>
|
||||
</tt> <span class="r">end</span><tt>
|
||||
</tt><span class="r">end</span></pre></td>
|
||||
</tr></table>
|
||||
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
I've extended ActiveRecord with `find_or_create(params)` and `find_or_initialize(params)`. Those are actually just wrappers around `find_or_do(action, params)` which does the heavy lifting.
|
||||
|
||||
They work exactly as you'd expect them to work with possibly one gotcha. If you pass in an `id` attribute then it will just find that record directly. If it fails it will try and find the record using the other params as it would have done normally.
|
||||
|
||||
Enough chat, here's the self-explanatory code:
|
||||
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
</pre>
|
||||
<pre><code><span class="c"># extend ActiveRecord::Base with find_or_create and find_or_initialize.</span>
|
||||
<span class="co">ActiveRecord</span>::<span class="co">Base</span>.class_eval <span class="r">do</span>
|
||||
include <span class="co">ActiveRecordExtensions</span>
|
||||
<span class="r">end</span></code></pre>
|
||||
|
||||
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
<strong>10</strong>
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
<strong>20</strong>
|
||||
21
|
||||
22
|
||||
23
|
||||
24
|
||||
25
|
||||
26
|
||||
27
|
||||
28
|
||||
29
|
||||
<strong>30</strong>
|
||||
31
|
||||
32
|
||||
33
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
<strong>40</strong>
|
||||
41
|
||||
</pre>
|
||||
<pre><code><span class="r">module</span> <span class="cl">ActiveRecordExtensions</span>
|
||||
<span class="r">def</span> <span class="pc">self</span>.included(base)
|
||||
base.extend(<span class="co">ClassMethods</span>)
|
||||
<span class="r">end</span>
|
||||
|
||||
<span class="r">module</span> <span class="cl">ClassMethods</span>
|
||||
<span class="r">def</span> <span class="fu">find_or_initialize</span>(params)
|
||||
find_or_do(<span class="s"><span class="dl">'</span><span class="k">initialize</span><span class="dl">'</span></span>, params)
|
||||
<span class="r">end</span>
|
||||
|
||||
<span class="r">def</span> <span class="fu">find_or_create</span>(params)
|
||||
find_or_do(<span class="s"><span class="dl">'</span><span class="k">create</span><span class="dl">'</span></span>, params)
|
||||
<span class="r">end</span>
|
||||
|
||||
private
|
||||
|
||||
<span class="c"># Find a record that matches the attributes given in the +params+ hash, or do +action+</span>
|
||||
<span class="c"># to retrieve a new object with the given parameters and return that.</span>
|
||||
<span class="r">def</span> <span class="fu">find_or_do</span>(action, params)
|
||||
<span class="c"># if an id is given just find the record directly</span>
|
||||
<span class="pc">self</span>.find(params[<span class="sy">:id</span>])
|
||||
|
||||
<span class="r">rescue</span> <span class="co">ActiveRecord</span>::<span class="co">RecordNotFound</span> => e
|
||||
attrs = {} <span class="c"># hash of attributes passed in params</span>
|
||||
|
||||
<span class="c"># search for valid attributes in params</span>
|
||||
<span class="pc">self</span>.column_names.map(&<span class="sy">:to_sym</span>).each <span class="r">do</span> |attrib|
|
||||
<span class="c"># skip unknown columns, and the id field</span>
|
||||
<span class="r">next</span> <span class="r">if</span> params[attrib].nil? || attrib == <span class="sy">:id</span>
|
||||
|
||||
attrs[attrib] = params[attrib]
|
||||
<span class="r">end</span>
|
||||
|
||||
<span class="c"># no valid params given, return nil</span>
|
||||
<span class="r">return</span> <span class="pc">nil</span> <span class="r">if</span> attrs.empty?
|
||||
|
||||
<span class="c"># call the appropriate ActiveRecord finder method</span>
|
||||
<span class="pc">self</span>.send(<span class="s"><span class="dl">"</span><span class="k">find_or_</span><span class="il"><span class="dl">#{</span>action<span class="dl">}</span></span><span class="k">_by_</span><span class="il"><span class="dl">#{</span>attrs.keys.join(<span class="s"><span class="dl">'</span><span class="k">_and_</span><span class="dl">'</span></span>)<span class="dl">}</span></span><span class="dl">"</span></span>, *attrs.values)
|
||||
<span class="r">end</span>
|
||||
<span class="r">end</span>
|
||||
<span class="r">end</span></code></pre>
|
||||
|
|
@ -22,9 +22,9 @@ According to [a post on a MySQL mailing list](http://lists.mysql.com/mysql/12924
|
|||
|
||||
### innodb_data_file_path ###
|
||||
|
||||
On Gentoo this one bit me right in the ass, and I mentioned it above. It specifies how large the files used to store your data can be, and how many of them there are. The default setting is almost sane: <tt>ibdata1:10M:autoextend:<b>max:128M</b></tt>. Limiting the total size to 128M caused my test to fail after inserting several million rows.
|
||||
On Gentoo this one bit me right in the ass, and I mentioned it above. It specifies how large the files used to store your data can be, and how many of them there are. The default setting is almost sane: <code>ibdata1:10M:autoextend:<b>max:128M</b></code>. Limiting the total size to 128M caused my test to fail after inserting several million rows.
|
||||
|
||||
Simply removing <tt>max:128M</tt> solves the problem. The resulting setting tells the InnoDB engine to use one file, named <b>ibdata1</b> which is initially 10M in size and grows as required.
|
||||
Simply removing <code>max:128M</code> solves the problem. The resulting setting tells the InnoDB engine to use one file, named <b>ibdata1</b> which is initially 10M in size and grows as required.
|
||||
|
||||
### innodb_log_file_size ###
|
||||
|
||||
|
|
@ -36,10 +36,10 @@ Again I only went as far as the Gentoo config to learn about this setting. They
|
|||
|
||||
### Save my.cnf and restart mysqld ###
|
||||
|
||||
That’s it for the MySQL config. Restart mysqld however you do that on your platform. <tt>sudo /etc/init.d/mysql restart</tt> should look familiar to many *nix users.
|
||||
That’s it for the MySQL config. Restart mysqld however you do that on your platform. <code>sudo /etc/init.d/mysql restart</code> should look familiar to many *nix users.
|
||||
|
||||
Now you should be able to insert dozens and indeed hundreds of millions of rows into your InnoDB tables. Sadly this brought little performance gains to the table. MySQL wraps single queries in implicit transactions. Wrapping everything in a transaction may work, but inevitably something will go wrong and you may want the ability to resume inserting the rows instead of starting all over.
|
||||
|
||||
The solution now is to execute <tt>SET AUTOCOMMIT=0</tt> before inserting the data, and then issuing a <tt>COMMIT</tt> when you’re done. With all that in place I’m inserting 14,000,000 rows into both MyISAM and InnoDB tables in 30 minutes. MyISAM is still ~ 2 min faster, but as I said earlier this is adequate for now. Prior to all this it took several <b>hours</b> to insert 14,000,000 rows so I am happy.
|
||||
The solution now is to execute <code>SET AUTOCOMMIT=0</code> before inserting the data, and then issuing a <code>COMMIT</code> when you’re done. With all that in place I’m inserting 14,000,000 rows into both MyISAM and InnoDB tables in 30 minutes. MyISAM is still ~ 2 min faster, but as I said earlier this is adequate for now. Prior to all this it took several <b>hours</b> to insert 14,000,000 rows so I am happy.
|
||||
|
||||
Now you can enjoy the speed MyISAM is known for with your InnoDB tables. Consider the data integrity a bonus! ;-)
|
||||
|
|
@ -85,7 +85,10 @@
|
|||
"extensions"
|
||||
],
|
||||
"author": "Sami Samhuri",
|
||||
"url": "/posts/2007/05/enumerable-pluck-and-string-to_proc-for-ruby"
|
||||
"url": "/posts/2007/05/enumerable-pluck-and-string-to_proc-for-ruby",
|
||||
"styles": [
|
||||
"/css/typocode.css"
|
||||
]
|
||||
},
|
||||
"dumping-objects-to-the-browser-in-rails": {
|
||||
"title": "Dumping Objects to the Browser in Rails",
|
||||
|
|
@ -95,7 +98,10 @@
|
|||
"rails"
|
||||
],
|
||||
"author": "Sami Samhuri",
|
||||
"url": "/posts/2007/05/dumping-objects-to-the-browser-in-rails"
|
||||
"url": "/posts/2007/05/dumping-objects-to-the-browser-in-rails",
|
||||
"styles": [
|
||||
"/css/typocode.css"
|
||||
]
|
||||
},
|
||||
"cheating-at-life-in-general": {
|
||||
"title": "Cheating at Life in General",
|
||||
|
|
|
|||
|
|
@ -15,21 +15,21 @@ parseNumber :: liftM (Number . read) $ many1 digit
|
|||
</code></pre>
|
||||
Okay that's pretty simple right? Let's break it down, first looking at the right-hand side of the <code>$</code> operator, then the left.
|
||||
|
||||
* <tt>many1 digit</tt> reads as many decimal digits as it can.
|
||||
* <code>many1 digit</code> reads as many decimal digits as it can.
|
||||
* <code>Number . read</code> is a <a href="http://en.wikipedia.org/wiki/Function_composition_(computer_science%29">function composition</a> just like we're used to using in math. It applies <code>read</code> to its argument, then applies <code>Number</code> to that result.
|
||||
* <tt>liftM</tt> is concisely and effectively defined <a href="http://members.chello.nl/hjgtuyl/tourdemonad.html#liftM">elsewhere</a>, and I'll borrow their description:
|
||||
* <code>liftM</code> is concisely and effectively defined <a href="http://members.chello.nl/hjgtuyl/tourdemonad.html#liftM">elsewhere</a>, and I'll borrow their description:
|
||||
|
||||
<blockquote>
|
||||
<p><tt>liftM f m</tt> lets a non-monadic function <tt>f</tt> operate on the contents of monad <tt>m</tt></p>
|
||||
<p><code>liftM f m</code> lets a non-monadic function <code>f</code> operate on the contents of monad <code>m</code></p>
|
||||
</blockquote>
|
||||
|
||||
<code>liftM</code>'s type is also quite telling: <tt>liftM :: (Monad m) => (a -> b) -> (m a -> m b)</tt>
|
||||
<code>liftM</code>'s type is also quite telling: <code>liftM :: (Monad m) => (a -> b) -> (m a -> m b)</code>
|
||||
|
||||
In a nutshell <code>liftM</code> turns a function from <code>a</code> to <code>b</code> to a function from a monad containing <code>a</code> to a monad containing <code>b</code>.
|
||||
|
||||
That results in a function on the left-hand side of <code>$</code>, which operates on and outputs a monad. The content of the input monad is a <code>String</code>. The content of the output monad is a <tt>LispVal</tt> (defined earlier in the tutorial). Specifically it is a <code>Number</code>.
|
||||
That results in a function on the left-hand side of <code>$</code>, which operates on and outputs a monad. The content of the input monad is a <code>String</code>. The content of the output monad is a <code>LispVal</code> (defined earlier in the tutorial). Specifically it is a <code>Number</code>.
|
||||
|
||||
The <code>$</code> acts similar to a pipe in <code>$FAVOURITE_SHELL</code>, and is right associative which means the expression on the right is passed to the expression (function) on the left. It's exactly the same as <tt>(liftM (Number . read)) (many1 digit)</tt> except it looks cleaner. If you know LISP or Scheme (sadly I do not) then it's analogous to the <code>apply</code> function.
|
||||
The <code>$</code> acts similar to a pipe in <code>$FAVOURITE_SHELL</code>, and is right associative which means the expression on the right is passed to the expression (function) on the left. It's exactly the same as <code>(liftM (Number . read)) (many1 digit)</code> except it looks cleaner. If you know LISP or Scheme (sadly I do not) then it's analogous to the <code>apply</code> function.
|
||||
|
||||
So how does a Haskell newbie go about re-writing that using other notations which haven't even been explained in the tutorial? Clearly one must search the web and read as much as they can until they understand enough to figure it out (which is one thing I like about the tutorial). If you're lazy like me, here are 3 equivalent pieces of code for you to chew on. <code>parseNumber</code>'s type is <code>Parser LispVal</code> (Parser is a monad).
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
Here's an easy way to solve a problem that may have nagged you as it did me. Simply using <code>foo.inspect</code> to dump out some object to the browser dumps one long string which is barely useful except for short strings and the like. The ideal output is already available using the <a href="http://www.ruby-doc.org/stdlib/libdoc/prettyprint/rdoc/index.html"><code>PrettyPrint</code></a> module so we just need to use it.
|
||||
|
||||
|
||||
Unfortunately typing <tt><pre><%= PP.pp(@something, '') %></pre></tt> to quickly debug some possibly large object (or collection) can get old fast so we need a shortcut.
|
||||
Unfortunately typing <code><pre><%= PP.pp(@something, '') %></pre></code> to quickly debug some possibly large object (or collection) can get old fast so we need a shortcut.
|
||||
|
||||
|
||||
Taking the definition of <a href="http://extensions.rubyforge.org/rdoc/classes/Object.html#M000020"><code>Object#pp_s</code></a> from the <a href="http://extensions.rubyforge.org/rdoc/">extensions project</a> it's trivial to create a helper method to just dump out an object in a reasonable manner.
|
||||
|
|
@ -115,4 +115,4 @@ I wrote another version without using the various <code>#to_proc</code> methods
|
|||
|
||||
It's just icing on the cake considering Ruby's convenient block syntax, but there it is. Do with it what you will. You can change or extend any of these to support drilling down into hashes quite easily too.
|
||||
|
||||
*<strong>Update #1:</strong> Fixed a potential performance issue in <code>Enumerable#to_proc</code> by saving the results of <code>to_proc</code> in <tt>@procs</tt>.*
|
||||
*<strong>Update #1:</strong> Fixed a potential performance issue in <code>Enumerable#to_proc</code> by saving the results of <code>to_proc</code> in <code>@procs</code>.*
|
||||
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
Add to that the random HTTP 500 errors which were very noticeable while I was trying to fix that post and I was about to write my own blog or switch to WordPress.
|
||||
|
||||
I don't love WP so I decided to just upgrade Typo instead. I was using Typo 2.6, and the current stable version is 4.1. They skipped version 3 to preclude any confusion that may have ensued between Typo v3 and the CMS <a href="http://typo3.com/">Typo3</a>. So it really isn't a big upgrade and it went perfectly. I checked out a new copy of the repo because I had some difficulty getting <tt>svn switch --relocate</tt> to work, configured the database settings and issued a <tt>rake db:migrate</tt>, copied my theme over and it all just worked. Bravo Typo team, that's how an upgrade should work.
|
||||
I don't love WP so I decided to just upgrade Typo instead. I was using Typo 2.6, and the current stable version is 4.1. They skipped version 3 to preclude any confusion that may have ensued between Typo v3 and the CMS <a href="http://typo3.com/">Typo3</a>. So it really isn't a big upgrade and it went perfectly. I checked out a new copy of the repo because I had some difficulty getting <code>svn switch --relocate</code> to work, configured the database settings and issued a <code>rake db:migrate</code>, copied my theme over and it all just worked. Bravo Typo team, that's how an upgrade should work.
|
||||
|
||||
No more random 500 errors, things seem faster (better caching perhaps), and that troublesome post is troublesome no more. I am happy with Typo again.
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
*Update #1: What I first posted will take out your < key by mistake (it's available via `C-q <`), it has since been revised to Do The Right Thing.*
|
||||
|
||||
*Update #2: Thanks to an anonymouse[sic] commenter this code is a little cleaner.*
|
||||
|
||||
*Update #3: I should read the Emacs manual sometime, especially since I have it in dead-tree form. Check out <a href="http://www.gnu.org/software/emacs/manual/html_node/autotype/Inserting-Pairs.html">skeleton pairs</a> in the Emacs manual.*
|
||||
|
||||
Despite my current infatuation with Emacs there are many reasons I started using TextMate, especially little time-savers that are very addictive. I'll talk about one of those features tonight. When you have text selected in TextMate and you hit say the <code>'</code> (single quote) then TextMate will surround the selected text with single quotes. The same goes for double quotes, parentheses, brackets, and braces. This little trick is one of my favourites so I had to come up with something similar in Emacs. It was easy since a <a href="http://osdir.com/ml/emacs.nxml.general/2005-08/msg00002.html">mailing list post</a> has a solution for surrounding the current region with tags, which served as a great starting point.
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6<tt>
|
||||
</tt>7 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">(defun surround-region-with-tag (tag-name beg end)<tt>
|
||||
</tt> (interactive "sTag name: \nr")<tt>
|
||||
</tt> (save-excursion<tt>
|
||||
</tt> (goto-char beg)<tt>
|
||||
</tt> (insert "<" tag-name ">")<tt>
|
||||
</tt> (goto-char (+ end 2 (length tag-name)))<tt>
|
||||
</tt> (insert "</" tag-name ">")))</pre></td>
|
||||
</tr></table>
|
||||
|
||||
|
||||
With a little modification I now have the following in my ~/.emacs file:
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6<tt>
|
||||
</tt>7<tt>
|
||||
</tt>8<tt>
|
||||
</tt>9<tt>
|
||||
</tt><strong>10</strong><tt>
|
||||
</tt>11<tt>
|
||||
</tt>12<tt>
|
||||
</tt>13<tt>
|
||||
</tt>14<tt>
|
||||
</tt>15<tt>
|
||||
</tt>16<tt>
|
||||
</tt>17<tt>
|
||||
</tt>18<tt>
|
||||
</tt>19<tt>
|
||||
</tt><strong>20</strong><tt>
|
||||
</tt>21<tt>
|
||||
</tt>22<tt>
|
||||
</tt>23<tt>
|
||||
</tt>24<tt>
|
||||
</tt>25<tt>
|
||||
</tt>26<tt>
|
||||
</tt>27<tt>
|
||||
</tt>28<tt>
|
||||
</tt>29<tt>
|
||||
</tt><strong>30</strong><tt>
|
||||
</tt>31<tt>
|
||||
</tt>32<tt>
|
||||
</tt>33<tt>
|
||||
</tt>34<tt>
|
||||
</tt>35<tt>
|
||||
</tt>36<tt>
|
||||
</tt>37<tt>
|
||||
</tt>38<tt>
|
||||
</tt>39<tt>
|
||||
</tt><strong>40</strong><tt>
|
||||
</tt>41<tt>
|
||||
</tt>42<tt>
|
||||
</tt>43<tt>
|
||||
</tt>44<tt>
|
||||
</tt>45<tt>
|
||||
</tt>46<tt>
|
||||
</tt>47 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">;; help out a TextMate junkie<tt>
|
||||
</tt><tt>
|
||||
</tt>(defun wrap-region (left right beg end)<tt>
|
||||
</tt> "Wrap the region in arbitrary text, LEFT goes to the left and RIGHT goes to the right."<tt>
|
||||
</tt> (interactive)<tt>
|
||||
</tt> (save-excursion<tt>
|
||||
</tt> (goto-char beg)<tt>
|
||||
</tt> (insert left)<tt>
|
||||
</tt> (goto-char (+ end (length left)))<tt>
|
||||
</tt> (insert right)))<tt>
|
||||
</tt><tt>
|
||||
</tt>(defmacro wrap-region-with-function (left right)<tt>
|
||||
</tt> "Returns a function which, when called, will interactively `wrap-region-or-insert' using LEFT and RIGHT."<tt>
|
||||
</tt> `(lambda () (interactive)<tt>
|
||||
</tt> (wrap-region-or-insert ,left ,right)))<tt>
|
||||
</tt><tt>
|
||||
</tt>(defun wrap-region-with-tag-or-insert ()<tt>
|
||||
</tt> (interactive)<tt>
|
||||
</tt> (if (and mark-active transient-mark-mode)<tt>
|
||||
</tt> (call-interactively 'wrap-region-with-tag)<tt>
|
||||
</tt> (insert "<")))<tt>
|
||||
</tt><tt>
|
||||
</tt>(defun wrap-region-with-tag (tag beg end)<tt>
|
||||
</tt> "Wrap the region in the given HTML/XML tag using `wrap-region'. If any<tt>
|
||||
</tt>attributes are specified then they are only included in the opening tag."<tt>
|
||||
</tt> (interactive "*sTag (including attributes): \nr")<tt>
|
||||
</tt> (let* ((elems (split-string tag " "))<tt>
|
||||
</tt> (tag-name (car elems))<tt>
|
||||
</tt> (right (concat "</" tag-name ">")))<tt>
|
||||
</tt> (if (= 1 (length elems))<tt>
|
||||
</tt> (wrap-region (concat "<" tag-name ">") right beg end)<tt>
|
||||
</tt> (wrap-region (concat "<" tag ">") right beg end))))<tt>
|
||||
</tt><tt>
|
||||
</tt>(defun wrap-region-or-insert (left right)<tt>
|
||||
</tt> "Wrap the region with `wrap-region' if an active region is marked, otherwise insert LEFT at point."<tt>
|
||||
</tt> (interactive)<tt>
|
||||
</tt> (if (and mark-active transient-mark-mode)<tt>
|
||||
</tt> (wrap-region left right (region-beginning) (region-end))<tt>
|
||||
</tt> (insert left)))<tt>
|
||||
</tt><tt>
|
||||
</tt>(global-set-key "'" (wrap-region-with-function "'" "'"))<tt>
|
||||
</tt>(global-set-key "\"" (wrap-region-with-function "\"" "\""))<tt>
|
||||
</tt>(global-set-key "`" (wrap-region-with-function "`" "`"))<tt>
|
||||
</tt>(global-set-key "(" (wrap-region-with-function "(" ")"))<tt>
|
||||
</tt>(global-set-key "[" (wrap-region-with-function "[" "]"))<tt>
|
||||
</tt>(global-set-key "{" (wrap-region-with-function "{" "}"))<tt>
|
||||
</tt>(global-set-key "<" 'wrap-region-with-tag-or-insert) ;; I opted not to have a wrap-with-angle-brackets</pre></td>
|
||||
</tr></table>
|
||||
|
||||
|
||||
<a href="/f/wrap-region.el" alt="wrap-region.el">Download wrap-region.el</a>
|
||||
|
||||
That more or less sums up why I like Emacs so much. I wanted that functionality so I implemented it (barely! It was basically done for me), debugged it by immediately evaluating sexps and then trying it out, and then once it worked I reloaded my config and used the wanted feature. That's just awesome, and shows one strength of open source.
|
||||
128
public/posts/2007/06/emacs-for-textmate-junkies.md
Normal file
128
public/posts/2007/06/emacs-for-textmate-junkies.md
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
*Update #1: What I first posted will take out your < key by mistake (it's available via `C-q <`), it has since been revised to Do The Right Thing.*
|
||||
|
||||
*Update #2: Thanks to an anonymouse[sic] commenter this code is a little cleaner.*
|
||||
|
||||
*Update #3: I should read the Emacs manual sometime, especially since I have it in dead-tree form. Check out <a href="http://www.gnu.org/software/emacs/manual/html_node/autotype/Inserting-Pairs.html">skeleton pairs</a> in the Emacs manual.*
|
||||
|
||||
Despite my current infatuation with Emacs there are many reasons I started using TextMate, especially little time-savers that are very addictive. I'll talk about one of those features tonight. When you have text selected in TextMate and you hit say the <code>'</code> (single quote) then TextMate will surround the selected text with single quotes. The same goes for double quotes, parentheses, brackets, and braces. This little trick is one of my favourites so I had to come up with something similar in Emacs. It was easy since a <a href="http://osdir.com/ml/emacs.nxml.general/2005-08/msg00002.html">mailing list post</a> has a solution for surrounding the current region with tags, which served as a great starting point.
|
||||
|
||||
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
</pre>
|
||||
<pre><code>(defun surround-region-with-tag (tag-name beg end)
|
||||
(interactive "sTag name: \nr")
|
||||
(save-excursion
|
||||
(goto-char beg)
|
||||
(insert "<" tag-name ">")
|
||||
(goto-char (+ end 2 (length tag-name)))
|
||||
(insert "</" tag-name ">")))</code></pre>
|
||||
|
||||
|
||||
With a little modification I now have the following in my ~/.emacs file:
|
||||
|
||||
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
<strong>10</strong>
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
<strong>20</strong>
|
||||
21
|
||||
22
|
||||
23
|
||||
24
|
||||
25
|
||||
26
|
||||
27
|
||||
28
|
||||
29
|
||||
<strong>30</strong>
|
||||
31
|
||||
32
|
||||
33
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
<strong>40</strong>
|
||||
41
|
||||
42
|
||||
43
|
||||
44
|
||||
45
|
||||
46
|
||||
47
|
||||
</pre>
|
||||
<pre><code>;; help out a TextMate junkie
|
||||
|
||||
(defun wrap-region (left right beg end)
|
||||
"Wrap the region in arbitrary text, LEFT goes to the left and RIGHT goes to the right."
|
||||
(interactive)
|
||||
(save-excursion
|
||||
(goto-char beg)
|
||||
(insert left)
|
||||
(goto-char (+ end (length left)))
|
||||
(insert right)))
|
||||
|
||||
(defmacro wrap-region-with-function (left right)
|
||||
"Returns a function which, when called, will interactively `wrap-region-or-insert' using LEFT and RIGHT."
|
||||
`(lambda () (interactive)
|
||||
(wrap-region-or-insert ,left ,right)))
|
||||
|
||||
(defun wrap-region-with-tag-or-insert ()
|
||||
(interactive)
|
||||
(if (and mark-active transient-mark-mode)
|
||||
(call-interactively 'wrap-region-with-tag)
|
||||
(insert "<")))
|
||||
|
||||
(defun wrap-region-with-tag (tag beg end)
|
||||
"Wrap the region in the given HTML/XML tag using `wrap-region'. If any
|
||||
attributes are specified then they are only included in the opening tag."
|
||||
(interactive "*sTag (including attributes): \nr")
|
||||
(let* ((elems (split-string tag " "))
|
||||
(tag-name (car elems))
|
||||
(right (concat "</" tag-name ">")))
|
||||
(if (= 1 (length elems))
|
||||
(wrap-region (concat "<" tag-name ">") right beg end)
|
||||
(wrap-region (concat "<" tag ">") right beg end))))
|
||||
|
||||
(defun wrap-region-or-insert (left right)
|
||||
"Wrap the region with `wrap-region' if an active region is marked, otherwise insert LEFT at point."
|
||||
(interactive)
|
||||
(if (and mark-active transient-mark-mode)
|
||||
(wrap-region left right (region-beginning) (region-end))
|
||||
(insert left)))
|
||||
|
||||
(global-set-key "'" (wrap-region-with-function "'" "'"))
|
||||
(global-set-key "\"" (wrap-region-with-function "\"" "\""))
|
||||
(global-set-key "`" (wrap-region-with-function "`" "`"))
|
||||
(global-set-key "(" (wrap-region-with-function "(" ")"))
|
||||
(global-set-key "[" (wrap-region-with-function "[" "]"))
|
||||
(global-set-key "{" (wrap-region-with-function "{" "}"))
|
||||
(global-set-key "<" 'wrap-region-with-tag-or-insert) ;; I opted not to have a wrap-with-angle-brackets</code></pre>
|
||||
|
||||
↓ <a href="/f/wrap-region.el" alt="wrap-region.el">Download wrap-region.el</a>
|
||||
|
||||
That more or less sums up why I like Emacs so much. I wanted that functionality so I implemented it (barely! It was basically done for me), debugged it by immediately evaluating sexps and then trying it out, and then once it worked I reloaded my config and used the wanted feature. That's just awesome, and shows one strength of open source.
|
||||
|
|
@ -1,441 +0,0 @@
|
|||
### Parsing floating point numbers ###
|
||||
|
||||
The first task is extending the <code>LispVal</code> type to grok floats.
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6<tt>
|
||||
</tt>7<tt>
|
||||
</tt>8<tt>
|
||||
</tt>9<tt>
|
||||
</tt><strong>10</strong><tt>
|
||||
</tt>11<tt>
|
||||
</tt>12<tt>
|
||||
</tt>13<tt>
|
||||
</tt>14<tt>
|
||||
</tt>15 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">type LispInt = Integer<tt>
|
||||
</tt>type LispFloat = Float<tt>
|
||||
</tt><tt>
|
||||
</tt>-- numeric data types<tt>
|
||||
</tt>data LispNum = Integer LispInt<tt>
|
||||
</tt> | Float LispFloat<tt>
|
||||
</tt><tt>
|
||||
</tt>-- data types<tt>
|
||||
</tt>data LispVal = Atom String<tt>
|
||||
</tt> | List [LispVal]<tt>
|
||||
</tt> | DottedList [LispVal] LispVal<tt>
|
||||
</tt> | Number LispNum<tt>
|
||||
</tt> | Char Char<tt>
|
||||
</tt> | String String<tt>
|
||||
</tt> | ...</pre></td>
|
||||
</tr></table>
|
||||
|
||||
|
||||
The reason for using the new <code>LispNum</code> type and not just throwing a new <code>Float Float</code> constructor in there is so that functions can accept and operate on parameters of any supported numeric type. First the floating point numbers need to be parsed. For now I only parse floating point numbers in decimal because the effort to parse other bases is too great for the benefits gained (none, for me).
|
||||
|
||||
ElSchemo now parses negative numbers so I'll start with 2 helper functions that are used when parsing both integers and floats:
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6<tt>
|
||||
</tt>7 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">parseSign :: Parser Char<tt>
|
||||
</tt>parseSign = do try (char '-')<tt>
|
||||
</tt> <|> do optional (char '+')<tt>
|
||||
</tt> return '+'<tt>
|
||||
</tt><tt>
|
||||
</tt>applySign :: Char -> LispNum -> LispNum<tt>
|
||||
</tt>applySign sign n = if sign == '-' then negate n else n</pre></td>
|
||||
</tr></table>
|
||||
|
||||
|
||||
<code>parseSign</code> is straightforward as it follows the convention that a literal number is positive unless explicitly marked as negative with a leading minus sign. A leading plus sign is allowed but not required.
|
||||
|
||||
<code>applySign</code> takes a sign character and a <code>LispNum</code> and negates it if necessary, returning a <code>LispNum</code>.
|
||||
|
||||
Armed with these 2 functions we can now parse floating point numbers in decimal. Conforming to R5RS an optional <code>#d</code> prefix is allowed.
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6<tt>
|
||||
</tt>7<tt>
|
||||
</tt>8 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">parseFloat :: Parser LispVal<tt>
|
||||
</tt>parseFloat = do optional (string "#d")<tt>
|
||||
</tt> sign <- parseSign<tt>
|
||||
</tt> whole <- many1 digit<tt>
|
||||
</tt> char '.'<tt>
|
||||
</tt> fract <- many1 digit<tt>
|
||||
</tt> return . Number $ applySign sign (makeFloat whole fract)<tt>
|
||||
</tt> where makeFloat whole fract = Float . fst . head . readFloat $ whole ++ "." ++ fract</pre></td>
|
||||
</tr></table>
|
||||
|
||||
|
||||
The first 6 lines should be clear. Line 7 simply applies the parsed sign to the parsed number and returns it, delegating most of the work to <code>makeFloat</code>. <code>makeFloat</code> in turn delegates the work to the <code>readFloat</code> library function, extracts the result and constructs a <code>LispNum</code> for it.
|
||||
|
||||
The last step for parsing is to modify <code>parseExpr</code> to try and parse floats.
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6<tt>
|
||||
</tt>7<tt>
|
||||
</tt>8<tt>
|
||||
</tt>9<tt>
|
||||
</tt><strong>10</strong><tt>
|
||||
</tt>11<tt>
|
||||
</tt>12<tt>
|
||||
</tt>13<tt>
|
||||
</tt>14 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">-- Integers, floats, characters and atoms can all start with a # so wrap those with try.<tt>
|
||||
</tt>-- (Left factor the grammar in the future)<tt>
|
||||
</tt>parseExpr :: Parser LispVal<tt>
|
||||
</tt>parseExpr = (try parseFloat)<tt>
|
||||
</tt> <|> (try parseInteger)<tt>
|
||||
</tt> <|> (try parseChar)<tt>
|
||||
</tt> <|> parseAtom<tt>
|
||||
</tt> <|> parseString<tt>
|
||||
</tt> <|> parseQuoted<tt>
|
||||
</tt> <|> do char '('<tt>
|
||||
</tt> x <- (try parseList) <|> parseDottedList<tt>
|
||||
</tt> char ')'<tt>
|
||||
</tt> return x<tt>
|
||||
</tt> <|> parseComment</pre></td>
|
||||
</tr></table>
|
||||
|
||||
|
||||
### Displaying the floats ###
|
||||
|
||||
|
||||
That's it for parsing, now let's provide a way to display these suckers. <code>LispVal</code> is an instance of show, where <code>show</code> = <code>showVal</code> so <code>showVal</code> is our first stop. Remembering that <code>LispVal</code> now has a single <code>Number</code> constructor we modify it accordingly:
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6<tt>
|
||||
</tt>7 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">showVal (Number n) = showNum n<tt>
|
||||
</tt><tt>
|
||||
</tt>showNum :: LispNum -> String<tt>
|
||||
</tt>showNum (Integer contents) = show contents<tt>
|
||||
</tt>showNum (Float contents) = show contents<tt>
|
||||
</tt><tt>
|
||||
</tt>instance Show LispNum where show = showNum</pre></td>
|
||||
</tr></table>
|
||||
|
||||
|
||||
One last, and certainly not least, step is to modify <code>eval</code> so that numbers evaluate to themselves.
|
||||
|
||||
|
||||
eval env val@(Number _) = return val
|
||||
|
||||
There's a little more housekeeping to be done such as fixing <code>integer?</code>, <code>number?</code>, implementing <code>float?</code> but I will leave those as an exercise to the reader, or just wait until I share the full code. As it stands now floating point numbers can be parsed and displayed. If you fire up the interpreter and type <code>2.5</code> or <code>-10.88</code> they will be understood. Now try adding them:
|
||||
|
||||
(+ 2.5 1.1)
|
||||
Invalid type: expected integer, found 2.5
|
||||
|
||||
Oops, we don't know how to operate on floats yet!
|
||||
|
||||
### Operating on floats ###
|
||||
|
||||
Parsing was the easy part. Operating on the new floats is not necessarily difficult, but it was more work than I realized it would be. I don't claim that this is the best or the only way to operate on any <code>LispNum</code>, it's just the way I did it and it seems to work. There's a bunch of boilerplate necessary to make <code>LispNum</code> an instance of the required classes, Eq, Num, Real, and Ord. I don't think I have done this properly but for now it works. What is clearly necessary is the code that operates on different types of numbers. I think I've specified sane semantics for coercion. This will be very handy shortly.
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6<tt>
|
||||
</tt>7<tt>
|
||||
</tt>8<tt>
|
||||
</tt>9<tt>
|
||||
</tt><strong>10</strong><tt>
|
||||
</tt>11<tt>
|
||||
</tt>12<tt>
|
||||
</tt>13<tt>
|
||||
</tt>14<tt>
|
||||
</tt>15<tt>
|
||||
</tt>16<tt>
|
||||
</tt>17<tt>
|
||||
</tt>18<tt>
|
||||
</tt>19<tt>
|
||||
</tt><strong>20</strong><tt>
|
||||
</tt>21<tt>
|
||||
</tt>22<tt>
|
||||
</tt>23<tt>
|
||||
</tt>24<tt>
|
||||
</tt>25<tt>
|
||||
</tt>26<tt>
|
||||
</tt>27<tt>
|
||||
</tt>28<tt>
|
||||
</tt>29<tt>
|
||||
</tt><strong>30</strong><tt>
|
||||
</tt>31<tt>
|
||||
</tt>32<tt>
|
||||
</tt>33<tt>
|
||||
</tt>34<tt>
|
||||
</tt>35<tt>
|
||||
</tt>36<tt>
|
||||
</tt>37<tt>
|
||||
</tt>38<tt>
|
||||
</tt>39<tt>
|
||||
</tt><strong>40</strong><tt>
|
||||
</tt>41<tt>
|
||||
</tt>42<tt>
|
||||
</tt>43<tt>
|
||||
</tt>44<tt>
|
||||
</tt>45<tt>
|
||||
</tt>46<tt>
|
||||
</tt>47<tt>
|
||||
</tt>48<tt>
|
||||
</tt>49<tt>
|
||||
</tt><strong>50</strong><tt>
|
||||
</tt>51<tt>
|
||||
</tt>52<tt>
|
||||
</tt>53<tt>
|
||||
</tt>54<tt>
|
||||
</tt>55<tt>
|
||||
</tt>56<tt>
|
||||
</tt>57<tt>
|
||||
</tt>58<tt>
|
||||
</tt>59<tt>
|
||||
</tt><strong>60</strong><tt>
|
||||
</tt>61<tt>
|
||||
</tt>62<tt>
|
||||
</tt>63<tt>
|
||||
</tt>64<tt>
|
||||
</tt>65<tt>
|
||||
</tt>66<tt>
|
||||
</tt>67<tt>
|
||||
</tt>68<tt>
|
||||
</tt>69<tt>
|
||||
</tt><strong>70 </strong><tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">lispNumEq :: LispNum -> LispNum -> Bool<tt>
|
||||
</tt>lispNumEq (Integer arg1) (Integer arg2) = arg1 == arg2<tt>
|
||||
</tt>lispNumEq (Integer arg1) (Float arg2) = (fromInteger arg1) == arg2<tt>
|
||||
</tt>lispNumEq (Float arg1) (Float arg2) = arg1 == arg2<tt>
|
||||
</tt>lispNumEq (Float arg1) (Integer arg2) = arg1 == (fromInteger arg2)<tt>
|
||||
</tt><tt>
|
||||
</tt>instance Eq LispNum where (==) = lispNumEq<tt>
|
||||
</tt><tt>
|
||||
</tt>lispNumPlus :: LispNum -> LispNum -> LispNum<tt>
|
||||
</tt>lispNumPlus (Integer x) (Integer y) = Integer $ x + y<tt>
|
||||
</tt>lispNumPlus (Integer x) (Float y) = Float $ (fromInteger x) + y<tt>
|
||||
</tt>lispNumPlus (Float x) (Float y) = Float $ x + y<tt>
|
||||
</tt>lispNumPlus (Float x) (Integer y) = Float $ x + (fromInteger y)<tt>
|
||||
</tt><tt>
|
||||
</tt>lispNumMinus :: LispNum -> LispNum -> LispNum<tt>
|
||||
</tt>lispNumMinus (Integer x) (Integer y) = Integer $ x - y<tt>
|
||||
</tt>lispNumMinus (Integer x) (Float y) = Float $ (fromInteger x) - y<tt>
|
||||
</tt>lispNumMinus (Float x) (Float y) = Float $ x - y<tt>
|
||||
</tt>lispNumMinus (Float x) (Integer y) = Float $ x - (fromInteger y)<tt>
|
||||
</tt><tt>
|
||||
</tt>lispNumMult :: LispNum -> LispNum -> LispNum<tt>
|
||||
</tt>lispNumMult (Integer x) (Integer y) = Integer $ x * y<tt>
|
||||
</tt>lispNumMult (Integer x) (Float y) = Float $ (fromInteger x) * y<tt>
|
||||
</tt>lispNumMult (Float x) (Float y) = Float $ x * y<tt>
|
||||
</tt>lispNumMult (Float x) (Integer y) = Float $ x * (fromInteger y)<tt>
|
||||
</tt><tt>
|
||||
</tt>lispNumDiv :: LispNum -> LispNum -> LispNum<tt>
|
||||
</tt>lispNumDiv (Integer x) (Integer y) = Integer $ x `div` y<tt>
|
||||
</tt>lispNumDiv (Integer x) (Float y) = Float $ (fromInteger x) / y<tt>
|
||||
</tt>lispNumDiv (Float x) (Float y) = Float $ x / y<tt>
|
||||
</tt>lispNumDiv (Float x) (Integer y) = Float $ x / (fromInteger y)<tt>
|
||||
</tt><tt>
|
||||
</tt>lispNumAbs :: LispNum -> LispNum<tt>
|
||||
</tt>lispNumAbs (Integer x) = Integer (abs x)<tt>
|
||||
</tt>lispNumAbs (Float x) = Float (abs x)<tt>
|
||||
</tt><tt>
|
||||
</tt>lispNumSignum :: LispNum -> LispNum<tt>
|
||||
</tt>lispNumSignum (Integer x) = Integer (signum x)<tt>
|
||||
</tt>lispNumSignum (Float x) = Float (signum x)<tt>
|
||||
</tt><tt>
|
||||
</tt>instance Num LispNum where<tt>
|
||||
</tt> (+) = lispNumPlus<tt>
|
||||
</tt> (-) = lispNumMinus<tt>
|
||||
</tt> (*) = lispNumMult<tt>
|
||||
</tt> abs = lispNumAbs<tt>
|
||||
</tt> signum = lispNumSignum<tt>
|
||||
</tt> fromInteger x = Integer x<tt>
|
||||
</tt><tt>
|
||||
</tt><tt>
|
||||
</tt>lispNumToRational :: LispNum -> Rational<tt>
|
||||
</tt>lispNumToRational (Integer x) = toRational x<tt>
|
||||
</tt>lispNumToRational (Float x) = toRational x<tt>
|
||||
</tt><tt>
|
||||
</tt>instance Real LispNum where<tt>
|
||||
</tt> toRational = lispNumToRational<tt>
|
||||
</tt><tt>
|
||||
</tt><tt>
|
||||
</tt>lispIntQuotRem :: LispInt -> LispInt -> (LispInt, LispInt)<tt>
|
||||
</tt>lispIntQuotRem n d = quotRem n d<tt>
|
||||
</tt><tt>
|
||||
</tt>lispIntToInteger :: LispInt -> Integer<tt>
|
||||
</tt>lispIntToInteger x = x<tt>
|
||||
</tt><tt>
|
||||
</tt>lispNumLessThanEq :: LispNum -> LispNum -> Bool<tt>
|
||||
</tt>lispNumLessThanEq (Integer x) (Integer y) = x <= y<tt>
|
||||
</tt>lispNumLessThanEq (Integer x) (Float y) = (fromInteger x) <= y<tt>
|
||||
</tt>lispNumLessThanEq (Float x) (Integer y) = x <= (fromInteger y)<tt>
|
||||
</tt>lispNumLessThanEq (Float x) (Float y) = x <= y<tt>
|
||||
</tt><tt>
|
||||
</tt>instance Ord LispNum where (<=) = lispNumLessThanEq</pre></td>
|
||||
</tr></table>
|
||||
|
||||
|
||||
Phew, ok with that out of the way now we can actually extend our operators to work with any type of <code>LispNum</code>. Our Scheme operators are defined using the functions <code>numericBinop</code> and <code>numBoolBinop</code>. First we'll slightly modify our definition of <code>primitives</code>:
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6<tt>
|
||||
</tt>7<tt>
|
||||
</tt>8<tt>
|
||||
</tt>9<tt>
|
||||
</tt><strong>10</strong><tt>
|
||||
</tt>11<tt>
|
||||
</tt>12<tt>
|
||||
</tt>13<tt>
|
||||
</tt>14<tt>
|
||||
</tt>15 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">primitives :: [(String, [LispVal] -> ThrowsError LispVal)]<tt>
|
||||
</tt>primitives = [("+", numericBinop (+)),<tt>
|
||||
</tt> ("-", subtractOp),<tt>
|
||||
</tt> ("*", numericBinop (*)),<tt>
|
||||
</tt> ("/", floatBinop (/)),<tt>
|
||||
</tt> ("mod", integralBinop mod),<tt>
|
||||
</tt> ("quotient", integralBinop quot),<tt>
|
||||
</tt> ("remainder", integralBinop rem),<tt>
|
||||
</tt> ("=", numBoolBinop (==)),<tt>
|
||||
</tt> ("<", numBoolBinop (<)),<tt>
|
||||
</tt> (">", numBoolBinop (>)),<tt>
|
||||
</tt> ("/=", numBoolBinop (/=)),<tt>
|
||||
</tt> (">=", numBoolBinop (>=)),<tt>
|
||||
</tt> ("<=", numBoolBinop (<=)),<tt>
|
||||
</tt> ...]</pre></td>
|
||||
</tr></table>
|
||||
|
||||
|
||||
Note that <code>mod</code>, <code>quotient</code>, and <code>remainder</code> are only defined for integers and as such use <code>integralBinop</code>, while division (/) is only defined for floating point numbers using <code>floatBinop</code>. <code>subtractOp</code> is different to support unary usage, e.g. <code>(- 4) => -4</code>, but it uses <code>numericBinop</code> internally when more than 1 argument is given. On to the implementation! First extend <code>unpackNum</code> to work with any <code>LispNum</code>, and provide separate <code>unpackInt</code> and <code>unpackFloat</code> functions to handle both kinds of <code>LispNum</code>.
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6<tt>
|
||||
</tt>7<tt>
|
||||
</tt>8<tt>
|
||||
</tt>9<tt>
|
||||
</tt><strong>10</strong><tt>
|
||||
</tt>11<tt>
|
||||
</tt>12<tt>
|
||||
</tt>13<tt>
|
||||
</tt>14<tt>
|
||||
</tt>15 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">unpackNum :: LispVal -> ThrowsError LispNum<tt>
|
||||
</tt>unpackNum (Number (Integer n)) = return $ Integer n<tt>
|
||||
</tt>unpackNum (Number (Float n)) = return $ Float n<tt>
|
||||
</tt>unpackNum notNum = throwError $ TypeMismatch "number" notNum<tt>
|
||||
</tt><tt>
|
||||
</tt>unpackInt :: LispVal -> ThrowsError Integer<tt>
|
||||
</tt>unpackInt (Number (Integer n)) = return n<tt>
|
||||
</tt>unpackInt (List [n]) = unpackInt n<tt>
|
||||
</tt>unpackInt notInt = throwError $ TypeMismatch "integer" notInt<tt>
|
||||
</tt><tt>
|
||||
</tt>unpackFloat :: LispVal -> ThrowsError Float<tt>
|
||||
</tt>unpackFloat (Number (Float f)) = return f<tt>
|
||||
</tt>unpackFloat (Number (Integer f)) = return $ fromInteger f<tt>
|
||||
</tt>unpackFloat (List [f]) = unpackFloat f<tt>
|
||||
</tt>unpackFloat notFloat = throwError $ TypeMismatch "float" notFloat</pre></td>
|
||||
</tr></table>
|
||||
|
||||
|
||||
The initial work of separating integers and floats into the <code>LispNum</code> abstraction, and the code I said would be handy shortly, are going to be really handy here. There's relatively no change in <code>numericBinop</code> except for the type signature. <code>integralBinop</code> and <code>floatBinop</code> are just specific versions of the same function. I'm sure there's a nice Haskelly way of doing this with less repetition, and I welcome such corrections.
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6<tt>
|
||||
</tt>7<tt>
|
||||
</tt>8<tt>
|
||||
</tt>9<tt>
|
||||
</tt><strong>10</strong><tt>
|
||||
</tt>11<tt>
|
||||
</tt>12<tt>
|
||||
</tt>13<tt>
|
||||
</tt>14<tt>
|
||||
</tt>15<tt>
|
||||
</tt>16<tt>
|
||||
</tt>17<tt>
|
||||
</tt>18 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">numericBinop :: (LispNum -> LispNum -> LispNum) -> [LispVal] -> ThrowsError LispVal<tt>
|
||||
</tt>numericBinop op singleVal@[_] = throwError $ NumArgs 2 singleVal<tt>
|
||||
</tt>numericBinop op params = mapM unpackNum params >>= return . Number . foldl1 op<tt>
|
||||
</tt><tt>
|
||||
</tt>integralBinop :: (LispInt -> LispInt -> LispInt) -> [LispVal] -> ThrowsError LispVal<tt>
|
||||
</tt>integralBinop op singleVal@[_] = throwError $ NumArgs 2 singleVal<tt>
|
||||
</tt>integralBinop op params = mapM unpackInt params >>= return . Number . Integer . foldl1 op<tt>
|
||||
</tt><tt>
|
||||
</tt>floatBinop :: (LispFloat -> LispFloat -> LispFloat) -> [LispVal] -> ThrowsError LispVal<tt>
|
||||
</tt>floatBinop op singleVal@[_] = throwError $ NumArgs 2 singleVal<tt>
|
||||
</tt>floatBinop op params = mapM unpackFloat params >>= return . Number . Float . foldl1 op<tt>
|
||||
</tt><tt>
|
||||
</tt>subtractOp :: [LispVal] -> ThrowsError LispVal<tt>
|
||||
</tt>subtractOp num@[_] = unpackNum (head num) >>= return . Number . negate<tt>
|
||||
</tt>subtractOp params = numericBinop (-) params<tt>
|
||||
</tt><tt>
|
||||
</tt>numBoolBinop :: (LispNum -> LispNum -> Bool) -> [LispVal] -> ThrowsError LispVal<tt>
|
||||
</tt>numBoolBinop op params = boolBinop unpackNum op params</pre></td>
|
||||
</tr></table>
|
||||
|
||||
|
||||
That was a bit of work but now ElSchemo supports floating point numbers, and if you're following along then your Scheme might too if I haven't missed any important details!
|
||||
|
||||
|
||||
Next time I'll go over some of the special forms I have added, including short-circuiting <code>and</code> and <code>or</code> forms and the full repetoire of <code>let</code>, <code>let*</code>, and <code>letrec</code>. Stay tuned!
|
||||
423
public/posts/2007/06/floating-point-in-elschemo.md
Normal file
423
public/posts/2007/06/floating-point-in-elschemo.md
Normal file
|
|
@ -0,0 +1,423 @@
|
|||
### Parsing floating point numbers ###
|
||||
|
||||
The first task is extending the <code>LispVal</code> type to grok floats.
|
||||
|
||||
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
<strong>10</strong>
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
</pre>
|
||||
<pre><code>type LispInt = Integer
|
||||
type LispFloat = Float
|
||||
|
||||
-- numeric data types
|
||||
data LispNum = Integer LispInt
|
||||
| Float LispFloat
|
||||
|
||||
-- data types
|
||||
data LispVal = Atom String
|
||||
| List [LispVal]
|
||||
| DottedList [LispVal] LispVal
|
||||
| Number LispNum
|
||||
| Char Char
|
||||
| String String
|
||||
| ...</code></pre>
|
||||
|
||||
|
||||
The reason for using the new <code>LispNum</code> type and not just throwing a new <code>Float Float</code> constructor in there is so that functions can accept and operate on parameters of any supported numeric type. First the floating point numbers need to be parsed. For now I only parse floating point numbers in decimal because the effort to parse other bases is too great for the benefits gained (none, for me).
|
||||
|
||||
ElSchemo now parses negative numbers so I'll start with 2 helper functions that are used when parsing both integers and floats:
|
||||
|
||||
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
</pre>
|
||||
<pre><code>parseSign :: Parser Char
|
||||
parseSign = do try (char '-')
|
||||
<|> do optional (char '+')
|
||||
return '+'
|
||||
|
||||
applySign :: Char -> LispNum -> LispNum
|
||||
applySign sign n = if sign == '-' then negate n else n</code></pre>
|
||||
|
||||
|
||||
<code>parseSign</code> is straightforward as it follows the convention that a literal number is positive unless explicitly marked as negative with a leading minus sign. A leading plus sign is allowed but not required.
|
||||
|
||||
<code>applySign</code> takes a sign character and a <code>LispNum</code> and negates it if necessary, returning a <code>LispNum</code>.
|
||||
|
||||
Armed with these 2 functions we can now parse floating point numbers in decimal. Conforming to R5RS an optional <code>#d</code> prefix is allowed.
|
||||
|
||||
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
</pre>
|
||||
<pre><code>parseFloat :: Parser LispVal
|
||||
parseFloat = do optional (string "#d")
|
||||
sign <- parseSign
|
||||
whole <- many1 digit
|
||||
char '.'
|
||||
fract <- many1 digit
|
||||
return . Number $ applySign sign (makeFloat whole fract)
|
||||
where makeFloat whole fract = Float . fst . head . readFloat $ whole ++ "." ++ fract</code></pre>
|
||||
|
||||
|
||||
The first 6 lines should be clear. Line 7 simply applies the parsed sign to the parsed number and returns it, delegating most of the work to <code>makeFloat</code>. <code>makeFloat</code> in turn delegates the work to the <code>readFloat</code> library function, extracts the result and constructs a <code>LispNum</code> for it.
|
||||
|
||||
The last step for parsing is to modify <code>parseExpr</code> to try and parse floats.
|
||||
|
||||
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
<strong>10</strong>
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
</pre>
|
||||
<pre><code>-- Integers, floats, characters and atoms can all start with a # so wrap those with try.
|
||||
-- (Left factor the grammar in the future)
|
||||
parseExpr :: Parser LispVal
|
||||
parseExpr = (try parseFloat)
|
||||
<|> (try parseInteger)
|
||||
<|> (try parseChar)
|
||||
<|> parseAtom
|
||||
<|> parseString
|
||||
<|> parseQuoted
|
||||
<|> do char '('
|
||||
x <- (try parseList) <|> parseDottedList
|
||||
char ')'
|
||||
return x
|
||||
<|> parseComment</code></pre>
|
||||
|
||||
|
||||
### Displaying the floats ###
|
||||
|
||||
|
||||
That's it for parsing, now let's provide a way to display these suckers. <code>LispVal</code> is an instance of show, where <code>show</code> = <code>showVal</code> so <code>showVal</code> is our first stop. Remembering that <code>LispVal</code> now has a single <code>Number</code> constructor we modify it accordingly:
|
||||
|
||||
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
</pre>
|
||||
<pre><code>showVal (Number n) = showNum n
|
||||
|
||||
showNum :: LispNum -> String
|
||||
showNum (Integer contents) = show contents
|
||||
showNum (Float contents) = show contents
|
||||
|
||||
instance Show LispNum where show = showNum</code></pre>
|
||||
|
||||
|
||||
One last, and certainly not least, step is to modify <code>eval</code> so that numbers evaluate to themselves.
|
||||
|
||||
|
||||
eval env val@(Number _) = return val
|
||||
|
||||
There's a little more housekeeping to be done such as fixing <code>integer?</code>, <code>number?</code>, implementing <code>float?</code> but I will leave those as an exercise to the reader, or just wait until I share the full code. As it stands now floating point numbers can be parsed and displayed. If you fire up the interpreter and type <code>2.5</code> or <code>-10.88</code> they will be understood. Now try adding them:
|
||||
|
||||
(+ 2.5 1.1)
|
||||
Invalid type: expected integer, found 2.5
|
||||
|
||||
Oops, we don't know how to operate on floats yet!
|
||||
|
||||
### Operating on floats ###
|
||||
|
||||
Parsing was the easy part. Operating on the new floats is not necessarily difficult, but it was more work than I realized it would be. I don't claim that this is the best or the only way to operate on any <code>LispNum</code>, it's just the way I did it and it seems to work. There's a bunch of boilerplate necessary to make <code>LispNum</code> an instance of the required classes, Eq, Num, Real, and Ord. I don't think I have done this properly but for now it works. What is clearly necessary is the code that operates on different types of numbers. I think I've specified sane semantics for coercion. This will be very handy shortly.
|
||||
|
||||
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
<strong>10</strong>
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
<strong>20</strong>
|
||||
21
|
||||
22
|
||||
23
|
||||
24
|
||||
25
|
||||
26
|
||||
27
|
||||
28
|
||||
29
|
||||
<strong>30</strong>
|
||||
31
|
||||
32
|
||||
33
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
<strong>40</strong>
|
||||
41
|
||||
42
|
||||
43
|
||||
44
|
||||
45
|
||||
46
|
||||
47
|
||||
48
|
||||
49
|
||||
<strong>50</strong>
|
||||
51
|
||||
52
|
||||
53
|
||||
54
|
||||
55
|
||||
56
|
||||
57
|
||||
58
|
||||
59
|
||||
<strong>60</strong>
|
||||
61
|
||||
62
|
||||
63
|
||||
64
|
||||
65
|
||||
66
|
||||
67
|
||||
68
|
||||
69
|
||||
<strong>70 </strong>
|
||||
</pre>
|
||||
<pre><code>lispNumEq :: LispNum -> LispNum -> Bool
|
||||
lispNumEq (Integer arg1) (Integer arg2) = arg1 == arg2
|
||||
lispNumEq (Integer arg1) (Float arg2) = (fromInteger arg1) == arg2
|
||||
lispNumEq (Float arg1) (Float arg2) = arg1 == arg2
|
||||
lispNumEq (Float arg1) (Integer arg2) = arg1 == (fromInteger arg2)
|
||||
|
||||
instance Eq LispNum where (==) = lispNumEq
|
||||
|
||||
lispNumPlus :: LispNum -> LispNum -> LispNum
|
||||
lispNumPlus (Integer x) (Integer y) = Integer $ x + y
|
||||
lispNumPlus (Integer x) (Float y) = Float $ (fromInteger x) + y
|
||||
lispNumPlus (Float x) (Float y) = Float $ x + y
|
||||
lispNumPlus (Float x) (Integer y) = Float $ x + (fromInteger y)
|
||||
|
||||
lispNumMinus :: LispNum -> LispNum -> LispNum
|
||||
lispNumMinus (Integer x) (Integer y) = Integer $ x - y
|
||||
lispNumMinus (Integer x) (Float y) = Float $ (fromInteger x) - y
|
||||
lispNumMinus (Float x) (Float y) = Float $ x - y
|
||||
lispNumMinus (Float x) (Integer y) = Float $ x - (fromInteger y)
|
||||
|
||||
lispNumMult :: LispNum -> LispNum -> LispNum
|
||||
lispNumMult (Integer x) (Integer y) = Integer $ x * y
|
||||
lispNumMult (Integer x) (Float y) = Float $ (fromInteger x) * y
|
||||
lispNumMult (Float x) (Float y) = Float $ x * y
|
||||
lispNumMult (Float x) (Integer y) = Float $ x * (fromInteger y)
|
||||
|
||||
lispNumDiv :: LispNum -> LispNum -> LispNum
|
||||
lispNumDiv (Integer x) (Integer y) = Integer $ x `div` y
|
||||
lispNumDiv (Integer x) (Float y) = Float $ (fromInteger x) / y
|
||||
lispNumDiv (Float x) (Float y) = Float $ x / y
|
||||
lispNumDiv (Float x) (Integer y) = Float $ x / (fromInteger y)
|
||||
|
||||
lispNumAbs :: LispNum -> LispNum
|
||||
lispNumAbs (Integer x) = Integer (abs x)
|
||||
lispNumAbs (Float x) = Float (abs x)
|
||||
|
||||
lispNumSignum :: LispNum -> LispNum
|
||||
lispNumSignum (Integer x) = Integer (signum x)
|
||||
lispNumSignum (Float x) = Float (signum x)
|
||||
|
||||
instance Num LispNum where
|
||||
(+) = lispNumPlus
|
||||
(-) = lispNumMinus
|
||||
(*) = lispNumMult
|
||||
abs = lispNumAbs
|
||||
signum = lispNumSignum
|
||||
fromInteger x = Integer x
|
||||
|
||||
|
||||
lispNumToRational :: LispNum -> Rational
|
||||
lispNumToRational (Integer x) = toRational x
|
||||
lispNumToRational (Float x) = toRational x
|
||||
|
||||
instance Real LispNum where
|
||||
toRational = lispNumToRational
|
||||
|
||||
|
||||
lispIntQuotRem :: LispInt -> LispInt -> (LispInt, LispInt)
|
||||
lispIntQuotRem n d = quotRem n d
|
||||
|
||||
lispIntToInteger :: LispInt -> Integer
|
||||
lispIntToInteger x = x
|
||||
|
||||
lispNumLessThanEq :: LispNum -> LispNum -> Bool
|
||||
lispNumLessThanEq (Integer x) (Integer y) = x <= y
|
||||
lispNumLessThanEq (Integer x) (Float y) = (fromInteger x) <= y
|
||||
lispNumLessThanEq (Float x) (Integer y) = x <= (fromInteger y)
|
||||
lispNumLessThanEq (Float x) (Float y) = x <= y
|
||||
|
||||
instance Ord LispNum where (<=) = lispNumLessThanEq</code></pre>
|
||||
|
||||
|
||||
Phew, ok with that out of the way now we can actually extend our operators to work with any type of <code>LispNum</code>. Our Scheme operators are defined using the functions <code>numericBinop</code> and <code>numBoolBinop</code>. First we'll slightly modify our definition of <code>primitives</code>:
|
||||
|
||||
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
<strong>10</strong>
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
</pre>
|
||||
<pre><code>primitives :: [(String, [LispVal] -> ThrowsError LispVal)]
|
||||
primitives = [("+", numericBinop (+)),
|
||||
("-", subtractOp),
|
||||
("*", numericBinop (*)),
|
||||
("/", floatBinop (/)),
|
||||
("mod", integralBinop mod),
|
||||
("quotient", integralBinop quot),
|
||||
("remainder", integralBinop rem),
|
||||
("=", numBoolBinop (==)),
|
||||
("<", numBoolBinop (<)),
|
||||
(">", numBoolBinop (>)),
|
||||
("/=", numBoolBinop (/=)),
|
||||
(">=", numBoolBinop (>=)),
|
||||
("<=", numBoolBinop (<=)),
|
||||
...]</code></pre>
|
||||
|
||||
|
||||
Note that <code>mod</code>, <code>quotient</code>, and <code>remainder</code> are only defined for integers and as such use <code>integralBinop</code>, while division (/) is only defined for floating point numbers using <code>floatBinop</code>. <code>subtractOp</code> is different to support unary usage, e.g. <code>(- 4) => -4</code>, but it uses <code>numericBinop</code> internally when more than 1 argument is given. On to the implementation! First extend <code>unpackNum</code> to work with any <code>LispNum</code>, and provide separate <code>unpackInt</code> and <code>unpackFloat</code> functions to handle both kinds of <code>LispNum</code>.
|
||||
|
||||
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
<strong>10</strong>
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
</pre>
|
||||
<pre><code>unpackNum :: LispVal -> ThrowsError LispNum
|
||||
unpackNum (Number (Integer n)) = return $ Integer n
|
||||
unpackNum (Number (Float n)) = return $ Float n
|
||||
unpackNum notNum = throwError $ TypeMismatch "number" notNum
|
||||
|
||||
unpackInt :: LispVal -> ThrowsError Integer
|
||||
unpackInt (Number (Integer n)) = return n
|
||||
unpackInt (List [n]) = unpackInt n
|
||||
unpackInt notInt = throwError $ TypeMismatch "integer" notInt
|
||||
|
||||
unpackFloat :: LispVal -> ThrowsError Float
|
||||
unpackFloat (Number (Float f)) = return f
|
||||
unpackFloat (Number (Integer f)) = return $ fromInteger f
|
||||
unpackFloat (List [f]) = unpackFloat f
|
||||
unpackFloat notFloat = throwError $ TypeMismatch "float" notFloat</code></pre>
|
||||
|
||||
|
||||
The initial work of separating integers and floats into the <code>LispNum</code> abstraction, and the code I said would be handy shortly, are going to be really handy here. There's relatively no change in <code>numericBinop</code> except for the type signature. <code>integralBinop</code> and <code>floatBinop</code> are just specific versions of the same function. I'm sure there's a nice Haskelly way of doing this with less repetition, and I welcome such corrections.
|
||||
|
||||
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
<strong>10</strong>
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
</pre>
|
||||
<pre><code>numericBinop :: (LispNum -> LispNum -> LispNum) -> [LispVal] -> ThrowsError LispVal
|
||||
numericBinop op singleVal@[_] = throwError $ NumArgs 2 singleVal
|
||||
numericBinop op params = mapM unpackNum params >>= return . Number . foldl1 op
|
||||
|
||||
integralBinop :: (LispInt -> LispInt -> LispInt) -> [LispVal] -> ThrowsError LispVal
|
||||
integralBinop op singleVal@[_] = throwError $ NumArgs 2 singleVal
|
||||
integralBinop op params = mapM unpackInt params >>= return . Number . Integer . foldl1 op
|
||||
|
||||
floatBinop :: (LispFloat -> LispFloat -> LispFloat) -> [LispVal] -> ThrowsError LispVal
|
||||
floatBinop op singleVal@[_] = throwError $ NumArgs 2 singleVal
|
||||
floatBinop op params = mapM unpackFloat params >>= return . Number . Float . foldl1 op
|
||||
|
||||
subtractOp :: [LispVal] -> ThrowsError LispVal
|
||||
subtractOp num@[_] = unpackNum (head num) >>= return . Number . negate
|
||||
subtractOp params = numericBinop (-) params
|
||||
|
||||
numBoolBinop :: (LispNum -> LispNum -> Bool) -> [LispVal] -> ThrowsError LispVal
|
||||
numBoolBinop op params = boolBinop unpackNum op params</code></pre>
|
||||
|
||||
|
||||
That was a bit of work but now ElSchemo supports floating point numbers, and if you're following along then your Scheme might too if I haven't missed any important details!
|
||||
|
||||
|
||||
Next time I'll go over some of the special forms I have added, including short-circuiting <code>and</code> and <code>or</code> forms and the full repetoire of <code>let</code>, <code>let*</code>, and <code>letrec</code>. Stay tuned!
|
||||
|
|
@ -54,27 +54,25 @@ The trickiest part of all this was figuring out how to use the various <code>rea
|
|||
It still takes me some time to knit together meaningful Haskell statements. Tonight I spent said time cobbling together an implementation of <a href="http://schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_sec_4.1.5">cond</a> as a new special form. Have a look at the code. The explanation follows.
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6<tt>
|
||||
</tt>7<tt>
|
||||
</tt>8<tt>
|
||||
</tt>9 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">eval env (List (Atom "cond" : List (Atom "else" : exprs) : [])) =<tt>
|
||||
</tt> liftM last $ mapM (eval env) exprs<tt>
|
||||
</tt>eval env (List (Atom "cond" : List (pred : conseq) : rest)) = <tt>
|
||||
</tt> do result <- eval env $ pred<tt>
|
||||
</tt> case result of<tt>
|
||||
</tt> Bool False -> case rest of<tt>
|
||||
</tt> [] -> return $ List []<tt>
|
||||
</tt> _ -> eval env $ List (Atom "cond" : rest)<tt>
|
||||
</tt> _ -> liftM last $ mapM (eval env) conseq</pre></td>
|
||||
</tr></table>
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
</pre>
|
||||
<pre><code>eval env (List (Atom "cond" : List (Atom "else" : exprs) : [])) =
|
||||
liftM last $ mapM (eval env) exprs
|
||||
eval env (List (Atom "cond" : List (pred : conseq) : rest)) =
|
||||
do result <- eval env $ pred
|
||||
case result of
|
||||
Bool False -> case rest of
|
||||
[] -> return $ List []
|
||||
_ -> eval env $ List (Atom "cond" : rest)
|
||||
_ -> liftM last $ mapM (eval env) conseq</code></pre>
|
||||
|
||||
|
||||
* __Lines 1-2:__ Handle <code>else</code> clauses by evaluating the given expression(s), returning the last result. It must come first or it's overlapped by the next pattern.
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
Things <a href="http://arstechnica.com/news.ars/post/20070625-spying-on-campus-fbi-warns-mit-harvard.html">like this</a> in modern times are surprising. Can't people spot this phony crap for what it is?
|
||||
|
||||
<tt>First they put away the dealers, keep our kids safe and off the streets<br>
|
||||
<code>First they put away the dealers, keep our kids safe and off the streets<br>
|
||||
Then they put away the prostitutes, keep married men cloistered at home<br>
|
||||
Then they shooed away the bums, and they beat and bashed the queers<br>
|
||||
Turned away asylum-seekers, fed us suspicions and fears<br>
|
||||
|
|
@ -26,5 +26,5 @@ And piss, while sucking on a giant pacifier<br>
|
|||
A country of adult infants, a legion of mental midgets<br>
|
||||
A country of adult infants, a country of adult infants<br>
|
||||
All regaining their unconsciousness<br>
|
||||
</tt>
|
||||
</code>
|
||||
—from the song <a href="http://www.nofxwiki.net/w/Lyrics:Regaining_Unconsciousness_%28song%29">Regaining Unconsciousness</a>, by <a href="http://www.nofx.org/">NOFX</a>
|
||||
|
|
@ -4,23 +4,21 @@ Recently I had looked at converting Typo to Mephisto and it seemed pretty painle
|
|||
|
||||
After running that code snippet to fix my tags, I decided to completely ditch categories in favour of tags. I tagged each new Mephisto article with a tag for each Typo category it had previously belonged to. I fired up <code>RAILS_ENV=production script/console</code> and typed something similar to the following:
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6<tt>
|
||||
</tt>7 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">require <span class="s"><span class="dl">'</span><span class="k">converters/base</span><span class="dl">'</span></span><tt>
|
||||
</tt>require <span class="s"><span class="dl">'</span><span class="k">converters/typo</span><span class="dl">'</span></span><tt>
|
||||
</tt>articles = <span class="co">Typo</span>::<span class="co">Article</span>.find(<span class="sy">:all</span>).map {|a| [a, <span class="co">Article</span>.find_by_permalink(a.permalink)] }<tt>
|
||||
</tt>articles.each <span class="r">do</span> |ta, ma|<tt>
|
||||
</tt> <span class="r">next</span> <span class="r">if</span> ma.nil?<tt>
|
||||
</tt> ma.tags << <span class="co">Tag</span>.find_or_create(ta.categories.map(&<span class="sy">:name</span>))<tt>
|
||||
</tt><span class="r">end</span></pre></td>
|
||||
</tr></table>
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
</pre>
|
||||
<pre><code>require <span class="s"><span class="dl">'</span><span class="k">converters/base</span><span class="dl">'</span></span>
|
||||
require <span class="s"><span class="dl">'</span><span class="k">converters/typo</span><span class="dl">'</span></span>
|
||||
articles = <span class="co">Typo</span>::<span class="co">Article</span>.find(<span class="sy">:all</span>).map {|a| [a, <span class="co">Article</span>.find_by_permalink(a.permalink)] }
|
||||
articles.each <span class="r">do</span> |ta, ma|
|
||||
<span class="r">next</span> <span class="r">if</span> ma.nil?
|
||||
ma.tags << <span class="co">Tag</span>.find_or_create(ta.categories.map(&<span class="sy">:name</span>))
|
||||
<span class="r">end</span></code></pre>
|
||||
|
||||
|
||||
When I say something similar I mean exactly that. I just typed that from memory so it may not work, or even be syntactically correct. If any permalinks changed then you'll have to manually add new tags corresponding to old Typo categories. The only case where this bit me was when I had edited the title of an article, in which case the new Mephisto permalink matched the new title while the Typo permalink matched the initial title, whatever it was.
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
This last week I've been getting to know <a href="http://chneukirchen.org/blog/archive/2007/01/announcing-test-spec-0-3-a-bdd-interface-for-test-unit.html">test/spec</a> via <a href="http://errtheblog.com/">err's</a> <a href="http://require.errtheblog.com/plugins/wiki/TestSpecRails">test/spec on rails</a> plugin. I have to say that I really dig this method of testing my code and I look forward to trying out some actual <a href="http://behaviour-driven.org/">BDD</a> in the future.
|
||||
|
||||
I did hit a little snag with functional testing though. The method of declaring which controller to use takes the form:
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre><tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">use_controller <span class="sy">:foo</span></pre></td>
|
||||
</tr></table>
|
||||
|
||||
|
||||
and can be placed in the <code>setup</code> method, like so:
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6<tt>
|
||||
</tt>7<tt>
|
||||
</tt>8<tt>
|
||||
</tt>9<tt>
|
||||
</tt><strong>10</strong><tt>
|
||||
</tt>11<tt>
|
||||
</tt>12<tt>
|
||||
</tt>13<tt>
|
||||
</tt>14<tt>
|
||||
</tt>15 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="c"># in test/functional/sessions_controller_test.rb</span><tt>
|
||||
</tt><tt>
|
||||
</tt>context <span class="s"><span class="dl">"</span><span class="k">A guest</span><span class="dl">"</span></span> <span class="r">do</span><tt>
|
||||
</tt> fixtures <span class="sy">:users</span><tt>
|
||||
</tt><tt>
|
||||
</tt> setup <span class="r">do</span><tt>
|
||||
</tt> use_controller <span class="sy">:sessions</span><tt>
|
||||
</tt> <span class="r">end</span><tt>
|
||||
</tt><tt>
|
||||
</tt> specify <span class="s"><span class="dl">"</span><span class="k">can login</span><span class="dl">"</span></span> <span class="r">do</span><tt>
|
||||
</tt> post <span class="sy">:create</span>, <span class="sy">:username</span> => <span class="s"><span class="dl">'</span><span class="k">sjs</span><span class="dl">'</span></span>, <span class="sy">:password</span> => <span class="s"><span class="dl">'</span><span class="k">blah</span><span class="dl">'</span></span><tt>
|
||||
</tt> response.should.redirect_to user_url(users(<span class="sy">:sjs</span>))<tt>
|
||||
</tt> ...<tt>
|
||||
</tt> <span class="r">end</span><tt>
|
||||
</tt><span class="r">end</span></pre></td>
|
||||
</tr></table>
|
||||
|
||||
|
||||
This is great and the test will work. But let's say that I have another controller that guests can access:
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6<tt>
|
||||
</tt>7<tt>
|
||||
</tt>8<tt>
|
||||
</tt>9<tt>
|
||||
</tt><strong>10</strong><tt>
|
||||
</tt>11<tt>
|
||||
</tt>12<tt>
|
||||
</tt>13 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="c"># in test/functional/foo_controller_test.rb</span><tt>
|
||||
</tt><tt>
|
||||
</tt>context <span class="s"><span class="dl">"</span><span class="k">A guest</span><span class="dl">"</span></span> <span class="r">do</span><tt>
|
||||
</tt> setup <span class="r">do</span><tt>
|
||||
</tt> use_controller <span class="sy">:foo</span><tt>
|
||||
</tt> <span class="r">end</span><tt>
|
||||
</tt><tt>
|
||||
</tt> specify <span class="s"><span class="dl">"</span><span class="k">can do foo stuff</span><span class="dl">"</span></span> <span class="r">do</span><tt>
|
||||
</tt> get <span class="sy">:fooriffic</span><tt>
|
||||
</tt> status.should.be <span class="sy">:success</span><tt>
|
||||
</tt> ...<tt>
|
||||
</tt> <span class="r">end</span><tt>
|
||||
</tt><span class="r">end</span></pre></td>
|
||||
</tr></table>
|
||||
|
||||
|
||||
This test will pass on its own as well, which is what really tripped me up. When I ran my tests individually as I wrote them, they passed. When I ran <code>rake test:functionals</code> this morning and saw over a dozen failures and errors I was pretty alarmed. Then I looked at the errors and was thoroughly confused. Of course the action <strong>fooriffic</strong> can't be found in <strong>SessionsController</strong>, it lives in <strong>FooController</strong> and that's the controller I said to use! What gives?!
|
||||
|
||||
The problem is that test/spec only creates one context with a specific name, and re-uses that context on subsequent tests using the same context name. The various <code>setup</code> methods are all added to a list and each one is executed, not just the one in the same <code>context</code> block as the specs. I can see how that's useful, but for me right now it's just a hinderance as I'd have to uniquely name each context. "Another guest" just looks strange in a file by itself, and I want my tests to work with my brain not against it.
|
||||
|
||||
My solution was to just create a new context each time and re-use nothing. Only 2 lines in test/spec need to be changed to achieve this, but I'm not sure if what I'm doing is a bad idea. My tests pass and right now that's basically all I care about though.
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
This last week I've been getting to know <a href="http://chneukirchen.org/blog/archive/2007/01/announcing-test-spec-0-3-a-bdd-interface-for-test-unit.html">test/spec</a> via <a href="http://errtheblog.com/">err's</a> <a href="http://require.errtheblog.com/plugins/wiki/TestSpecRails">test/spec on rails</a> plugin. I have to say that I really dig this method of testing my code and I look forward to trying out some actual <a href="http://behaviour-driven.org/">BDD</a> in the future.
|
||||
|
||||
I did hit a little snag with functional testing though. The method of declaring which controller to use takes the form:
|
||||
|
||||
|
||||
<pre class="line-numbers">
|
||||
</pre>
|
||||
<pre><code>use_controller <span class="sy">:foo</span></code></pre>
|
||||
|
||||
|
||||
and can be placed in the <code>setup</code> method, like so:
|
||||
|
||||
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
<strong>10</strong>
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
</pre>
|
||||
<pre><code><span class="c"># in test/functional/sessions_controller_test.rb</span>
|
||||
|
||||
context <span class="s"><span class="dl">"</span><span class="k">A guest</span><span class="dl">"</span></span> <span class="r">do</span>
|
||||
fixtures <span class="sy">:users</span>
|
||||
|
||||
setup <span class="r">do</span>
|
||||
use_controller <span class="sy">:sessions</span>
|
||||
<span class="r">end</span>
|
||||
|
||||
specify <span class="s"><span class="dl">"</span><span class="k">can login</span><span class="dl">"</span></span> <span class="r">do</span>
|
||||
post <span class="sy">:create</span>, <span class="sy">:username</span> => <span class="s"><span class="dl">'</span><span class="k">sjs</span><span class="dl">'</span></span>, <span class="sy">:password</span> => <span class="s"><span class="dl">'</span><span class="k">blah</span><span class="dl">'</span></span>
|
||||
response.should.redirect_to user_url(users(<span class="sy">:sjs</span>))
|
||||
...
|
||||
<span class="r">end</span>
|
||||
<span class="r">end</span></code></pre>
|
||||
|
||||
|
||||
This is great and the test will work. But let's say that I have another controller that guests can access:
|
||||
|
||||
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
<strong>10</strong>
|
||||
11
|
||||
12
|
||||
13
|
||||
</pre>
|
||||
<pre><code><span class="c"># in test/functional/foo_controller_test.rb</span>
|
||||
|
||||
context <span class="s"><span class="dl">"</span><span class="k">A guest</span><span class="dl">"</span></span> <span class="r">do</span>
|
||||
setup <span class="r">do</span>
|
||||
use_controller <span class="sy">:foo</span>
|
||||
<span class="r">end</span>
|
||||
|
||||
specify <span class="s"><span class="dl">"</span><span class="k">can do foo stuff</span><span class="dl">"</span></span> <span class="r">do</span>
|
||||
get <span class="sy">:fooriffic</span>
|
||||
status.should.be <span class="sy">:success</span>
|
||||
...
|
||||
<span class="r">end</span>
|
||||
<span class="r">end</span></code></pre>
|
||||
|
||||
|
||||
This test will pass on its own as well, which is what really tripped me up. When I ran my tests individually as I wrote them, they passed. When I ran <code>rake test:functionals</code> this morning and saw over a dozen failures and errors I was pretty alarmed. Then I looked at the errors and was thoroughly confused. Of course the action <strong>fooriffic</strong> can't be found in <strong>SessionsController</strong>, it lives in <strong>FooController</strong> and that's the controller I said to use! What gives?!
|
||||
|
||||
The problem is that test/spec only creates one context with a specific name, and re-uses that context on subsequent tests using the same context name. The various <code>setup</code> methods are all added to a list and each one is executed, not just the one in the same <code>context</code> block as the specs. I can see how that's useful, but for me right now it's just a hinderance as I'd have to uniquely name each context. "Another guest" just looks strange in a file by itself, and I want my tests to work with my brain not against it.
|
||||
|
||||
My solution was to just create a new context each time and re-use nothing. Only 2 lines in test/spec need to be changed to achieve this, but I'm not sure if what I'm doing is a bad idea. My tests pass and right now that's basically all I care about though.
|
||||
|
|
@ -38,25 +38,23 @@ concise language. My explanations may be redundant because of this.
|
|||
### lispAnd ###
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6<tt>
|
||||
</tt>7<tt>
|
||||
</tt>8 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">lispAnd :: Env -> [LispVal] -> IOThrowsError LispVal<tt>
|
||||
</tt>lispAnd env [] = return $ Bool True<tt>
|
||||
</tt>lispAnd env [pred] = eval env pred<tt>
|
||||
</tt>lispAnd env (pred:rest) = do<tt>
|
||||
</tt> result <- eval env pred<tt>
|
||||
</tt> case result of<tt>
|
||||
</tt> Bool False -> return result<tt>
|
||||
</tt> _ -> lispAnd env rest</pre></td>
|
||||
</tr></table>
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
</pre>
|
||||
<pre><code>lispAnd :: Env -> [LispVal] -> IOThrowsError LispVal
|
||||
lispAnd env [] = return $ Bool True
|
||||
lispAnd env [pred] = eval env pred
|
||||
lispAnd env (pred:rest) = do
|
||||
result <- eval env pred
|
||||
case result of
|
||||
Bool False -> return result
|
||||
_ -> lispAnd env rest</code></pre>
|
||||
|
||||
|
||||
Starting with the trivial case, <code>and</code> returns <code>#t</code> with zero
|
||||
|
|
@ -79,25 +77,23 @@ just complicates things but it's a viable solution.
|
|||
Predictably this is quite similar to <code>lispAnd</code>.
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6<tt>
|
||||
</tt>7<tt>
|
||||
</tt>8 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">lispOr :: Env -> [LispVal] -> IOThrowsError LispVal<tt>
|
||||
</tt>lispOr env [] = return $ Bool False<tt>
|
||||
</tt>lispOr env [pred] = eval env pred<tt>
|
||||
</tt>lispOr env (pred:rest) = do<tt>
|
||||
</tt> result <- eval env pred<tt>
|
||||
</tt> case result of<tt>
|
||||
</tt> Bool False -> lispOr env rest<tt>
|
||||
</tt> _ -> return result</pre></td>
|
||||
</tr></table>
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
</pre>
|
||||
<pre><code>lispOr :: Env -> [LispVal] -> IOThrowsError LispVal
|
||||
lispOr env [] = return $ Bool False
|
||||
lispOr env [pred] = eval env pred
|
||||
lispOr env (pred:rest) = do
|
||||
result <- eval env pred
|
||||
case result of
|
||||
Bool False -> lispOr env rest
|
||||
_ -> return result</code></pre>
|
||||
|
||||
|
||||
With no arguments <code>lispOr</code> returns <code>#f</code>, and with one argument it
|
||||
|
|
@ -114,13 +110,11 @@ ElSchemo. It maps a list of expressions to their values by evaluating
|
|||
each one in the given environment.
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">evalExprs :: Env -> [LispVal] -> IOThrowsError [LispVal]<tt>
|
||||
</tt>evalExprs env exprs = mapM (eval env) exprs</pre></td>
|
||||
</tr></table>
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
</pre>
|
||||
<pre><code>evalExprs :: Env -> [LispVal] -> IOThrowsError [LispVal]
|
||||
evalExprs env exprs = mapM (eval env) exprs</code></pre>
|
||||
|
||||
|
||||
### lispCond ###
|
||||
|
|
@ -128,21 +122,19 @@ each one in the given environment.
|
|||
Again, <code>lispCond</code> has the same type as <code>eval</code>.
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3<tt>
|
||||
</tt>4<tt>
|
||||
</tt>5<tt>
|
||||
</tt>6 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">lispCond :: Env -> [LispVal] -> IOThrowsError LispVal<tt>
|
||||
</tt>lispCond env (List (pred:conseq) : rest) = do<tt>
|
||||
</tt> result <- eval env pred<tt>
|
||||
</tt> case result of<tt>
|
||||
</tt> Bool False -> if null rest then return result else lispCond env rest<tt>
|
||||
</tt> _ -> liftM last $ evalExprs env conseq</pre></td>
|
||||
</tr></table>
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
</pre>
|
||||
<pre><code>lispCond :: Env -> [LispVal] -> IOThrowsError LispVal
|
||||
lispCond env (List (pred:conseq) : rest) = do
|
||||
result <- eval env pred
|
||||
case result of
|
||||
Bool False -> if null rest then return result else lispCond env rest
|
||||
_ -> liftM last $ evalExprs env conseq</code></pre>
|
||||
|
||||
|
||||
Unlike Lisp – which uses a predicate of <code>T</code> (true) – Scheme uses a
|
||||
|
|
@ -165,15 +157,13 @@ expressions and return the value of the last one.
|
|||
Now all that's left is to hook up the new functions in <code>eval</code>.
|
||||
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1<tt>
|
||||
</tt>2<tt>
|
||||
</tt>3 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">eval env (List (Atom "and" : params)) = lispAnd env params<tt>
|
||||
</tt>eval env (List (Atom "or" : params)) = lispOr env params<tt>
|
||||
</tt>eval env (List (Atom "cond" : params)) = lispCond env params</pre></td>
|
||||
</tr></table>
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
</pre>
|
||||
<pre><code>eval env (List (Atom "and" : params)) = lispAnd env params
|
||||
eval env (List (Atom "or" : params)) = lispOr env params
|
||||
eval env (List (Atom "cond" : params)) = lispCond env params</code></pre>
|
||||
|
||||
|
||||
You could, of course, throw the entire definitions in <code>eval</code> itself but <code>eval</code> is big
|
||||
|
|
@ -13,30 +13,29 @@ Now that you know what to do I'll give you what you probably wanted at the begin
|
|||
|
||||
↓ <a href="/f/gtkpod-aac-fix.sh">gtkpod-aac-fix.sh</a>
|
||||
|
||||
<table class="code"><tr>
|
||||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre style="color: #888">1 <tt>
|
||||
</tt>2 <tt>
|
||||
</tt>3 <tt>
|
||||
</tt>4 <tt>
|
||||
</tt>5 <tt>
|
||||
</tt>6 <tt>
|
||||
</tt>7 <tt>
|
||||
</tt>8 <tt>
|
||||
</tt>9 <tt>
|
||||
</tt>10 <tt>
|
||||
</tt>11 <tt>
|
||||
</tt>12 <tt>
|
||||
</tt></pre></td>
|
||||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">mkdir /tmp/gtkpod-fix<tt>
|
||||
</tt>cd /tmp/gtkpod-fix<tt>
|
||||
</tt>wget http://ftp.uni-kl.de/debian-multimedia/pool/main/libm/libmpeg4ip/libmp4v2-0_1.5.0.1-0.3_amd64.deb<tt>
|
||||
</tt>wget http://ftp.uni-kl.de/debian-multimedia/pool/main/libm/libmpeg4ip/libmp4v2-dev_1.5.0.1-0.3_amd64.deb<tt>
|
||||
</tt>wget http://ftp.uni-kl.de/debian-multimedia/pool/main/libm/libmpeg4ip/libmpeg4ip-0_1.5.0.1-0.3_amd64.deb<tt>
|
||||
</tt>wget http://ftp.uni-kl.de/debian-multimedia/pool/main/libm/libmpeg4ip/libmpeg4ip-dev_1.5.0.1-0.3_amd64.deb<tt>
|
||||
</tt>for f in *.deb; do sudo gdebi -n "$f"; done<tt>
|
||||
</tt>svn co https://gtkpod.svn.sourceforge.net/svnroot/gtkpod/gtkpod/trunk gtkpod<tt>
|
||||
</tt>cd gtkpod<tt>
|
||||
</tt>./autogen.sh --with-mp4v2 && make && sudo make install<tt>
|
||||
</tt>cd<tt>
|
||||
</tt>rm -rf /tmp/gtkpod-fix</pre></td>
|
||||
</tr></table>
|
||||
<pre class="line-numbers">1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
</pre>
|
||||
|
||||
<pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><code>mkdir /tmp/gtkpod-fix
|
||||
cd /tmp/gtkpod-fix
|
||||
wget http://ftp.uni-kl.de/debian-multimedia/pool/main/libm/libmpeg4ip/libmp4v2-0_1.5.0.1-0.3_amd64.deb
|
||||
wget http://ftp.uni-kl.de/debian-multimedia/pool/main/libm/libmpeg4ip/libmp4v2-dev_1.5.0.1-0.3_amd64.deb
|
||||
wget http://ftp.uni-kl.de/debian-multimedia/pool/main/libm/libmpeg4ip/libmpeg4ip-0_1.5.0.1-0.3_amd64.deb
|
||||
wget http://ftp.uni-kl.de/debian-multimedia/pool/main/libm/libmpeg4ip/libmpeg4ip-dev_1.5.0.1-0.3_amd64.deb
|
||||
for f in *.deb; do sudo gdebi -n "$f"; done
|
||||
svn co https://gtkpod.svn.sourceforge.net/svnroot/gtkpod/gtkpod/trunk gtkpod
|
||||
cd gtkpod
|
||||
./autogen.sh --with-mp4v2 && make && sudo make install
|
||||
cd
|
||||
rm -rf /tmp/gtkpod-fix</code></pre>
|
||||
|
|
@ -54,7 +54,7 @@ diagram showing the layout of such a file:</p>
|
|||
<p>The Mach header contains the architecture (cpu type), the type of
|
||||
file (object in our case), and the number of segments. There is more
|
||||
to it but that's about all we care about. To see exactly what's in a
|
||||
Mach header fire up a shell and type <tt>otool -h /bin/zsh</tt> (on a
|
||||
Mach header fire up a shell and type <code>otool -h /bin/zsh</code> (on a
|
||||
Mac).</p>
|
||||
|
||||
<p>Using
|
||||
|
|
@ -122,18 +122,18 @@ constants as well.</p>
|
|||
<h2>Looking at real Mach-O files</h2>
|
||||
|
||||
<p>To see the segments and sections of an object file, run
|
||||
<tt>otool -l /usr/lib/crt1.o</tt>. <b>-l</b> is for load commands.
|
||||
<code>otool -l /usr/lib/crt1.o</code>. <b>-l</b> is for load commands.
|
||||
If you want to see why we stick to generating object files instead of
|
||||
executables run <tt>otool -l /bin/zsh</tt>. They are complicated
|
||||
executables run <code>otool -l /bin/zsh</code>. They are complicated
|
||||
beasts.</p>
|
||||
|
||||
<p>If you want to see the actual data for a section otool provides a
|
||||
couple of ways to do this. The first is to use
|
||||
<tt>otool -d <segment> <section></tt> for an arbitrary
|
||||
<code>otool -d <segment> <section></code> for an arbitrary
|
||||
section. To see the contents of a well-known section, such as __text
|
||||
in the __TEXT segment, use <tt>otool -t /usr/bin/true</tt>. You can
|
||||
in the __TEXT segment, use <code>otool -t /usr/bin/true</code>. You can
|
||||
also disassemble the __text section with
|
||||
<tt>otool -tv /usr/bin/true</tt>.</p>
|
||||
<code>otool -tv /usr/bin/true</code>.</p>
|
||||
|
||||
<p>You'll get to know otool quite well if you work with Mach-O.</p>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue