Skip to content

Commit

Permalink
Merge pull request #9 from kenniaa/mutability
Browse files Browse the repository at this point in the history
Mutability
  • Loading branch information
mbStavola authored Nov 1, 2020
2 parents d24259f + 563977b commit 45a705f
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 37 deletions.
15 changes: 11 additions & 4 deletions SLY.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ Comments start with `#` and ignore the rest of the line
You can define variables with Sly, the syntax is pretty simple:

```
variableName = 1;
let variableName = 1;
mut otherVarName = "f";
```

A variable declaration is composed of three parts: mutability binding, identifier, and an initial value.

A variable can either be bound as immutable (`let`) or mutable (`mut`). Immutable variables cannot be reassigned after initialization whereas mutables ones can.

The identifier for the variable must start with a letter but can contain any alphanumeric character subsequently.

The possible value types for a variable will be outlined in the Data Types section.
Expand All @@ -23,11 +28,13 @@ All variable declarations must end with a semicolon.
You can use a variable in an attribute declaration or possibly in another variable declaration.

```
variable1 = "hello";
variable2 = variable1;
let variable1 = "hello";
let variable2 = variable1;
```

Variables are global and are not scoped to slides or blocks. They will be overwritten on reassignment!
Variables are global and are not scoped to slides or blocks.

Variables may be shadowed by redeclaring them.

## Attribute Declaration

Expand Down
6 changes: 3 additions & 3 deletions examples/basic.sly
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
# either HTML, PDF, or (eventually) PPT

# Setup our color palette
coolGray = (26, 83, 92);
paleGreen = (247, 255, 247);
tealBlue = (78, 205, 196);
let coolGray = (26, 83, 92);
mut paleGreen = (247, 255, 247);
let tealBlue = (78, 205, 196);

# Set the default styles for the rest
# of the presentation
Expand Down
117 changes: 100 additions & 17 deletions pkg/lang/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ import (
"github.com/mbStavola/slydes/pkg/types"
)

var reservedNames = map[string]bool{
"backgroundColor": true,
"justify": true,
"font": true,
"fontColor": true,
"fontSize": true,
}

type Compiler interface {
Compile(statements []Statement) (types.Show, error)
}
Expand Down Expand Up @@ -41,13 +49,13 @@ type compilationState struct {
show types.Show
slide types.Slide
block types.Block
variables map[string]interface{}
variables map[string]variableValue
macros map[string][]Statement
}

