package org.shedlang.compiler; import org.shedlang.compiler.ast.untyped.UntypedNamespaceNode; import org.shedlang.compiler.backends.Backend; import org.shedlang.compiler.backends.paths.TargetDir; import org.shedlang.compiler.config.ConfigChecker; import org.shedlang.compiler.config.ProfileConfig; import org.shedlang.compiler.config.ProjectConfig; import org.shedlang.compiler.parser.Parser; import org.shedlang.compiler.shid.ShidCodeGen; import org.shedlang.compiler.shid.ShidNamespace; import org.shedlang.compiler.sourcefiles.SourceFile; import org.shedlang.compiler.typechecker.TypeChecker; import org.shedlang.compiler.typechecker.TypeCheckerContext; import org.shedlang.compiler.types.Effects; import org.shedlang.compiler.types.FieldsInfo; import org.shedlang.compiler.types.NamespaceTypeInfo; import org.shedlang.compiler.types.Types; import org.shedlang.compiler.util.P; import org.zwobble.sourcetext.SourceText; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import static org.shedlang.compiler.parser.Tokeniser.tokenise; import static org.shedlang.compiler.sourcefiles.SourceFiles.collectSourceFiles; import static org.shedlang.compiler.typechecker.NamespaceDependencyLineariser.orderNamespaces; public class ShedCompiler { public TargetDir compile( Path projectRoot, Backend backend, Optional profileName ) throws IOException, InterruptedException { var config = ProjectConfig.read(projectRoot); var projectRoots = findProjectRoots(config); var untypedNamespaceNodes = projectRoots.stream() .flatMap(root -> { var sourceRoot = root.resolve("src"); try { var sourceFiles = collectSourceFiles(sourceRoot); return parseSource(sourceFiles).stream(); } catch (IOException exception) { throw new RuntimeException(exception); } }) .toList(); var namespaces = new ArrayList(); var typeCheckerContext = TypeCheckerContext.EMPTY .addNamespaceTypeInfo( ShedBuiltins.NAMESPACE_NAME, new NamespaceTypeInfo(FieldsInfo.of( FieldsInfo.freeField(ShedBuiltins.PRINT_NAME, ShedBuiltins.PRINT_TYPE) )) ) .enterGlobal(P.map( Map.entry("Bool", Types.metaType(Types.BOOL)), Map.entry("Int", Types.metaType(Types.INT)), Map.entry("Io", Types.effectType(Effects.IO)), Map.entry("String", Types.metaType(Types.STRING)), Map.entry("Unit", Types.metaType(Types.UNIT)), Map.entry(ShedBuiltins.PRINT_NAME, ShedBuiltins.PRINT_TYPE) )); for (var untypedNamespaceNode : orderNamespaces(untypedNamespaceNodes)) { var typeCheckNamespaceResult = TypeChecker.typeCheckNamespace(untypedNamespaceNode, typeCheckerContext); typeCheckerContext = typeCheckNamespaceResult.context(); var shidNamespace = ShidCodeGen.compileNamespace(typeCheckNamespaceResult.value()); namespaces.add(shidNamespace); } var typeInfos = typeCheckerContext.toTypeInfos(); ConfigChecker.configCheck(config, typeInfos); var profileConfig = profileName.map(config::profile) .orElse(ProfileConfig.DEFAULT); var targetDir = config.targetDir(backend, profileConfig); backend.compile( config, profileConfig, namespaces, typeInfos, targetDir ); return targetDir; } /** * Find the roots of any projects that require compilation, including * transitive dependencies. */ private LinkedHashSet findProjectRoots(ProjectConfig config) throws IOException { var projectRoots = new LinkedHashSet(); var pendingProjects = new ArrayDeque(); pendingProjects.add(config.root()); // TODO: detect cycles while (!pendingProjects.isEmpty()) { var projectRoot = pendingProjects.removeFirst(); if (projectRoots.add(projectRoot)) { var projectConfig = ProjectConfig.read(projectRoot); for (var dependency : projectConfig.dependencies()) { pendingProjects.add(dependency.path()); } } } return projectRoots; } private List parseSource( List sourceFiles ) throws IOException { var untypedNamespaceNodes = new ArrayList(); for (var sourceFile : sourceFiles) { var sourceContents = Files.readString(sourceFile.path(), StandardCharsets.UTF_8); var sourceText = SourceText.fromString(sourceFile.path().toString(), sourceContents); var tokens = tokenise(sourceText); var untypedNode = Parser.parseNamespace(tokens, sourceFile.namespaceName()); untypedNamespaceNodes.add(untypedNode); } return untypedNamespaceNodes; } }