Browse Source

Consider operators as part of a command chain.

Beoran 4 years ago
parent
commit
27222415ce
4 changed files with 126 additions and 94 deletions
  1. 33 17
      README.md
  2. 40 20
      design_muesli.muesli
  3. 44 51
      parser.go
  4. 9 6
      vm.go

+ 33 - 17
README.md

@@ -1,24 +1,39 @@
 # Muesli is a Multi Use Embeddable Scripting Language Implementation.
 
 ## Introduction
-It is a scripting language implemented in Go, that has a simple command based
-TCL like syntax but with rich semantics. Unlice TCL, all values are typed,
-and many types are supported, not just string values. Additionaly, if so 
-desired, every symbol used in the syntax can be replaced by a key word, 
-leading to a native language programming feel.
+
+Muesli is a scripting language implemented in Go, that has a simple command 
+chain based TCL or shell like syntax but with rich semantics. Unlike TCL
+or shels, all muelsi values are typed, and many types are supported, 
+not just string values. Additionaly, if so desired, every symbol used in the 
+syntax can be replaced by a key word, leading to a native language programming 
+feel.
 
 ## Syntax
 
-A Muesli program consists of one or more commands, separated by newlines or 
-periods. Commands can be grouped together in blocks between braces {}. 
-Lists are formed bu placing their contents between brackest []. A single command 
-can be placed in parenthesis (), where the return value of the command will be 
-subsituted.
+A Muesli program consists of blocks and/or chains, separated by newlines or 
+periods. A chain consists of one or more commands, linked together by operators.
+
+Blocks consist of chains grouped together in blocks between braces {}. The 
+commands in a block are not executed immediately but stored.
+
+A single chain can be placed in parenthesis (), where the return value of the 
+chain will be subsituted.  Lists are formed by placing their contents 
+between brackets [].
 
-A command starts with a word, and is followed by any number of arguments,
-which may be blocks, parenthesis, lists , or values. Like this, the language 
-itself has no control statements, such as if, these are implemented as commands 
-in stead.
+Every command has a return value, which can be of any type, and not just a 
+number like in most shells. Furthermore commands have input and output, which 
+default to stdin and stdout, but can be redirected or reconfigured. 
+
+There are three types of commands, namely direct commands indirect commmands
+and literal commands. A direct command starts with a name, and is followed by 
+any number of parameters. Parameters may be which may be blocks, parenthesis, 
+lists, names or literals, or values. An indirect command starts with a 
+substitution followed by parameters. A literal command is simply a numeric,
+string, boolean or nil literal that evaluates to itself.
+
+The language itself has no control statements, such as if, or for, 
+these are implemented as commands in stead.
 
 Muesly supports several kinds of values, namely: 
 - Integers
@@ -26,9 +41,10 @@ Muesly supports several kinds of values, namely:
 - Strings between "double quotes"
 - The booleans !true and !false
 - The nil value !nil
-- Words, which are simple runs of alphanumercial characters starting with a lower case lettter, such as foo
-- Types which are runs of alphanumerical characters starting with an upper case letter. such as Foo
-
+- Words, which are simple runs of alphanumercial characters starting with a 
+  lower case lettter, such as foo
+- Types which are runs of alphanumerical characters starting with an upper 
+  case letter, such as Foo.
 
 
 

+ 40 - 20
design_muesli.muesli

@@ -1,52 +1,54 @@
 # Muesli is a Multi Use Embeddable Scripting Language Interpreter. 
 # It is a scripting language with a TCL or shell like syntax, but somewhat 
 # OOP and LISP like semantics.
+#
 # First it will be an embeddable scripting language implemented in Go.
 # In what follows I will describe the design of the language though example.
 # 
 # Why is there a # before these lines? # begins a comment, until the end of the 
-# line. MUESLI does not execute comments but they are collected 
-# for documentation purposes. More on that topic later
+# line. MUESLI does not execute comments but skips them in stead.
 # Muesli is a line based language.
 #
 # Muesli consists of newline separated statements, however, a newline 
-# after { or the do keyword is ignored and does not count as a separator.
+# after { is ignored and does not count as a separator.
+#
+# There are also bock comments: 
 #{ This is a block comment, it nests 
-but the {} pairs must match. 
+but the {} pairs must match.
 }
 
 
-
 # Empty lines are ignored.
 
-# ... 
-# Muesli supports integer constants with type Int
+# Muesly supports a whole range of typed values. 
+# Muesli supports integer constants of type Int
 p 1
 p 2
 p 378
 p +108
 p -878
 
-# It also supports multi line string constants with escapes, with type String
+# It also supports multi line string constants with escapes, of type String
 p "Hello world\"
 " 
 
-# and multi line strings without escapes
+# Muesli also supports multi line strings without escapes
 p `
 "Pop" goes the 
 weasel's tail.
 `
 
-# Built in identfiers for type Bool or nil
+# There are built in identifiers for type Bool or nil
 p !true !false !nil
 
