Skip to content

Commit

Permalink
initial commit of the mystery language
Browse files Browse the repository at this point in the history
  • Loading branch information
jashkenas committed Dec 13, 2009
0 parents commit 8e9d637
Show file tree
Hide file tree
Showing 10 changed files with 2,339 additions and 0 deletions.
30 changes: 30 additions & 0 deletions SYNTAX
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Every line is an expression. Multiple expressions on a single line can be
separated by a ";" character.

NUM: 1
1.0

STRING: "hello"
'hello'

OBJECT: {one : 1, two : 2}

ARRAY: [1, 2, 3]

CODE: a, b => a * b.

IF: return x if x > 1

if (x > 1) return x

ASSIGN: a : b

LOGICAL: x && y
x and y
x || y
x or y





40 changes: 40 additions & 0 deletions code.jaa
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# I'm a comment and I'm ok.
# Return
# Case

square: x => x * x.

sum: x, y => x + y.

odd: x => x % 2 is 0.

even: x => x % 2 aint 0.

object_literal: {one: 1, two: 2, three: 3}

multiline_object: {
pi: 3.14159
list: [1, 2, 3, 4]
three: 3
inner_obj: {
freedom: => _.freedom().
}
}

run_loop: =>
fire_events(e => e.stopPropagation().)
listen()
wait().

if submarine.shields_up
full_speed_ahead()
weapons.fire_torpedos()
else
run_away().

eldest: if 25 > 21 then liz else marge.





76 changes: 76 additions & 0 deletions documents.jaa
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Document Model
dc.model.Document: dc.Model.extend({

constructor : attributes => this.base(attributes).

# For display, show either the highlighted search results, or the summary,
# if no highlights are available.
# The import process will take care of this in the future, but the inline
# version of the summary has all runs of whitespace squeezed out.
displaySummary : =>
text: this.get('highlight') or this.get('summary')
text ? text.replace(/\s+/g, ' ') else ''

# Return a list of the document's metadata. Think about caching this on the
# document by binding to Metadata, instead of on-the-fly.
metadata : =>
docId : this.id
_.select(Metadata.models(), (m =>
_.any(m.get('instances'), (i =>
i.document_id == docId
))
))

bookmark : pageNumber =>
bookmark : new dc.model.Bookmark({title : this.get('title'), page_number : pageNumber, document_id : this.id})
Bookmarks.create(bookmark)

# Inspect.
toString : =>
'Document ' + this.id + ' "' + this.get('title') + '"'

})

# Document Set
dc.model.DocumentSet : dc.model.RESTfulSet.extend({

resource : 'documents'

SELECTION_CHANGED : 'documents:selection_changed'

constructor : options =>
this.base(options)
_.bindAll(this, 'downloadSelectedViewers', 'downloadSelectedPDF', 'downloadSelectedFullText')

selected : => _.select(this.models(), m => m.get('selected'))

selectedIds : => _.pluck(this.selected(), 'id')

countSelected : => this.selected().length

downloadSelectedViewers : =>
dc.app.download('/download/' + this.selectedIds().join('/') + '/document_viewer.zip');

downloadSelectedPDF : =>
return window.open(this.selected()[0].get('pdf_url')) if this.countSelected() <= 1
dc.app.download('/download/' + this.selectedIds().join('/') + '/document_pdfs.zip')

downloadSelectedFullText : =>
return window.open(this.selected()[0].get('full_text_url')) if this.countSelected() <= 1
dc.app.download('/download/' + this.selectedIds().join('/') + '/document_text.zip')

# We override "_onModelEvent" to fire selection changed events when documents
# change their selected state.
_onModelEvent : e, model =>
this.base(e, model)
fire : (e == dc.Model.CHANGED and model.hasChanged('selected'))
_.defer(_(this.fire).bind(this, this.SELECTION_CHANGED, this)) if fire
}

})

# The main set of Documents, used by the search tab.
window.Documents : new dc.model.DocumentSet()

# The set of documents that is used to look at a particular label.
dc.app.LabeledDocuments : new dc.model.DocumentSet()
206 changes: 206 additions & 0 deletions grammar.y
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
class Parser

# Declare tokens produced by the lexer
token IF ELSE THEN
token NEWLINE
token NUMBER
token STRING
token TRUE FALSE NULL
token IDENTIFIER PROPERTY_ACCESS
token CODE

prechigh
nonassoc UMINUS NOT '!'
left '*' '/' '%'
left '+' '-'
left '<=' '<' '>' '>='
right '==' '!=' IS AINT
left '&&' '||' AND OR
right '-=' '+=' '/=' '*='
preclow

rule
# All rules are declared in this format:
#
# RuleName:
# OtherRule TOKEN AnotherRule { code to run when this matches }
# | OtherRule { ... }
# ;
#
# In the code section (inside the {...} on the right):
# - Assign to "result" the value returned by the rule.
# - Use val[index of expression] to reference expressions on the left.


# All parsing will end in this rule, being the trunk of the AST.
Root:
/* nothing */ { result = Nodes.new([]) }
| Expressions { result = val[0] }
;

