Skip to content

Commit

Permalink
compiler: initialize the std::runtime package and integration with ar…
Browse files Browse the repository at this point in the history
…ray comparison handling
  • Loading branch information
mertcandav committed Sep 6, 2024
1 parent 493cb49 commit 752795d
Show file tree
Hide file tree
Showing 17 changed files with 303 additions and 52 deletions.
26 changes: 23 additions & 3 deletions src/julec/obj/cxx/expr.jule
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,12 @@ impl exprCoder {
ret
}

// Special cases for comparable types.
if op == TokenId.Eqs || op == TokenId.NotEq {
// If this binary operator comparing <any> type.
// The m.Left is will be <any> one always.
if obj::IsAny(lk) {
match {
| obj::IsAny(lk):
// If this binary operator comparing <any> type.
// The left operand is will be <any> one always.
if !rk.IsNil() && !obj::IsAny(rk) {
buf.WriteByte('(')
if op == TokenId.NotEq {
Expand All @@ -282,6 +284,24 @@ impl exprCoder {
buf.WriteStr("))")
ret
}
| lk.Arr() != nil:
// If this binary operator comparing array type.
// The left operand is will be array one always.
arr := lk.Arr()
mut f := obj::RuntimeFindFn(self.oc.ir.Runtime, obj::RuntimeFunc.arrayCmp)
mut ins := obj::FindGenericInstance(f, arr.Elem)
identCoder.funcIns(buf, ins)
if op == TokenId.NotEq {
buf.WriteByte('!')
}
buf.WriteByte('(')
buf.WriteStr(l)
buf.WriteStr(".begin(), ")
buf.WriteStr(r)
buf.WriteStr(".begin(), ")
buf.WriteStr(conv::Itoa(arr.N))
buf.WriteByte(')')
ret
}
}
buf.WriteByte('(')
Expand Down
19 changes: 14 additions & 5 deletions src/julec/obj/cxx/object.jule
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ impl ObjectCoder {
self.write("(this); }")
}

fn structureOperatorEq(mut &self, ident: str, mut &s: &StructIns) {
fn structureOperatorEq(mut &self, ident: str, mut &s: &StructIns, decl: bool) {
if !defaultEq(s) {
ret
}
Expand All @@ -581,7 +581,13 @@ impl ObjectCoder {
self.write(ident)
self.write(" *_self_, ")
self.write(ident)
self.write(" _other_) {")
self.write(" _other_)")
if decl {
// Declaration only.
self.write(";\n\n")
ret
}
self.write(" {")
if len(s.Fields) == 0 {
self.write(" return true; }\n\n")
ret
Expand Down Expand Up @@ -628,13 +634,13 @@ impl ObjectCoder {
self.write("}\n\n")
}

fn structureOperators(mut &self, mut &s: &StructIns) {
fn structureOperators(mut &self, mut &s: &StructIns, decl: bool) {
mut sb := StrBuilder.New(40)
identCoder.structureIns(sb, s)
ident := sb.Str()

// Binary.
self.structureOperatorEq(ident, s)
self.structureOperatorEq(ident, s, decl)
}

fn structureInsDecl(mut &self, mut &s: &StructIns) {
Expand Down Expand Up @@ -663,7 +669,8 @@ impl ObjectCoder {
self.indent()
self.write("};")

self.structureOperators(s)
const DeclOnly = true
self.structureOperators(s, DeclOnly)
}

fn structureDecl(mut &self, mut &s: &Struct) {
Expand Down Expand Up @@ -1132,6 +1139,8 @@ impl ObjectCoder {
}

fn structureIns(mut &self, mut &s: &StructIns) {
const DeclOnly = false
self.structureOperators(s, DeclOnly)
self.structureMethods(s)
self.write("\n\n")
self.structureOstream(s)
Expand Down
7 changes: 7 additions & 0 deletions src/julec/obj/determine.jule
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use comptime for std::comptime
use path for std::fs::path
use build for std::jule::build
use std::jule::sema::{
ImportInfo,
Scope,
Fn,
Var,
Expand Down Expand Up @@ -93,4 +94,10 @@ fn IsStdPackage(&f: str, p: str): bool {
// Do not handle '/' separators of p, because it
// valid path separator for all supported platforms.
ret strings::HasPrefix(f, path::Join(build::PathStdlib, p))
}

// Reports whether imp is implicitly imported.
// See developer reference (9).
fn IsImplicitImport(imp: &ImportInfo): bool {
ret imp.Token == nil
}
39 changes: 34 additions & 5 deletions src/julec/obj/ir.jule
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// license that can be found in the LICENSE file.

use env
use ast for std::jule::ast
use std::jule::build::{Log, LogKind}
use std::jule::importer::{JuleImporter, CompileInfo, Compiler, CppStd}
use sema for std::jule::sema
Expand All @@ -13,6 +14,7 @@ struct IR {
Root: str
Passes: []str
Main: &sema::Package
Runtime: &sema::ImportInfo // std::runtime
Used: []&sema::ImportInfo
Ordered: OrderedDefines
}
Expand All @@ -21,12 +23,13 @@ impl IR {
// Returns compiler IR of source code.
// Returned IR is lexed, parsed, and analyzed.
//
// - Returns nil reference and nil logs if path has not any Jule file.
// - Returns nil reference and logs if exist any log.
// - Returns (nil, nil) logs if path has not any Jule file.
// - Returns (nil, logs) if exist any log.
// - Returns IR and nil logs if everything is fine.
static fn Build(path: str, flags: sema::SemaFlag): (&IR, []Log) {
mut importer := JuleImporter.New(buildCompileInfo())
mut files, mut logs := importer.ImportPackage(path, true)
const UpdateMod = true // Use root module for project if exist.
mut files, mut logs := importer.ImportPackage(path, UpdateMod)
if len(logs) > 0 {
ret nil, logs
}
Expand All @@ -36,6 +39,10 @@ impl IR {
ret nil, nil
}

// Push std::runtime package to first file.
// Each Jule program should import this standard package.
pushRuntimeToAST(files[0])

mut pkg, logs := sema::AnalyzePackage(files, importer, flags)
if len(logs) > 0 {
ret nil, logs
Expand All @@ -48,6 +55,9 @@ impl IR {
}
ir.Passes = getAllUniquePasses(ir.Main, ir.Used)

// Set up special packages.
ir.Runtime = pkg.Files[0].Imports[0] // std::runtime

ret ir, nil
}
}
Expand Down Expand Up @@ -101,10 +111,10 @@ impl IR {

// Order defines at update ordered field of instance.
fn Order(mut self) {
self.Ordered.Structs = self.GetAllStructures()
self.Ordered.Globals = self.GetAllGlobals()
order(self.Ordered.Structs)
self.Ordered.Structs = self.GetAllStructures()
order(self.Ordered.Globals)
order(self.Ordered.Structs)
}
}

Expand Down Expand Up @@ -161,4 +171,23 @@ fn buildCompileInfo(): CompileInfo {
}

ret info
}

// See [std::jule] developer reference (9).
fn pushRuntimeToAST(mut &f: &ast::Ast) {
mut decl := &ast::UseDecl{
Token: nil, // Nil token is a flag for implicit declaration.
LinkPath: "std::runtime",
Alias: "",
Full: false,
Selected: nil,
Binded: false,
Std: true,
}
if len(f.UseDecls) > 0 {
f.UseDecls = append(f.UseDecls[:1], f.UseDecls...)
f.UseDecls[0] = decl
} else {
f.UseDecls = append(f.UseDecls, decl)
}
}
18 changes: 17 additions & 1 deletion src/julec/obj/lookup.jule
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use comptime for std::comptime
use ast for std::jule::ast
use std::jule::lex::{TokenId}
use std::jule::sema::{Trait, StructIns, TypeKind, FnIns}
use std::jule::sema::{Trait, StructIns, TypeKind, Fn, FnIns}

// Returns directive if exist.
fn FindDirective(mut &directives: []&ast::Directive, tag: str): &ast::Directive {
Expand Down Expand Up @@ -139,4 +139,20 @@ fn FindOperator(mut &s: &StructIns, op: TokenId, unary: bool): &FnIns {
|:
ret nil
}
}

// Returns function instance by generics.
// Assumes generics parameter have enough and same size with generic count of f.
// Panics if not exist any instance.
fn FindGenericInstance(mut &f: &Fn, generics: ...&TypeKind): &FnIns {
lookup:
for (_, mut ins) in f.Instances {
for i in ins.Generics {
if !ins.Generics[i].Kind.Equal(generics[i]) {
continue lookup
}
}
ret ins
}
panic("generic instance lookup failed, this is an implementation mistake")
}
2 changes: 1 addition & 1 deletion src/julec/obj/order.jule
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ lookup:
ret true
}

fn order[T](mut &s: []&T) {
fn order[T](mut s: []&T) {
mut i := 0
repeat:
mut j := i
Expand Down
19 changes: 19 additions & 0 deletions src/julec/obj/runtime.jule
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2024 The Jule Programming Language.
// Use of this source code is governed by a BSD 3-Clause
// license that can be found in the LICENSE file.

use std::jule::sema::{ImportInfo, Fn}

enum RuntimeFunc: str {
arrayCmp: "arrayCmp",
}

fn RuntimeFindFn(mut &runtime: &ImportInfo, ident: RuntimeFunc): &Fn {
const Binded = false
mut f := runtime.FindFn(ident, Binded)
if f == nil {
outln(ident)
panic("runtime function is not exist, this is an implementation mistake, this panic call should be unreachable")
}
ret f
}
3 changes: 2 additions & 1 deletion src/julec/opt/deadcode/define.jule
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,8 @@ impl ObjectDeadCode {

fn removeDeads(mut &self) {
for (_, mut used) in self.ir.Used {
if !used.Binded {
// Skip binded and implicit imports.
if !used.Binded && !obj::IsImplicitImport(used) {
self.removeDeadsPackage(used.Package)
}
}
Expand Down
26 changes: 24 additions & 2 deletions std/jule/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ It is also used by the official reference compiler JuleC and is developed in par
- [`lex`](./lex): Lexical analyzer.
- [`importer`](./importer): Default Jule importer.
- [`parser`](./parser): Parser.
- [`sema`](./sema): Semantic analyzer.
- [`sema`](./sema): Semantic analyzer and CAST (Compilation Abstract Syntax Tree) components.
- [`types`](./types): Elementary package for type safety.

## Developer Reference
Expand Down Expand Up @@ -49,4 +49,26 @@ For example:

- **(8)** Check `enum` declarations first before using them or any using possibility appears. Enum fields should be evaluated before evaluate algorithms executed. Otherwise, probably program will panic because of unevaluated enum field(s) when tried to use.

- **(8.1)** This is not apply for type enums. Type enum's fields are type alias actually. They are should anaylsis like type aliases.
- **(8.1)** This is not apply for type enums. Type enum's fields are type alias actually. They are should anaylsis like type aliases.

- **(9)** Semantic analysis supports built-in use declarations for developers, but this functionality is not for common purposes. These declarations do not cause problems in duplication analysis. For example, you added the `x` package as embedded in the AST, but the source also contains a use declaration for this package, in which case a conflict does not occur.\
\
These packages are specially processed and treated differently than standard use declarations. These treatments only apply to supported packages. To see relevant treatments, see implicit imports section of the reference.\
\
Typical uses are things like capturing or tracing private behavior. For example, the reference Jule compiler may embed the `std::runtime` package for some special calls. The semantic analyzer makes the necessary private calls for this embedded package when necessary. For example, appends instance to array compare generic method for array comparions.
- **(9.1)** The `Token` field is used to distinguish specific packages. If the `Token` field of the AST element is set to `nil`, the package built-in use declaration is considered. Accordingly, AST must always set the `Token` field for each use declaration.
- **(9.2)** Semantic ananlyzer must to guarantee any duplication will not appended to imports. So, built-in implicit imported packages cannot be duplicated even placed source file contains separate use declaration for the same package.
- **(9.3)** These packages should be placed as first use declarations of the main package's first file.
- **(9.4)** Semantic analyzer will not collect references for these packages. So any definition will not have a collection of references.
- **(9.5)** If semantic anlayzer encourter same package with any implicitly imported package in the same source file, assigns token of source file declaration to implicitly imported package. It also helps to caught dupliations for same packages after first one in the source file.

### Implicit Imports

Implicit imports are as described in developer reference (9). This section addresses which package is supported and what special behaviors it has.

#### `std::runtime`

This package is a basic package developed for Jule programs and focuses on runtime functionalities.

Here is the list of custom behaviors for this package;
- (1) `arrayCmp`: Developed to eliminate the need for the Jule compiler to generate code specifically for array comparisons for each backend and to reduce analysis cost. The semantic analyzer creates the necessary instance for this generic function when an array comparison is made. Thus, the necessary comparison function for each array is programmed at the Jule frontent level.
4 changes: 2 additions & 2 deletions std/jule/importer/importer.jule
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,12 @@ impl Importer for JuleImporter {
ret nil
}

fn ImportPackage(mut self, path: str, update_mod: bool): ([]&Ast, []Log) {
fn ImportPackage(mut self, path: str, updateMod: bool): ([]&Ast, []Log) {
mut dirents := Directory.Read(path) else {
ret nil, [flatCompilerErr("connot read package directory: " + path)]
}

if update_mod {
if updateMod {
newMod := mod::FindModuleFileDeep(path)
if newMod != self.mod {
self.mod = newMod
Expand Down
2 changes: 1 addition & 1 deletion std/jule/parser/parser.jule
Original file line number Diff line number Diff line change
Expand Up @@ -1068,7 +1068,7 @@ impl parser {

fn buildUseDecl(mut self, mut tokens: []&Token, binded: bool): &UseDecl {
mut decl := &UseDecl{
Token: tokens[0],
Token: tokens[0], // See developer reference (9).
Binded: binded,
}
if len(tokens) < 2 {
Expand Down
20 changes: 20 additions & 0 deletions std/jule/sema/analysis.jule
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ fn buildSymbols(mut &ast: &Ast, mut &importer: Importer, mut owner: &symbolBuild
ret nil, sb.errors
}

// See developer reference (9).
fn collectImplicitImports(mut &s: &Sema, mut &file: &SymbolTable) {
for (_, mut imp) in file.Imports {
if !isImplicitImport(imp) {
break
}
match imp.LinkPath {
| "std::runtime":
s.meta.runtime = imp
|:
panic("implementation mistake in implicit import collection, this panic call should be unreachable")
}
}
}

fn analyzePackage(mut &files: []&Ast, mut &importer: Importer, &flags: SemaFlag): (&Package, []Log) {
// Build symbol tables of files.
mut tables := make([]&SymbolTable, 0, len(files))
Expand All @@ -42,6 +57,11 @@ fn analyzePackage(mut &files: []&Ast, mut &importer: Importer, &flags: SemaFlag)
flags: flags,
meta: new(commonSemaMeta),
}

// Use first table (so first file) for this.
// See developer reference (9).
collectImplicitImports(sema, tables[0])

sema.check(tables)
if len(sema.errors) > 0 {
ret nil, sema.errors
Expand Down
Loading

0 comments on commit 752795d

Please sign in to comment.