-# And simple floating point constants, but no exponential notation, with type Float
+# Museli has simple floating point constants, but no exponential notation, 
+# of type Float
 p +0.5
 p -7.000005
 
 # Lists can be created between [ ] and may be heterogenous or homogenous. 
 # The [ ] must be space separated. 
-# The type is Any[] if heterogenous,  
+# The type is Any[] if the list is heterogenous. 
 
 p [ foo "bar" ]
 
@@ -59,19 +61,18 @@ p [ 1 2 3 ]
 # For example: this-IS*a/single+Word._ 
 # The value of a word is a string with the word itself. 
 # If the word is at the beginning of the line it is invoked as a command.
-# Muesli's basic syntax is that of the command. Spaces separate the arguments.
-# of the command. The first word is the command, the rest are the arguments. 
+# Muesli's basic syntax is that of the command chain. Spaces separate the 
+# arguments of the command. The first word is the command, 
+# the rest are the arguments. 
 
 print "hello" world 7 0.9
 
-# the previous command outputs: "hello world 7 0.9" to standard output
-
-# A command has one or more results that can be captured with a parenthesis
+# the previous command outputs: "hello world 7 0.9" to command output
 
+# A command has one or more results that can be captured with a parenthesis.
 
 print ( mul 3 ( sum 5 7 ) )
 
-
 # Commands can be grouped into blocks. The value of the block is that of it's last command.
 
 # block { 