func newCompilationState() compilationState {
show := types.NewShow()
variables := make(map[string]interface{})
variables := make(map[string]variableValue)
macros := make(map[string][]Statement)

var slide = types.NewSlide()
Expand Down Expand Up @@ -86,17 +94,54 @@ func (cs *compilationState) processStatement(statement Statement) error {
case WordBlock:
cs.block.Words = statement.data.(string)

break
case VariableDeclaration:
variable := statement.data.(VariableDeclStatement)
if reservedNames[variable.name] {
return tokenErrorInfo(statement.token, compilation, "cannot declare using a reserved name")
}

value := variableValue{isMutable: variable.isMutable}

switch data := variable.value.(type) {
case uint8, string, ColorLiteral:
value.value = data

break
case VariableReference:
dereferenced, err := cs.dereferenceVariable(statement.token, data.reference)
if err != nil {
return err
}

value.value = dereferenced
}

cs.variables[variable.name] = value

break
case VariableAssignment:
variable := statement.data.(VariableStatement)

switch value := variable.value.(type) {
value, ok := cs.variables[variable.name]
if !ok {
return tokenErrorInfo(statement.token, compilation, "cannot assign to undeclared variable")
} else if !value.isMutable {
return tokenErrorInfo(statement.token, compilation, "cannot assign to an immutable binding")
}

switch data := variable.value.(type) {
case uint8, string, ColorLiteral:
cs.variables[variable.name] = value
value.value = data

break
case VariableReference:
cs.variables[variable.name] = cs.variables[value.reference]
dereferenced, err := cs.dereferenceVariable(statement.token, data.reference)
if err != nil {
return err
}

value.value = dereferenced
}

break
Expand All @@ -107,7 +152,12 @@ func (cs *compilationState) processStatement(statement Statement) error {
case "backgroundColor":
switch value := attribute.value.(type) {
case VariableReference:
c, err := colorFromLiteral(statement.token, cs.variables[value.reference])
val, err := cs.dereferenceVariable(statement.token, value.reference)
if err != nil {
return err
}

c, err := colorFromLiteral(statement.token, val)
if err != nil {
return err
}
Expand Down Expand Up @@ -149,12 +199,16 @@ func (cs *compilationState) processStatement(statement Statement) error {
case "font":
switch value := attribute.value.(type) {
case VariableReference:
val := cs.variables[value.reference]
val, err := cs.dereferenceVariable(statement.token, value.reference)
if err != nil {
return err
}

switch val := val.(type) {
case string:
cs.block.Style.Font = val
default:
return tokenErrorInfo(statement.token, "Font attribute must be a string")
return tokenErrorInfo(statement.token, compilation, "Font attribute must be a string")
}

break
Expand All @@ -166,7 +220,12 @@ func (cs *compilationState) processStatement(statement Statement) error {
case "fontColor":
switch value := attribute.value.(type) {
case VariableReference:
c, err := colorFromLiteral(statement.token, cs.variables[value.reference])
val, err := cs.dereferenceVariable(statement.token, value.reference)
if err != nil {
return err
}

c, err := colorFromLiteral(statement.token, val)
if err != nil {
return err
}
Expand All @@ -187,9 +246,14 @@ func (cs *compilationState) processStatement(statement Statement) error {
case "fontSize":
switch value := attribute.value.(type) {
case VariableReference:
size, ok := cs.variables[value.reference].(uint8)
val, err := cs.dereferenceVariable(statement.token, value.reference)
if err != nil {
return err
}

size, ok := val.(uint8)
if !ok {
return tokenErrorInfo(statement.token, "Font size attribute must be an integer")
return tokenErrorInfo(statement.token, compilation, "Font size attribute must be an integer")
}

cs.block.Style.Size = size
Expand All @@ -200,10 +264,10 @@ func (cs *compilationState) processStatement(statement Statement) error {

break
default:
return tokenErrorInfo(statement.token, "Font size attribute must be an integer")
return tokenErrorInfo(statement.token, compilation, "Font size attribute must be an integer")
}
default:
return tokenErrorInfo(statement.token, "Unrecognized attribute")
return tokenErrorInfo(statement.token, compilation, "Unrecognized attribute")
}

break
Expand All @@ -216,7 +280,7 @@ func (cs *compilationState) processStatement(statement Statement) error {
macroInvocation := statement.data.(MacroInvocation)
macro, ok := cs.macros[macroInvocation.reference]
if !ok {
return tokenErrorInfo(statement.token, "Macro not defined")
return tokenErrorInfo(statement.token, compilation, "Macro not defined")
}

for _, statement := range macro {
Expand All @@ -229,12 +293,30 @@ func (cs *compilationState) processStatement(statement Statement) error {
return nil
}

func (cs *compilationState) dereferenceVariable(token Token, name string) (interface{}, error) {
if reservedNames[name] {
return nil, tokenErrorInfo(token, compilation, "cannot dereference a reserved name")
}

value, ok := cs.variables[name]
if !ok {
return nil, tokenErrorInfo(token, compilation, "variable must be initialized before dereference")
}

return value.value, nil
}

func (cs *compilationState) finalizeCompilation() types.Show {
cs.slide.Blocks = append(cs.slide.Blocks, cs.block)
cs.show.Slides = append(cs.show.Slides, cs.slide)
return cs.show
}

type variableValue struct {
isMutable bool
value interface{}
}

func justificationFromLiteral(token Token, value interface{}) (types.Justification, error) {
switch value := value.(type) {
case string:
Expand All @@ -249,7 +331,7 @@ func justificationFromLiteral(token Token, value interface{}) (types.Justificati
}

message := "Justification attribute must be either 'left', 'right', or 'center'"
return types.Left, tokenErrorInfo(token, message)
return types.Left, tokenErrorInfo(token, compilation, message)
}

func colorFromLiteral(token Token, value interface{}) (color.Color, error) {
Expand Down Expand Up @@ -283,7 +365,7 @@ func colorFromLiteral(token Token, value interface{}) (color.Color, error) {
}, nil
default:
message := fmt.Sprintf("Unsupported color '%s'", value)
return nil, tokenErrorInfo(token, message)
return nil, tokenErrorInfo(token, compilation, message)
}
case ColorLiteral:
return color.RGBA{
Expand All @@ -293,6 +375,7 @@ func colorFromLiteral(token Token, value interface{}) (color.Color, error) {
A: value.a,
}, nil
default:
return nil, tokenErrorInfo(token, "Color attribute must be either a tuple or string")
fmt.Printf("ff %v\n", value)
return nil, tokenErrorInfo(token, compilation, "Color attribute must be either a tuple or string")
}
}
18 changes: 16 additions & 2 deletions pkg/lang/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func (b ErrorInfoBundle) Error() string {
type ErrorInfo struct {
line uint
location string
stage stage
message string
}

Expand All @@ -49,18 +50,31 @@ func lexemeErrorInfo(line uint, lexeme rune, message string) ErrorInfo {
return ErrorInfo{
line: line,
location: fmt.Sprintf(" at '%c'", lexeme),
stage: lexing,
message: message,
}
}

func tokenErrorInfo(token Token, message string) ErrorInfo {
func tokenErrorInfo(token Token, stage stage, message string) ErrorInfo {
return ErrorInfo{
line: token.line,
location: fmt.Sprintf(" at '%c'", token.lexeme),
stage: stage,
message: message,
}
}

func (err ErrorInfo) Error() string {
return fmt.Sprintf("[line=%d] Error%s: %s", err.line, err.location, err.message)
stage := ""
if err.stage != unspecified {
stage = fmt.Sprintf(" %s ", err.stage)
}

return fmt.Sprintf(
"[line=%d]%sError%s: %s",
err.line,
stage,
err.location,
err.message,
)
}
39 changes: 39 additions & 0 deletions pkg/lang/lex.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const (
EqualSign
Comma

// Keywords
Let
Mut

// Special
SlideScope
SubScope
Expand Down Expand Up @@ -56,6 +60,9 @@ func (t TokenType) String() string {
"EqualSign",
"Comma",

"Let",
"Mut",

"SlideScope",
"SubScope",

Expand Down Expand Up @@ -222,6 +229,38 @@ func processRune(muncher *runeMuncher) (Token, error) {
lexeme: char,
}, nil

case 'l':
if chars, err := muncher.Peek(2); err == io.EOF {
return Token{}, lexemeErrorInfo(muncher.line, char, "Unexpected end of file")
} else if err != nil {
return Token{}, err
} else if string(chars[:]) == "et" {
muncher.eatN(2)
return Token{
Type: Let,
line: muncher.line,
lexeme: char,
}, nil
}

// Intentional fallthrough-- this might be an identifier

case 'm':
if chars, err := muncher.Peek(2); err == io.EOF {
return Token{}, lexemeErrorInfo(muncher.line, char, "Unexpected end of file")
} else if err != nil {
return Token{}, err
} else if string(chars[:]) == "ut" {
muncher.eatN(2)
return Token{
Type: Mut,
line: muncher.line,
lexeme: char,
}, nil
}

// Intentional fallthrough-- this might be an identifier

case '-':
if chars, err := muncher.Peek(2); err == io.EOF {
return Token{}, lexemeErrorInfo(muncher.line, char, "Unexpected end of file")
Expand Down
Loading

0 comments on commit 45a705f

Please sign in to comment.