diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 9109882..9cef804 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -7,6 +7,7 @@ on: pull_request: branches: - main + - feature/* jobs: build: diff --git a/Readme.md b/Readme.md index cf7cea3..bea2fe3 100644 --- a/Readme.md +++ b/Readme.md @@ -9,24 +9,24 @@ It is also very easy to learn. It is a very simple language and is not very powe 1. Clone the repo - ```` - git clone``` + ``` + git clone - ```` + ``` 2. cd to the repo - ```` - cd JIPL``` + ``` + cd JIPL - ```` + ``` 3. Run the main.go file - ```` - go run ./cmd/main.go``` + ``` + go run ./cmd/main.go - ```` + ``` 4. Now you can use JIPL in the terminal @@ -75,4 +75,3 @@ It is also very easy to learn. It is a very simple language and is not very powe 1. `for (initialization; condition; increment) { body ;}` 2. example 1. `for (def i = 0; i <= 10; i++) { if(i==3){return 3;}}` -5. diff --git a/cmd/REPL/repel.go b/cmd/REPL/repel.go index f9c550b..38ba07d 100644 --- a/cmd/REPL/repel.go +++ b/cmd/REPL/repel.go @@ -55,6 +55,8 @@ func Start(in io.Reader, out io.Writer) { pr := repParser.Parse() errs := repParser.Errors() + fmt.Println(pr.ToString()) + if len(errs) != 0 { io.WriteString(out, fmt.Sprintf("%d errors ❌ occurred while parsing your input \n", len(errs))) for idx, e := range errs { diff --git a/cmd/test/test.go b/cmd/test/test.go new file mode 100644 index 0000000..fe1318e --- /dev/null +++ b/cmd/test/test.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/houcine7/JIPL/internal/lexer" + "github.com/houcine7/JIPL/internal/parser" + "github.com/houcine7/JIPL/internal/runtime" + "github.com/houcine7/JIPL/internal/types" +) + +func main() { + // read file and evalal the code + + // read file + + fl, err := os.Open("./test.jipl") + defer fl.Close() + + if err != nil { + panic(err) + } + + // read file content + content, err := ioutil.ReadAll(fl) + + if err != nil { + panic(err) + } + + // eval the code + // lexer + + // fmt.Println(string(content)) + l := lexer.InitLexer(string(content)) + + p := parser.InitParser(l) + + pr :=p.Parse() + + // if err + if len(p.Errors()) != 0 { + for _, e := range p.Errors() { + panic(e) + } + } + + ctx := types.NewContext() + + //eval + evl,_ :=runtime.Eval(pr,ctx) + if evl!=nil { + fmt.Println(evl.ToString()) + } + +} \ No newline at end of file diff --git a/cmd/test/test.jipl b/cmd/test/test.jipl new file mode 100644 index 0000000..93787bb --- /dev/null +++ b/cmd/test/test.jipl @@ -0,0 +1,22 @@ +out("begin interpreting"); + +function isPrime(n) { + if (n < 2) { + return false; + } else { + for (def i = 2; i < n; i++) { + if (n % i == 0) { + return false; + } + } + return true; + } +}; + +out("here test 7 "); + +out(isPrime(7)); + +out("here test 2"); + +out(isPrime(1)); \ No newline at end of file diff --git a/internal/AST/ast_imp.go b/internal/AST/ast_imp.go index 0583917..4cd39d8 100644 --- a/internal/AST/ast_imp.go +++ b/internal/AST/ast_imp.go @@ -221,6 +221,21 @@ func (exStm *ExpressionStatement) ToString() string { func (exStm *ExpressionStatement) statementNode() {} + +// string literal node +type StringLiteral struct { + Token token.Token + Value string +} + +func (strLit *StringLiteral) TokenLiteral() string { + return strLit.Token.Value +} +func (strLit *StringLiteral) ToString() string { + return strLit.Token.Value +} +func (strLit *StringLiteral) expressionNode() {} + /* * Integer Literals Node * they can Occur in different type of expression's diff --git a/internal/AST/ast_interfaces.go b/internal/AST/ast_interfaces.go index d0c984e..cfe8b11 100644 --- a/internal/AST/ast_interfaces.go +++ b/internal/AST/ast_interfaces.go @@ -25,7 +25,7 @@ type Node interface{ * Expression nodes : does return a value */ type Statement interface { - Node + Node statementNode() } type Expression interface { diff --git a/internal/lexer/lexer.go b/internal/lexer/lexer.go index 2d5a7f4..414bc1f 100644 --- a/internal/lexer/lexer.go +++ b/internal/lexer/lexer.go @@ -28,7 +28,7 @@ func InitLexer(input string) *Lexer { */ func (l *Lexer) NextToken() token.Token { // var tokens []token.Token - var test token.Token + var tok token.Token l.ignoreWhiteSpace() switch l.char { @@ -36,104 +36,106 @@ func (l *Lexer) NextToken() token.Token { if l.peek() == '=' { prev := l.char l.readChar() - test = token.CreateToken(token.EQUAL, string(prev)+string(l.char)) + tok = token.CreateToken(token.EQUAL, string(prev)+string(l.char)) } else { - test = token.CreateToken(token.ASSIGN, string(l.char)) + tok = token.CreateToken(token.ASSIGN, string(l.char)) } case '&': if l.peek() == '&' { prev := l.char l.readChar() - test = token.CreateToken(token.AND, string(prev)+string(l.char)) + tok = token.CreateToken(token.AND, string(prev)+string(l.char)) } else { - test = token.CreateToken(token.ILLEGAL, string(l.char)) + tok = token.CreateToken(token.ILLEGAL, string(l.char)) } case '|': if l.peek() == '|' { prev := l.char l.readChar() - test = token.CreateToken(token.OR, string(prev)+string(l.char)) + tok = token.CreateToken(token.OR, string(prev)+string(l.char)) } else { - test = token.CreateToken(token.ILLEGAL, string(l.char)) + tok = token.CreateToken(token.ILLEGAL, string(l.char)) } case '+': if l.peek() == '+' { prev := l.char l.readChar() - test = token.CreateToken(token.INCREMENT, string(prev)+string(l.char)) + tok = token.CreateToken(token.INCREMENT, string(prev)+string(l.char)) } else { - test = token.CreateToken(token.PLUS, string(l.char)) + tok = token.CreateToken(token.PLUS, string(l.char)) } case '-': if l.peek() == '-' { prev := l.char l.readChar() - test = token.CreateToken(token.DECREMENT, string(l.char)+string(prev)) + tok = token.CreateToken(token.DECREMENT, string(l.char)+string(prev)) } else { - test = token.CreateToken(token.MINUS, string(l.char)) + tok = token.CreateToken(token.MINUS, string(l.char)) } case '/': - test = token.CreateToken(token.SLASH, string(l.char)) + tok = token.CreateToken(token.SLASH, string(l.char)) case '%': - test = token.CreateToken(token.MODULO, string(l.char)) + tok = token.CreateToken(token.MODULO, string(l.char)) case '*': - test = token.CreateToken(token.STAR, string(l.char)) + tok = token.CreateToken(token.STAR, string(l.char)) case '!': if l.peek() == '=' { prev := l.char l.readChar() - test = token.CreateToken(token.NOT_EQUAL, string(prev)+string(l.char)) + tok = token.CreateToken(token.NOT_EQUAL, string(prev)+string(l.char)) } else { - test = token.CreateToken(token.EX_MARK, string(l.char)) + tok = token.CreateToken(token.EX_MARK, string(l.char)) } case '<': if l.peek() == '=' { prev := l.char l.readChar() - test = token.CreateToken(token.LT_OR_EQ, string(prev)+string(l.char)) + tok = token.CreateToken(token.LT_OR_EQ, string(prev)+string(l.char)) } else { - test = token.CreateToken(token.LT, string(l.char)) + tok = token.CreateToken(token.LT, string(l.char)) } case '>': if l.peek() == '=' { prev := l.char l.readChar() - test = token.CreateToken(token.GT_OR_EQ, string(prev)+string(l.char)) + tok = token.CreateToken(token.GT_OR_EQ, string(prev)+string(l.char)) } else { - test = token.CreateToken(token.GT, string(l.char)) + tok = token.CreateToken(token.GT, string(l.char)) } case ')': - test = token.CreateToken(token.RP, string(l.char)) + tok = token.CreateToken(token.RP, string(l.char)) case '(': - test = token.CreateToken(token.LP, string(l.char)) + tok = token.CreateToken(token.LP, string(l.char)) case '{': - test = token.CreateToken(token.LCB, string(l.char)) + tok = token.CreateToken(token.LCB, string(l.char)) case '}': - test = token.CreateToken(token.RCB, string(l.char)) + tok = token.CreateToken(token.RCB, string(l.char)) case ',': - test = token.CreateToken(token.COMMA, string(l.char)) + tok = token.CreateToken(token.COMMA, string(l.char)) case ';': - test = token.CreateToken(token.S_COLON, string(l.char)) + tok = token.CreateToken(token.S_COLON, string(l.char)) + case '"': + tok = token.CreateToken(token.STRING, l.ReadString()) case 0: // program ends here - test = token.CreateToken(token.FILE_ENDED, string(rune(0))) + tok = token.CreateToken(token.FILE_ENDED, string(rune(0))) default: if utils.IsLetter(l.char) { ident := l.ReadIdentifier() - test = token.CreateToken(token.GetIdentifierTokenType(ident), ident) - return test + tok = token.CreateToken(token.GetIdentifierTokenType(ident), ident) + return tok } else if utils.IsDigit(l.char) { num := l.ReadNumber() - test = token.CreateToken(token.INT, num) - return test // this prevents calling read char which is already done with the method ReadNumber() + tok = token.CreateToken(token.INT, num) + return tok // this prevents calling read char which is already done with the method ReadNumber() } else { - test = token.CreateToken(token.ILLEGAL, string(l.char)) + tok = token.CreateToken(token.ILLEGAL, string(l.char)) } } l.readChar() // move to next char - return test + return tok } // HELPER FUNCTIONS @@ -152,6 +154,19 @@ func (l *Lexer) readChar() { } } + +// read string literals +func (l *Lexer) ReadString() string { + currPosition := l.currentPos + 1 + for { + l.readChar() + if l.char == '"' || l.char == 0 { + break + } + } + return l.input[currPosition:l.currentPos] +} + /* * This function peeks the next character * used in case of tokens that are compose of more than 2 tokens ( like "==" and "<=" ">=" and "!=") diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 1d41177..49a5ca8 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -20,8 +20,8 @@ type Parser struct { currToken token.Token // the current token in examination peekedToken token.Token // the next token after the current one - prefixParseFuncs map[token.TokenType]prefixParse - infixParseFuncs map[token.TokenType]infixParse + prefixParseFuncs map[token.TokenType]prefixParse // function used for prefix parsing + infixParseFuncs map[token.TokenType]infixParse // function used for infix parsing } /* @@ -60,7 +60,7 @@ func InitParser(l *lexer.Lexer) *Parser { p.addPrefixFn(token.IF, p.parseIfExpression) p.addPrefixFn(token.FUNCTION, p.parseFunctionExpression) p.addPrefixFn(token.FOR, p.parseForLoopExpression) - + p.addPrefixFn(token.STRING, p.parseStringLit) // infix expresion parseres p.infixParseFuncs = make(map[token.TokenType]infixParse) infixParseTokens := []token.TokenType{ @@ -133,7 +133,6 @@ func (p *Parser) Parse() *ast.Program { // Advance with token p.Next() } - return program } @@ -327,6 +326,12 @@ func (p *Parser) parseFnArgs() []ast.Expression { return ans } +// parse string lit +func (p *Parser) parseStringLit() ast.Expression { + exp := &ast.StringLiteral{Token: p.currToken, Value: p.currToken.Value} + return exp +} + // to parse Return statement func (p *Parser) parseReturnStmt() *ast.ReturnStatement { stm := &ast.ReturnStatement{Token: p.currToken} @@ -352,7 +357,7 @@ func (p *Parser) parseGroupExpression() ast.Expression { return grpExp } -// parser if expresssions +// parser if expressions func (p *Parser) parseIfExpression() ast.Expression { exp := &ast.IfExpression{Token: p.currToken} diff --git a/internal/parser/parser_cte.go b/internal/parser/parser_cte.go index 0018ed5..fc7292b 100644 --- a/internal/parser/parser_cte.go +++ b/internal/parser/parser_cte.go @@ -16,8 +16,6 @@ const ( PREFIX // -a or !a CALL // hello(a) - - INCREMENT // -- ++ ) diff --git a/internal/runtime/builtin.go b/internal/runtime/builtin.go new file mode 100644 index 0000000..99bed21 --- /dev/null +++ b/internal/runtime/builtin.go @@ -0,0 +1,16 @@ +package runtime + +import ( + "fmt" + + "github.com/houcine7/JIPL/internal/types" +) + + +var builtins = map[string]*types.BuiltIn{ + "out" : &types.BuiltIn{ Fn: func(args ...types.ObjectJIPL) types.ObjectJIPL { + for _, arg := range args { + fmt.Println(arg.ToString()) + } + return nil + }},} \ No newline at end of file diff --git a/internal/runtime/eval.go b/internal/runtime/eval.go index 8955623..bc8d817 100644 --- a/internal/runtime/eval.go +++ b/internal/runtime/eval.go @@ -55,6 +55,8 @@ func Eval(node ast.Node, ctx *types.Context) (types.ObjectJIPL, *debug.Error) { return evalABlockStatements(node.Statements,ctx) case *ast.IntegerLiteral: return &types.Integer{Val: node.Value},debug.NOERROR + case *ast.StringLiteral: + return &types.String{Val: node.Value},debug.NOERROR case *ast.BooleanExp: return types.BoolToObJIPL(node.Value),debug.NOERROR case *ast.PrefixExpression: @@ -73,22 +75,25 @@ func Eval(node ast.Node, ctx *types.Context) (types.ObjectJIPL, *debug.Error) { } func applyFunction(function types.ObjectJIPL, args []types.ObjectJIPL) (types.ObjectJIPL, *debug.Error) { - fn , ok := function.(*types.Function) - if !ok { - return nil,debug.NewError("not a function") - } - appendedCtx := appedCtx(fn,args) - eval,err := Eval(fn.Body,appendedCtx) - - if err != debug.NOERROR { - return nil,err - } + //fn , ok := function.(*types.Function) + switch function := function.(type) { + case *types.Function: + appendedCtx := appedCtx(function,args) + eval,err := Eval(function.Body,appendedCtx) - if eval,ok := eval.(*types.Return); ok { - return eval.Val,debug.NOERROR + if err != debug.NOERROR { + return nil,err + } + + if eval,ok := eval.(*types.Return); ok { + return eval.Val,debug.NOERROR + } + case *types.BuiltIn: + return function.Fn(args...),debug.NOERROR + default: + return nil,debug.NewError("not a function") } - - return eval,debug.NOERROR + return nil,debug.NewError("not a function") } @@ -133,11 +138,15 @@ func evalExpressions(node []ast.Expression, ctx *types.Context) ([]types.ObjectJ func evalIdentifier(node *ast.Identifier,ctx *types.Context) (types.ObjectJIPL, *debug.Error) { val,ok := ctx.Get(node.Value) - if !ok { - return nil, debug.NewError(fmt.Sprintf("identifier not found: %s", - node.Value)) + + if ok { + return val,debug.NOERROR } - return val,debug.NOERROR + builtin,ok := builtins[node.Value] + if ok { + return builtin,debug.NOERROR + } + return nil,debug.NewError(fmt.Sprintf("identifier not found: %s", node.Value)) } func evalIfExpression(ifExp *ast.IfExpression,ctx *types.Context) (types.ObjectJIPL , *debug.Error) { @@ -163,9 +172,28 @@ func evalInfixExpression(operator string, leftOperand, rightOperand types.Object return evalBoolInfixExpression(operator,leftOperand,rightOperand) } + if leftOperand.GetType() == types.T_STRING && + rightOperand.GetType() ==types.T_STRING { + return evlStringInfix(operator,leftOperand,rightOperand) + } + return nil,debug.NewError(fmt.Sprintf("type mismatch: %s %s %s", leftOperand.GetType(), operator, rightOperand.GetType())) } +func evlStringInfix(operator string, left, right types.ObjectJIPL) (types.ObjectJIPL , *debug.Error){ + stringObjRight := right.(*types.String) + stringObjLeft := left.(*types.String) + switch operator { + case "+": + return &types.String{Val: stringObjLeft.Val + stringObjRight.Val},debug.NOERROR + case "==": + return types.BoolToObJIPL(stringObjLeft.Val == stringObjRight.Val),debug.NOERROR + case "!=": + return types.BoolToObJIPL(stringObjLeft.Val != stringObjRight.Val),debug.NOERROR + default: + return nil, debug.NewError("unknown operator") + } +} func evalBoolInfixExpression(operator string, left, right types.ObjectJIPL) (types.ObjectJIPL , *debug.Error){ boolObjRight := right.(*types.Boolean) boolObjLeft := left.(*types.Boolean) diff --git a/internal/token/token_ctes.go b/internal/token/token_ctes.go index 9901da1..dc4aefa 100644 --- a/internal/token/token_ctes.go +++ b/internal/token/token_ctes.go @@ -15,6 +15,7 @@ const ( // literals [2,20] INT = 2 // int values + STRING = 3 // string values //OPERATORS values [40,80] ASSIGN = 40 // = @@ -47,6 +48,8 @@ const ( LCB = 24 // { RCB = 25 // } + + // KEYWORDS [100,110] FUNCTION = 100 // function keyword DEF = 101 // an identifier definition diff --git a/internal/types/types.go b/internal/types/types.go index 750124f..3ec7530 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -18,6 +18,10 @@ type Integer struct { Val int } + +type String struct { + Val string +} type Boolean struct { Val bool } @@ -41,6 +45,16 @@ type Context struct { Outer *Context // for nested scopes } +type BuiltIn struct { + Fn func(args ...ObjectJIPL) ObjectJIPL +} + +func (bi *BuiltIn) GetType() TypeObj { + return T_BUILTIN +} +func (bi *BuiltIn) ToString() string { + return "builtin function" +} func NewContextWithOuter(outer *Context) *Context { ctx := NewContext() @@ -133,6 +147,13 @@ func BoolToObJIPL(bl bool) ObjectJIPL{ } } +func (str *String) GetType() TypeObj { + return T_STRING +} +func (str *String) ToString() string { + return str.Val +} + // cte of types const ( @@ -141,6 +162,8 @@ const ( T_UNDEFINED = "UNDEFINED" T_RETURN = "RETURN" T_FUNCTION = "FUNCTION" + T_STRING = "STRING" + T_BUILTIN = "BUILTIN" ) var (