# Any list of expressions or method body, seperated by line breaks.
Expressions:
Expression { result = Nodes.new(val) }
| Expressions Terminator Expression { result = val[0] << val[2] }
| Expressions Terminator { result = Nodes.new([val[0]]) }
| Terminator Expressions { result = Nodes.new([val[1]]) }
;

# All types of expressions in our language
Expression:
Literal
| Variable
| Call
| Assign
| Object
| Code
| Operation
| Array
| If
;

# All tokens that can terminate an expression
Terminator:
"\n"
| ";"
;

# All hard-coded values
Literal:
NUMBER { result = LiteralNode.new(val[0]) }
| STRING { result = LiteralNode.new(val[0]) }
| TRUE { result = LiteralNode.new(true) }
| FALSE { result = LiteralNode.new(false) }
| NULL { result = LiteralNode.new(nil) }
;

# Assign to a variable
Assign:
Variable ":" Expression { result = AssignNode.new(val[0], val[2]) }
;

# Assignment within an object literal.
AssignObj:
IDENTIFIER ":" Expression { result = AssignNode.new(val[0], val[2], :object) }
;

# Arithmetic and logical operators
# For Ruby's Operator precedence, see:
# https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html
Operation:
'!' Expression { result = OpNode.new(val[0], val[1]) }
| '-' Expression = UMINUS { result = OpNode.new(val[0], val[1]) }
| NOT Expression { result = OpNode.new(val[0], val[1]) }
| Expression '*' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '/' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '%' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '+' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '-' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '<=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '<' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '>' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '>=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '==' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '!=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression IS Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression AINT Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '&&' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '||' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression AND Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression OR Expression { result = OpNode.new(val[1], val[0], val[2]) }
# Add ternary?
| Expression '-=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '+=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '/=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '*=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
# Add ||= &&=
;
# Method definition
Code:
"=>" Expressions "." { result = CodeNode.new([], val[1]) }
| ParamList
"=>" Expressions "." { result = CodeNode.new(val[0], val[2]) }
;
ParamList:
/* nothing */ { result = [] }
| IDENTIFIER { result = val }
| ParamList "," IDENTIFIER { result = val[0] << val[2] }
;
Variable:
IDENTIFIER { result = VariableNode.new(val) }
| Variable PROPERTY_ACCESS IDENTIFIER { result = val[0] << val[2] }
;
Object:
"{" "}" { result = ObjectNode.new([]) }
| "{" AssignList "}" { result = ObjectNode.new(val[1]) }
| "{" Terminator AssignList
Terminator "}" { result = ObjectNode.new(val[2]) }
;
AssignList:
/* nothing */ { result = []}
| AssignObj { result = val }
| AssignList "," AssignObj { result = val[0] << val[2] }
| AssignList Terminator AssignObj { result = val[0] << val[2] }
;
# A method call.
Call:
Variable "(" ArgList ")" { result = CallNode.new(val[0], val[2]) }
;
# An Array.
Array:
"[" ArgList "]" { result = ArrayNode.new(val[1]) }
;
# A list of arguments to a method call.
ArgList:
/* nothing */ { result = [] }
| Expression { result = val }
| ArgList "," Expression { result = val[0] << val[2] }
;
If:
IF Expression
THEN Expression "." { result = TernaryNode.new(val[1], val[3]) }
| IF Expression Terminator
Expressions "." { result = IfNode.new(val[1], val[3]) }
| IF Expression
THEN Expression
ELSE Expression "." { result = TernaryNode.new(val[1], val[3], val[5]) }
| IF Expression Terminator
Expressions Terminator
ELSE Expressions "." { result = IfNode.new(val[1], val[3], val[6]) }
;
end
---- header
require "lexer"
require "nodes"
---- inner
def parse(code, show_tokens=false)
# @yydebug = true
@tokens = Lexer.new.tokenize(code)
puts @tokens.inspect if show_tokens
do_parse
end
def next_token
@tokens.shift
end

7 comments on commit 8e9d637

@amrnt
Copy link

@amrnt amrnt commented on 8e9d637 Jun 17, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh my god! this is an epic commit!!
I know coffee was built initially on Ruby — but didn't know that the extension was .jaa! 😆

@jashkenas
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know, so embarrassing ;) Didn't have any sort of name for the language, and the first three letters that popped into my head were initials.

@vendethiel
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I knew about the ruby part, the syntax part, but I certainly didn't know about that extension either !

@michaelficarra
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty glad we don't have an aint operator any longer.

@tiye
Copy link

@tiye tiye commented on 8e9d637 Jun 17, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't believe it was CoffeeScript:

sum: x, y => x + y.

@davidchambers
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty glad we don't have an aint operator any longer.

say it aint so

@amrnt
Copy link

@amrnt amrnt commented on 8e9d637 Jun 17, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jashkenas: here's a good guess for .jaa 😉

%w(awesome artistic javascript).collect{|s| s[0]}.join.reverse

Please sign in to comment.