@@ -114,7 +115,7 @@ to open[door Door key Item]  Bool {
 # the types of the arguments. 
 cover open open[door Door key Item]
 
-# Variables are not part of the language but there arebuiltin commands
+# Variables are not part of the language but there are builtin commands
 # to define them. Variables are set in the current scope with
 set a 10
 # And fetched in the current scope with get
@@ -147,9 +148,28 @@ if (less a 10) {
     print "More"
 }
 
+# A literal command evaluates to itself and writes it's value to command output.
+5
+
+# Commands can be chained together using operators.
+# Operators in muesli are always binary. There are no unary or ternary 
+# operators.
+# An operator is any token that starts with -+*/|>< and contains no 
+# alphanumeric characters.
+# The semantics of operators is that the operator is called as if it was 
+# a command, with the first operand as the first argument and the second 
+# operand as the second argument.
+
+=foo ( sqrt 2.0 + 3.14 - 2.1 )
+# Means the same as 
+# =foo ( sub ( add ( sqrt 2.0) 2.1 ) ) 
+
+$foo 
+#
+
 # That's all there is to the syntax. Apart from the built in commands, 
 # the semantics are up to you to implement as 
-# embedded commands.
+# embedded commands, or by redirecting the command output and input.
 # Do note that MUESLI uses dynamic scoping for ease of implementation
 
 #{

+ 44 - 51
parser.go

@@ -60,31 +60,50 @@ GETTER -> get ORIGIN .
 GETCALL -> comma COMMAND | .
 ORIGIN -> word | SETTER | GETTER .
 
-Note that the Muesli syntax description below is not strictly LL(1), 
-notably the optional operator after a getter, but the recursive descent parser 
-can parse it correctly.
 
-PROGRAM -> STATEMENTS.
-CLOSED -> BLOCK | LIST | PARENTHESIS .
+Or, new syntax with operators for command chains at the top level, 
+for easier shunting the operator into commands:
+
+PROGRAM -> STATEMENTS .
 STATEMENTS -> STATEMENT STATEMENTS | .
-STATEMENT -> CLOSED | EXPRESSION eos | eos .
-COMMAND -> WORDVALUE DETAILS .
-DETAILS -> OPERATION | PARAMETERS .
-OPT_OPER -> OPERATION | .
-OPERATION -> operator COMMAND .
-PARAMETERS -> PARAMETER PARAMETERS | .
-PARAMETER -> WORDVALUE | GETTER | SETTER | CLOSED .
-EXPRESSION -> COMMAND | GETTER | SETTER.
-PARENTHESIS -> openparen EXPRESSION closeparen .
+STATEMENT -> BLOCK | CHAIN eos | eos .
+CHAIN -> COMMAND LINKS . 
+LINKS -> LINK LINKS | .
+LINK -> operator COMMAND .  
+COMMAND -> DIRECT | INDIRECT | LITERAL . 
+DIRECT -> NAME PARAMETERS .
+INDIRECT -> SUBSTITUTION PARAMETERS .
+PARAMETER -> NAME | LITERAL | SUBSTITUTION | BLOCK .
+SUBSTITUTION -> PARENTHESIS | GETTER | SETTER | LIST .
+PARENTHESIS -> openparen CHAIN closeparen .
 BLOCK -> openblock STATEMENTS closeblock .
 LIST -> openlist PARAMETERS closelist .
-VALUE -> string | int | float | symbol | type .
-WORDVALUE -> word | VALUE.
-SETTER -> set TARGET . 
-TARGET -> word PARAMETER | GETTER PARAMETER .
-GETTER -> get ORIGIN OPT_OPER .
-GETCALL -> comma COMMAND | .
-ORIGIN -> word | SETTER | GETTER .
+LITERAL -> string | int | float | true | false | nil .
+NAME -> word | type | symbol .
+SETTER -> set PARAMETER PARAMETER . 
+GETTER -> get PARAMETER .
+
+semantics: 
+
+
+- A muesli program consists of statements.
+- A statement is either a block, or a command chain terminated by eos, or just an eos .
+- A command chain consists of commands chained together with operators.
+- A command may be a direct command, an indirect command, or a literal value. 
+- A () parenthesis gets substituted inline anywhere it occurs,  also in the
+  beginning of a command.
+- A bracketed list [ elem1 elem2 ... ]  is syntactic sugar for (list elem1 elem 2)
+- A dollar getter $varname is syntactic sugar for (get varname)
+- A equals setter =varname value is syntactic sugar for (set varname value)
+- Therefore, parenthesis, lists, getters and setters are allowed anywhere,
+  also in the beginning of the command with substitution semantics.
+- If a command starts with a substitution, it is an indirect command. The
+  substitution is evaluated and it's value is looked up as the command name.
+  In case the result is not a name, this is a run time error.
+  In case the result is a list, each value of the list is executed as a command
+  with the same parameters.
+- A literal evaluate to itself.
+- A name also evaluates as itself but is specific for direct commands.
 
 
  *
@@ -212,8 +231,6 @@ func AstKindForToken(token Token) AstKind {
 			 return AstKindValue
 		case TokenKindWord:
 			 return AstKindWord
-        case TokenKindOperator:
-			 return AstKindWord
 		case TokenKindType:
 			return AstKindType
 		default:
@@ -399,11 +416,7 @@ func (parser *Parser) ParseSet() *Ast {
 	ast := NewAstWithToken(AstKindSet, set)
 	target := parser.ParseTarget()
 	ast.AppendChild(target)
-    if parser.NextIs(TokenKindOperator)  {
-        ast = parser.ParseOperator(ast)
-    }
-    
-    return ast
+	return ast
 }
 
 func (parser *Parser) ParseGet() *Ast {
@@ -413,35 +426,15 @@ func (parser *Parser) ParseGet() *Ast {
 	ast := NewAstWithToken(AstKindGet, get)
 	target := parser.ParseOrigin()
 	ast.AppendChild(target)
-    if parser.NextIs(TokenKindOperator)  {
-        ast = parser.ParseOperator(ast)
-    }
-    
 	return ast
 }
 
-func (parser *Parser) ParseOperator(operand *Ast) *Ast {
-    parser.LogDebug("ParseOperator: %s\n", parser.current.String())
-    
-    operator := parser.Require(TokenKindOperator)
-    arguments := parser.ParseArguments(operand)
-    command := NewAstWithToken(AstKindCommand, operator)
-    command.AppendChild(arguments)
-    return command
-}
-
 func (parser *Parser) ParseCommand() *Ast {
     parser.LogDebug("ParseCommand: %s\n", parser.current.String())
 
-    wordvalue := parser.ParseWordValue()
-    
-    // operator command
-    if parser.NextIs(TokenKindOperator) {
-        return parser.ParseOperator(wordvalue)
-    } 
-    
-    arguments := parser.ParseArguments()
-	command := NewAstWithToken(AstKindCommand, wordvalue.Token())
+	word := parser.Require(TokenKindWord, TokenKindType)
+	arguments := parser.ParseArguments()
+	command := NewAstWithToken(AstKindCommand, word)
 	command.AppendChild(arguments)
 	return command
 }

+ 9 - 6
vm.go

@@ -4,6 +4,7 @@ package muesli
 import (
 	"fmt"
 	"strings"
+    "io"
 )
 
 
@@ -462,16 +463,18 @@ type Tracer interface {
 
 // Virtual machine
 type VM struct {
-	TopScope *Scope // Top level scope
-	TopFrame *Frame // Top level scope
-	*Scope          // Current Scope
-	*Frame          // Current frame
-	Tracer			// Tracer to emit tracing info to, could be used for logging or debugging
+	TopScope *Scope     // Top level scope
+	TopFrame *Frame     // Top level scope
+	*Scope              // Current Scope
+	*Frame              // Current frame
+	Tracer			    // Tracer to emit tracing info to, could be used for logging or debugging
+    Output  io.Writer   // Writer to which command output should be written.
+    Input   io.Reader   // Reader from which command input should be read.
 	ExitStatus int
 }
 
 func NewVM() *VM {
-	vm := &VM{NewScope(nil), NewFrame(nil, nil), nil, nil, nil, 0}
+	vm := &VM{NewScope(nil), NewFrame(nil, nil), nil, nil, nil, nil, nil, 0}
 	vm.Scope = vm.TopScope
 	vm.Frame = vm.TopFrame
 	return vm